如何快速掌握Python數據採集與網路爬蟲技術
摘要:本文詳細講解了python網路爬蟲,並介紹抓包分析等技術,實戰訓練三個網路爬蟲案例,並簡單補充了常見的反爬策略與反爬攻克手段。通過本文的學習,可以快速掌握網路爬蟲基礎,結合實戰練習,寫出一些簡單的爬蟲項目。
演講嘉賓簡介:
韋瑋,企業家,資深IT領域專家/講師/作家,暢銷書《精通Python網路爬蟲》作者,阿里雲社區技術專家。
本次直播視頻精彩回顧,戳這裡!
本節課(爬蟲)所有代碼下載
以下內容根據演講嘉賓視頻分享以及PPT整理而成。
本次的分享主要圍繞以下五個方面:
一、數據採集與網路爬蟲技術簡介
二、網路爬蟲技術基礎
三、抓包分析
四、挑戰案例
五、推薦內容
一、數據採集與網路爬蟲技術簡介
網路爬蟲是用於數據採集的一門技術,可以幫助我們自動地進行信息的獲取與篩選。從技術手段來說,網路爬蟲有多種實現方案,如PHP、Java、Python ...。那麼用python 也會有很多不同的技術方案(Urllib、requests、scrapy、selenium...),每種技術各有各的特點,只需掌握一種技術,其它便迎刃而解。同理,某一種技術解決不了的難題,用其它技術或方依然無法解決。網路爬蟲的難點並不在於網路爬蟲本身,而在於網頁的分析與爬蟲的反爬攻克問題。希望在本次課程中大家可以領會爬蟲中相對比較精髓的內容。
二、網路爬蟲技術基礎
在本次課中,將使用Urllib技術手段進行項目的編寫。同樣,掌握了該技術手段,其他的技術手段也不難掌握,因為爬蟲的難點不在於技術手段本身。本知識點包括如下內容:
·Urllib基礎
·瀏覽器偽裝
·用戶代理池
·糗事百科爬蟲實戰
需要提前具備的基礎知識:正則表達式
1)Urllib基礎
爬網頁
打開python命令行界面,兩種方法:ulropen()爬到內存,urlretrieve()爬到硬碟文件。
>>> import urllib.request#open百度,讀取並爬到內存中,解碼(ignore可忽略解碼中的細微錯誤), 並賦值給data>>> data=urllib.request.ulropen("http://www.baidu.com").read().decode("utf-8」, 「ignore")#判斷網頁內的數據是否存在,通過查看data長度>>> len(data)提取網頁標題#首先導入正則表達式, .*?代表任意信息,()代表要提取括弧內的內容>>> import re#正則表達式>>> pat="<title>(.*?)</title>"#re.compile()指編譯正則表達式#re.S是模式修正符,網頁信息往往包含多行內容,re.S可以消除多行影響>>> rst=re.compile(pat,re.S).findall(data)>>> print(rst)#[『百度一下,你就知道』]
同理,只需換掉網址可爬取另一個網頁內容
>>> data=urllib.request.ulropen("http://www.jd.com").read().decode("utf-8", "ignore")>>> rst=re.compile(pat,re.S).findall(data)>>> print(rst)
上面是將爬到的內容存在內存中,其實也可以存在硬碟文件中,使用urlretrieve()方法
>>> urllib.request.urlretrieve("http://www.jd.com",filename="D:/我的教學/Python/阿里雲系列直播/第2次直播代碼/test.html")
之後可以打開test.html,京東網頁就出來了。由於存在隱藏數據,有些數據信息和圖片無法顯示,可以使用抓包分析進行獲取。
2)瀏覽器偽裝
嘗試用上面的方法去爬取糗事百科網站url="https://www.qiushibaike.com/",會返回拒絕訪問的回復,但使用瀏覽器卻可以正常打開。那麼問題肯定是出在爬蟲程序上,其原因在於爬蟲發送的請求頭所導致。
打開糗事百科頁面,如下圖,通過F12,找到headers,這裡主要關注用戶代理User-Agent欄位。User-Agent代表是用什麼工具訪問糗事百科網站的。不同瀏覽器的User-Agent值是不同的。那麼就可以在爬蟲程序中,將其偽裝成瀏覽器。
將User-Agent設置為瀏覽器中的值,雖然urlopen()不支持請求頭的添加,但是可以利用opener進行addheaders,opener是支持高級功能的管理對象。代碼如下:
#瀏覽器偽裝url="https://www.qiushibaike.com/"#構建openeropener=urllib.request.build_opener()#User-Agent設置成瀏覽器的值UA=("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0")#將UA添加到headers中opener.addheaders=[UA]urllib.request.install_opener(opener)data=urllib.request.urlopen(url).read().decode("utf-8","ignore")
3)用戶代理池
在爬取過程中,一直用同樣一個地址爬取是不可取的。如果每一次訪問都是不同的用戶,對方就很難進行反爬,那麼用戶代理池就是一種很好的反爬攻克的手段。
第一步,收集大量的用戶代理User-Agent
#用戶代理池uapools=[ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)", ]
第二步,建立函數UA(),用於切換用戶代理User-Agent
def UA(): opener=urllib.request.build_opener() #從用戶代理池中隨機選擇一個 thisua=random.choice(uapools) ua=("User-Agent",thisua) opener.addheaders=[ua] urllib.request.install_opener(opener) print("當前使用UA:"+str(thisua))
for循環,每訪問一次切換一次UA
for i in range(0,10): UA() data=urllib.request.urlopen(url).read().decode("utf-8","ignore")
每爬3次換一次UA
for i in range(0,10): if(i%3==0): UA() data=urllib.request.urlopen(url).read().decode("utf-8","ignore")
(*每幾次做某件事情,利用求余運算)
4)第一項練習-糗事百科爬蟲實戰
目標網站:https://www.qiushibaike.com/
需要把糗事百科中的熱門段子爬取下來,包括翻頁之後內容,該如何獲取?
第一步,對網址進行分析,如下圖所示,發現翻頁之後變化的部分只是page後面的頁面數字。
第二步,思考如何提取某個段子?查看網頁代碼,如下圖所示,可以發現<div class="content">的數量和每頁段子數量相同,可以用<div class="content">這個標識提取出每條段子信息。
第三步,利用上面所提到的用戶代理池進行爬取。首先建立用戶代理池,從用戶代理池中隨機選擇一項,設置UA。
import urllib.requestimport reimport random#用戶代理池uapools=[ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)", ]def UA(): opener=urllib.request.build_opener() thisua=random.choice(uapools) ua=("User-Agent",thisua) opener.addheaders=[ua] urllib.request.install_opener(opener) print("當前使用UA:"+str(thisua))#for循環,爬取第1頁到第36頁的段子內容for i in range(0,35): UA() #構造不同頁碼對應網址 thisurl="http://www.qiushibaike.com/8hr/page/"+str(i+1)+"/" data=urllib.request.urlopen(thisurl).read().decode("utf-8","ignore") #利用<div class="content">提取段子內容 pat=<div class="content">.*?<span>(.*?)</span>.*?</div> rst=re.compile(pat,re.S).findall(data) for j in range(0,len(rst)): print(rst[j]) print("-------")
還可以定時的爬取:
Import time#然後在後面調用time.sleep()方法
換言之,學習爬蟲需要靈活變通的思想,針對不同的情況,不同的約束而靈活運用。
三、抓包分析
抓包分析可以將網頁中的訪問細節信息取出。有時會發現直接爬網頁時是無法獲取到目標數據的,因為這些數據做了隱藏,此時可以使用抓包分析的手段進行分析,並獲取隱藏數據。
1)Fiddler簡介
抓包分析可以直接使用瀏覽器F12進行,也可以使用一些抓包工具進行,這裡推薦Fiddler。Fiddler下載安裝。假設給Fiddler配合的是火狐瀏覽器,打開瀏覽器,如下圖,找到連接設置,選擇手動代理設置並確定。
假設打開百度,如下圖,載入的數據包信息就會在Fiddler中左側列表中列出來,那麼網站中隱藏相關的數據可以從載入的數據包中找到。
2)第二項練習-騰訊視頻評論爬蟲實戰
目標網站:https://v.qq.com/
需要獲取的數據:某部電影的評論數據,實現自動載入。
首先可以發現騰訊視頻中某個視頻的評論,在下面的圖片中,如果點擊」查看更多評論」,網頁地址並無變化,與上面提到的糗事百科中的頁碼變化不同。而且通過查看源代碼,只能看到部分評論。即評論信息是動態載入的,那麼該如何爬取多頁的評論數據信息?
第一步,分析騰訊視頻評論網址變化規律。點擊」查看更多評論」,同時打開Fiddler,第一條信息的TextView中,TextView中可以看到對應的content內容是unicode編碼,剛好對應的是某條評論的內容。
解碼出來可以看到對應評論內容。
將第一條信息的網址複製出來進行分析,觀察其中的規律。下圖是兩個緊連著的不同評論的url地址,如下圖,可以發現只有cursor欄位發生變化,只要得到cursor,那麼評論的地址就可以輕鬆獲得。如何找到cursor值?
第二步,查找網址中變化的cursor欄位值。從上面的第一條評論信息里尋找,發現恰好在last欄位值與後一條評論的cursor值相同。即表示cursor的值是迭代方式生成的,每條評論的cursor信息在其上一條評論的數據包中尋找即可。
第三步,完整代碼
a.騰訊視頻評論爬蟲:獲取」深度解讀」評論內容(單頁評論爬蟲)
#單頁評論爬蟲import urllib.requestimport re#https://video.coral.qq.com/filmreviewr/c/upcomment/[視頻id]?commentid=[評論id]&reqnum=[每次提取的評論的個數]#視頻idvid="j6cgzhtkuonf6te"#評論idcid="6233603654052033588"num="20"#構造當前評論網址url="https://video.coral.qq.com/filmreviewr/c/upcomment/"+vid+"?commentid="+cid+"&reqnum="+numheaders={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0", "Content-Type":"application/javascript", }opener=urllib.request.build_opener()headall=[]for key,value in headers.items(): item=(key,value) headall.append(item)opener.addheaders=headallurllib.request.install_opener(opener)#爬取當前評論頁面data=urllib.request.urlopen(url).read().decode("utf-8")titlepat="title":"(.*?)"commentpat="content":"(.*?)"titleall=re.compile(titlepat,re.S).findall(data)commentall=re.compile(commentpat,re.S).findall(data)for i in range(0,len(titleall)): try: print("評論標題是:"+eval(u"+titleall[i]+")) print("評論內容是:"+eval(u"+commentall[i]+")) print("------") except Exception as err: print(err)
b.騰訊視頻評論爬蟲:獲取」深度解讀」評論內容(自動切換下一頁評論的爬蟲)
#自動切換下一頁評論的爬蟲import urllib.requestimport re#https://video.coral.qq.com/filmreviewr/c/upcomment/[視頻id]?commentid=[評論id]&reqnum=[每次提取的評論的個數]vid="j6cgzhtkuonf6te"cid="6233603654052033588"num="3"headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0", "Content-Type":"application/javascript", }opener=urllib.request.build_opener()headall=[]for key,value in headers.items(): item=(key,value) headall.append(item)opener.addheaders=headallurllib.request.install_opener(opener)#for循環,多個頁面切換for j in range(0,100): #爬取當前評論頁面 print("第"+str(j)+"頁")#構造當前評論網址thisurl="https://video.coral.qq.com/filmreviewr/c/upcomment/"+vid+"?commentid="+cid+"&reqnum="+num data=urllib.request.urlopen(thisurl).read().decode("utf-8") titlepat="title":"(.*?)","abstract":" commentpat="content":"(.*?)" titleall=re.compile(titlepat,re.S).findall(data) commentall=re.compile(commentpat,re.S).findall(data) lastpat="last":"(.*?)" #獲取last值,賦值給cid,進行評論id切換 cid=re.compile(lastpat,re.S).findall(data)[0] for i in range(0,len(titleall)): try: print("評論標題是:"+eval(u"+titleall[i]+")) print("評論內容是:"+eval(u"+commentall[i]+")) print("------") except Exception as err: print(err)
四、挑戰案例
1)第三項練習-中國裁判文書網爬蟲實戰
目標網站:http://wenshu.court.gov.cn/
需要獲取的數據:2018年上海市的刑事案件接下來進入實戰講解。
第一步,觀察換頁之後的網頁地址變化規律。打開中國裁判文書網2018年上海市刑事案件的第一頁,在換頁時,如下圖中的地址,發現網址是完全不變的,這種情況就是屬於隱藏,使用抓包分析進行爬取。
第二步,查找變化欄位。從Fiddler中可以找到,獲取某頁的文書數據的地址:http://wenshu.court.gov.cn/List/ListContent
可以發現沒有對應的網頁變換,意味著中國裁判文書網換頁是通過POST進行請求,對應的變化數據不顯示在網址中。通過F12查看網頁代碼,再換頁操作之後,如下圖,查看ListContent,其中有幾個欄位需要了解:
Param:檢索條件
Index:頁碼
Page:每頁展示案件數量
...
重要的是最後三個欄位(vl5x,number,guid)該如何獲取?首先,guid即uuid,叫全球唯一標識,是利用python中的uuid隨機生成的欄位。其次是number欄位,找到ListContent上面的GetCode請求,恰好其Response中包含了number欄位的值。而GetCode又是通過POST請求的,發現請求的欄位只要guid這一項,那麼問題便迎刃而解。
最後,難點在於vl5x欄位如何獲取?打開Fiddler,在換頁操作後,查看ListContent中的vl5x的值,並在此次ListContent之前出現的數據包中的TextView里尋找這個欄位或值,一般的網站可以很容易找到,但中國裁判文書網是政府網站,反爬策略非常高明,尋找的過程需要極高的耐心。
事實上,中國裁判文書網的vl5x欄位可以從某個js包中獲得,獲取的方式是通過getKey()函數。從網頁源代碼中找到getKey()函數的js代碼,由於代碼是packed狀態,用unpacked工具, 將其進行解碼,後利用js界面美觀工具可以方便理解。
但無關緊要,只需直接將getKey()函數s代碼複製到unpack_js.html中,就可以解出vl5x欄位的值,其中需要用到Cookie中的vjkl5欄位值。需要注意提前下載好base64.js和md5.js,並在unpack_js.html載入。
第三步,以下是中國裁判文書網爬蟲完整代碼:
import urllib.requestimport reimport http.cookiejarimport execjsimport uuid#隨機生成guidguid=uuid.uuid4()print("guid:"+str(guid))fh=open("./base64.js","r")js1=fh.read()fh.close()fh=open("./md5.js","r")js2=fh.read()fh.close()fh=open("./getkey.js","r")js3=fh.read()fh.close()#將完整js代碼都載入進來js_all=js1+js2+js3#在生成的CookieJar添加到opner中cjar=http.cookiejar.CookieJar()opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cjar))#Referer常用於反爬,指來源網址opener.addheaders=[("Referer","http://wenshu.court.gov.cn/list/list/?sorttype=1&conditions=searchWord+1+AJLX++%E6%A1%88%E4%BB%B6%E7%B1%BB%E5%9E%8B:%E5%88%91%E4%BA%8B%E6%A1%88%E4%BB%B6&conditions=searchWord+2018+++%E8%A3%81%E5%88%A4%E5%B9%B4%E4%BB%BD:2018&conditions=searchWord+%E4%B8%8A%E6%B5%B7%E5%B8%82+++%E6%B3%95%E9%99%A2%E5%9C%B0%E5%9F%9F:%E4%B8%8A%E6%B5%B7%E5%B8%82")]urllib.request.install_opener(opener)#用戶代理池import randomuapools=[ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)",]#訪問首頁urllib.request.urlopen("http://wenshu.court.gov.cn/list/list/?sorttype=1&conditions=searchWord+1+AJLX++%E6%A1%88%E4%BB%B6%E7%B1%BB%E5%9E%8B:%E5%88%91%E4%BA%8B%E6%A1%88%E4%BB%B6&conditions=searchWord+2018+++%E8%A3%81%E5%88%A4%E5%B9%B4%E4%BB%BD:2018&conditions=searchWord+%E4%B8%8A%E6%B5%B7%E5%B8%82+++%E6%B3%95%E9%99%A2%E5%9C%B0%E5%9F%9F:%E4%B8%8A%E6%B5%B7%E5%B8%82").read().decode("utf-8","ignore")#將Cookie中的vjkl5欄位提取出來pat="vjkl5=(.*?)s"vjkl5=re.compile(pat,re.S).findall(str(cjar))if(len(vjkl5)>0): vjkl5=vjkl5[0]else: vjkl5=0print("vjkl5:"+str(vjkl5))#將js代碼中的舊Cookie的值替換為新的vjkl5的值js_all=js_all.replace("ce7c8849dffea151c0179187f85efc9751115a7b",str(vjkl5))#使用python執行js代碼,請提前安裝好對應模塊(在命令行中執行pip install pyexejs)compile_js=execjs.compile(js_all)#獲得vl5x欄位值vl5x=compile_js.call("getKey")print("vl5x:"+str(vl5x))url="http://wenshu.court.gov.cn/List/ListContent"#for循環,切換第1頁到10頁for i in range(0,10):try: #從GetCode中獲取number欄位值 codeurl="http://wenshu.court.gov.cn/ValiCode/GetCode" #上面提到,GetCode中,只要guid一個欄位,將其獲取到 codedata=urllib.parse.urlencode({ "guid":guid, }).encode(utf-8) codereq = urllib.request.Request(codeurl,codedata) codereq.add_header(User-Agent,random.choice(uapools)) codedata=urllib.request.urlopen(codereq).read().decode("utf-8","ignore") #print(codedata) #構造請求的參數 postdata =urllib.parse.urlencode({ "Param":"案件類型:刑事案件,裁判年份:2018,法院地域:上海市", "Index":str(i+1), "Page":"20", "Order":"法院層級", "Direction":"asc", "number":str(codedata), "guid":guid, "vl5x":vl5x, }).encode(utf-8) #直接發送POST請求 req = urllib.request.Request(url,postdata) req.add_header(User-Agent,random.choice(uapools)) #獲得ListContent中的文書ID值 data=urllib.request.urlopen(req).read().decode("utf-8","ignore") pat=文書ID.*?".*?"(.*?)." allid=re.compile(pat).findall(data) print(allid) except Exception as err: print(err)
如此便可批量獲取中國裁判文書網中的案件信息。友情提示,如果頻繁爬取該網站,需擴充用戶代理池。
五、推薦內容
1)常見反爬策略與反爬攻克手段介紹
數據的隱藏可以算是一種反爬策略之一,抓包分析是該反爬策略常見的反爬攻克手段。
當然,反爬還有很多手段,比如通過IP限制、UA限制、驗證碼限制...等等.
2)如何深入學習Python網路爬蟲(深入學習路線介紹)
通過上述的介紹,相信對網路爬蟲已經有了基礎的了解,以及能夠寫出一些簡單的爬蟲項目。
以下項目可以提供練習:
淘寶商品圖片爬蟲項目
淘寶商品爬蟲項目
…
3)關於Python爬蟲,推薦書籍
《Python程序設計基礎實戰教程》 . 清華大學出版社.2018年
《精通Python網路爬蟲》.機械工業出版社.2017年4月
本文由雲棲志願小組董黎明整理,編輯百見
原文鏈接
更多技術乾貨敬請關注云棲社區知乎機構號:阿里云云棲社區 - 知乎
推薦閱讀:
※Go語言中使用正則提取匹配的字元串
※python爬蟲基礎之正則表達式的基本了解(一)
※正則表達式及其簡單應用-前端筆記
※好文配好圖:正則表達式RegExp