一名合格的數(shù)據(jù)分析師分享Python網(wǎng)絡(luò)爬蟲二三事
一、前言
作為一名合格的數(shù)據(jù)分析師,其完整的技術(shù)知識體系必須貫穿數(shù)據(jù)獲取、數(shù)據(jù)存儲、數(shù)據(jù)提取、數(shù)據(jù)分析、數(shù)據(jù)挖掘、數(shù)據(jù)可視化等各大部分。
在此作為初出茅廬的數(shù)據(jù)小白,我將會把自己學(xué)習(xí)數(shù)據(jù)科學(xué)過程中遇到的一些問題記錄下來,以便后續(xù)的查閱,同時(shí)也希望與各路同學(xué)一起交流、一起進(jìn)步。剛好前段時(shí)間學(xué)習(xí)了Python網(wǎng)絡(luò)爬蟲,在此將網(wǎng)絡(luò)爬蟲做一個(gè)總結(jié)。
二、何為網(wǎng)絡(luò)爬蟲?
1. 爬蟲場景
我們先自己想象一下平時(shí)到天貓商城購物(PC端)的步驟,可能就是:
打開瀏覽器==》搜索天貓商城==》點(diǎn)擊鏈接進(jìn)入天貓商城==》選擇所需商品類目(站內(nèi)搜索)==》瀏覽商品(價(jià)格、詳情參數(shù)、評論等)==》點(diǎn)擊鏈接==》進(jìn)入下一個(gè)商品頁面......
這樣子周而復(fù)始。當(dāng)然這其中的搜索也是爬蟲的應(yīng)用之一。簡單講,網(wǎng)絡(luò)爬蟲是類似又區(qū)別于上述場景的一種程序。
2. 爬蟲分類
(1)分類與關(guān)系
一般最常用的爬蟲類型主要有通用爬蟲和聚焦爬蟲,其中聚焦爬蟲又分為淺聚焦與深聚焦,三者關(guān)系如下圖:
(2)區(qū)別
通用爬蟲與聚焦爬蟲的區(qū)別就在有沒有對信息進(jìn)行過濾以盡量保證只抓取與主題相關(guān)的網(wǎng)頁信息。
(3)聚焦爬蟲過濾方法
- 淺聚焦爬蟲
選取符合目標(biāo)主題的種子URL,例如我們定義抓取的信息為招聘信息,我們便可將招聘網(wǎng)站的URL(拉勾網(wǎng)、大街網(wǎng)等)作為種子URL,這樣便保證了抓取內(nèi)容與我們定義的主題的一致性。
- 深聚焦爬蟲
一般有兩種,一是針對內(nèi)容二是針對URL。其中針對內(nèi)容的如頁面中絕大部分超鏈接都是帶有錨文本的,我們可以根據(jù)錨文本進(jìn)行篩選。
3. 爬蟲原理
總的來說,爬蟲就是從種子URL開始,通過 HTTP 請求獲取頁面內(nèi)容,并從頁面內(nèi)容中通過各種技術(shù)手段解析出更多的 URL,遞歸地請求獲取頁面的程序網(wǎng)絡(luò)爬蟲,總結(jié)其主要原理如下圖(其中紅色為聚焦爬蟲相對通用爬蟲所需額外進(jìn)行步驟):
4. 爬蟲應(yīng)用
網(wǎng)絡(luò)爬蟲可以做的事情很多,如以下列出:
- 搜索引擎
- 采集數(shù)據(jù)(金融、商品、競品等)
- 廣告過濾
- ……
其實(shí)就我們個(gè)人興趣,學(xué)完爬蟲我們可以看看當(dāng)當(dāng)網(wǎng)上哪種技術(shù)圖書賣得比較火(銷量、評論等信息)、看某個(gè)在線教育網(wǎng)站哪門網(wǎng)絡(luò)課程做得比較成功、看雙十一天貓的活動(dòng)情況等等,只要我們感興趣的數(shù)據(jù),一般的話都可以爬取得到,不過有些網(wǎng)站比較狡猾,設(shè)置各種各樣的反扒機(jī)制??偠灾?,網(wǎng)絡(luò)爬蟲可以幫助我們做很多有趣的事情。
三、網(wǎng)絡(luò)爬蟲基礎(chǔ)
個(gè)人建議本章除3.3以外,其他內(nèi)容可以大致先看一下,有些許印象即可,等到后面已經(jīng)完成一些簡單爬蟲后或者在寫爬蟲過程中遇到一些問題再回頭來鞏固一下,這樣子或許更有助于我們進(jìn)一步網(wǎng)絡(luò)理解爬蟲。
1. HTTP協(xié)議
HTTP 協(xié)議是爬蟲的基礎(chǔ),通過封裝 TCP/IP 協(xié)議鏈接,簡化了網(wǎng)絡(luò)請求的流程,使得用戶不需要關(guān)注三次握手,丟包超時(shí)等底層交互。
2. 前端技術(shù)
作為新手,個(gè)人覺得入門的話懂一點(diǎn)HTML與JavaScript就可以實(shí)現(xiàn)基本的爬蟲項(xiàng)目,HTML主要協(xié)助我們處理靜態(tài)頁面,而實(shí)際上很多數(shù)據(jù)并不是我們簡單的右擊查看網(wǎng)頁源碼便可以看到的,而是存在JSON(JavaScript Object Notation)文件中,這時(shí)我們便需要采取抓包分析,詳見《5.2 爬取基于Ajax技術(shù)網(wǎng)頁數(shù)據(jù)》。
3. 正則表達(dá)式與XPath
做爬蟲必不可少的步驟便是做解析。正則表達(dá)式是文本匹配提取的利器,并且被各種語言支持。XPath即為XML路徑語言,類似Windows的文件路徑,區(qū)別就在XPath是應(yīng)用在網(wǎng)頁頁面中來定位我們所需內(nèi)容的精確位置。
四、網(wǎng)絡(luò)爬蟲常見問題
1. 爬蟲利器——python
Python 是一種十分便利的腳本語言,廣泛被應(yīng)用在各種爬蟲框架。Python提供了如urllib、re、json、pyquery等模塊,同時(shí)前人又利用Python造了許許多多的輪,如Scrapy框架、PySpider爬蟲系統(tǒng)等,所以做爬蟲Python是一大利器。
說明:本章開發(fā)環(huán)境細(xì)節(jié)如下
- 系統(tǒng)環(huán)境:windows 8.1
- 開發(fā)語言:Python3.5
- 開發(fā)工具:Spyder、Pycharm
- 輔助工具:Chrome瀏覽器
2. 編碼格式
Python3中,只有Unicode編碼的為str,其他編碼格式如gbk,utf-8,gb2312等都為bytes,在編解碼過程中字節(jié)bytes通過解碼方法decode()解碼為字符串str,然后字符串str通過編碼方法encode()編碼為字節(jié)bytes,關(guān)系如下圖:
實(shí)戰(zhàn)——爬取當(dāng)當(dāng)網(wǎng)
爬取網(wǎng)頁
- In [5]:import urllib.request
- ...:data = urllib.request.urlopen("http://www.dangdang.com/").read()
- #爬取的data中的<title>標(biāo)簽中的內(nèi)容如下:
- <title>\xb5\xb1\xb5\xb1\xa1\xaa\xcd\xf8\xc9\xcf\xb9\xba\xce\xef\xd6\xd0\xd0\xc4\xa3\xba\xcd\xbc\xca\xe9\xa1\xa2\xc4\xb8\xd3\xa4\xa1\xa2\xc3\xc0\xd7\xb1\xa1\xa2\xbc\xd2\xbe\xd3\xa1\xa2\xca\xfd\xc2\xeb\xa1\xa2\xbc\xd2\xb5\xe7\xa1\xa2\xb7\xfe\xd7\xb0\xa1\xa2\xd0\xac\xb0\xfc\xb5\xc8\xa3\xac\xd5\xfd\xc6\xb7\xb5\xcd\xbc\xdb\xa3\xac\xbb\xf5\xb5\xbd\xb8\xb6\xbf\xee</title>
查看編碼格式
- In [5]:import chardet
- ...:chardet.detect(data)
- Out[5]: {'confidence': 0.99, 'encoding': 'GB2312'}
可知爬取到的網(wǎng)頁是GB2312編碼,這是漢字的國標(biāo)碼,專門用來表示漢字。
解碼
- In [5]:decodeData = data.decode("gbk")
- #此時(shí)bytes已經(jīng)解碼成str,<title>標(biāo)簽內(nèi)容解碼結(jié)果如下:
- <title>當(dāng)當(dāng)—網(wǎng)上購物中心:圖書、母嬰、美妝、家居、數(shù)碼、家電、服裝、鞋包等,正品低價(jià),貨到付款</title>
重編碼
- dataEncode = decodeData.encode("utf-8","ignore")
- #重編碼結(jié)果
- <title>\xe5\xbd\x93\xe5\xbd\x93\xe2\x80\x94\xe7\xbd\x91\xe4\xb8\x8a\xe8\xb4\xad\xe7\x89\xa9\xe4\xb8\xad\xe5\xbf\x83\xef\xbc\x9a\xe5\x9b\xbe\xe4\xb9\xa6\xe3\x80\x81\xe6\xaf\x8d\xe5\xa9\xb4\xe3\x80\x81\xe7\xbe\x8e\xe5\xa6\x86\xe3\x80\x81\xe5\xae\xb6\xe5\xb1\x85\xe3\x80\x81\xe6\x95\xb0\xe7\xa0\x81\xe3\x80\x81\xe5\xae\xb6\xe7\x94\xb5\xe3\x80\x81\xe6\x9c\x8d\xe8\xa3\x85\xe3\x80\x81\xe9\x9e\x8b\xe5\x8c\x85\xe7\xad\x89\xef\xbc\x8c\xe6\xad\xa3\xe5\x93\x81\xe4\xbd\x8e\xe4\xbb\xb7\xef\xbc\x8c\xe8\xb4\xa7\xe5\x88\xb0\xe4\xbb\x98\xe6\xac\xbe</title>
3. 超時(shí)設(shè)置
允許超時(shí)
data = urllib.request.urlopen(“http://www.dangdang.com/”,timeout=3).read()
線程推遲(單位為秒)
import timetime.sleep(3)
4. 異常處理
每個(gè)程序都不可避免地要進(jìn)行異常處理,爬蟲也不例外,假如不進(jìn)行異常處理,可能導(dǎo)致爬蟲程序直接崩掉。
1. 網(wǎng)絡(luò)爬蟲中處理異常的種類與關(guān)系
(1)URLError
通常,URLError在沒有網(wǎng)絡(luò)連接(沒有路由到特定服務(wù)器),或者服務(wù)器不存在的情況下產(chǎn)生。
(2)HTTPError
首先我們要明白服務(wù)器上每一個(gè)HTTP 應(yīng)答對象response都包含一個(gè)數(shù)字“狀態(tài)碼”,該狀態(tài)碼表示HTTP協(xié)議所返回的響應(yīng)的狀態(tài),這就是HTTPError。比如當(dāng)產(chǎn)生“404 Not Found”的時(shí)候,便表示“沒有找到對應(yīng)頁面”,可能是輸錯(cuò)了URL地址,也可能IP被該網(wǎng)站屏蔽了,這時(shí)便要使用代理IP進(jìn)行爬取數(shù)據(jù),關(guān)于代理IP的設(shè)定我們下面會講到。
(3)兩者關(guān)系
兩者是父類與子類的關(guān)系,即HTTPError是URLError的子類,HTTPError有異常狀態(tài)碼與異常原因,URLError沒有異常狀態(tài)碼。所以,我們在處理的時(shí)候,不能使用URLError直接代替HTTPError。同時(shí),Python中所有異常都是基類Exception的成員,所有異常都從此基類繼承,而且都在exceptions模塊中定義。如果要代替,必須要判斷是否有狀態(tài)碼屬性。
2. Python中有一套異常處理機(jī)制語法
(1)try-except語句
- try:
- blockexcept Exception as e:
- blockelse:
- block
- try 語句:捕獲異常
- except語句:處理不同的異常,Exception是異常的種類,在爬蟲中常見如上文所述。
- e:異常的信息,可供后面打印輸出
- else: 表示若沒有發(fā)生異常,當(dāng)try執(zhí)行完畢之后,就會執(zhí)行else
(2)try-except-finally語句
- try:
- block except Exception as e:
- blockfinally:
- block
假如try沒有捕獲到錯(cuò)誤信息,則直接跳過except語句轉(zhuǎn)而執(zhí)行finally語句,其實(shí)無論是否捕獲到異常都會執(zhí)行finally語句,因此一般我們都會將一些釋放資源的工作放到該步中,如關(guān)閉文件句柄或者關(guān)閉數(shù)據(jù)庫連接等。
4. 自動(dòng)模擬HTTP請求
一般客戶端需要通過HTTP請求才能與服務(wù)端進(jìn)行通信,常見的HTTP請求有POST與GET兩種。例如我們打開淘寶網(wǎng)頁后一旦HTML加載完成,瀏覽器將會發(fā)送GET請求去獲取圖片等,這樣子我們才能看到一個(gè)完整的動(dòng)態(tài)頁面,假如我們?yōu)g覽后需要下單那么還需要向服務(wù)器傳遞登錄信息。
(1)GET方式
向服務(wù)器發(fā)索取數(shù)據(jù)的一種請求,將請求數(shù)據(jù)融入到URL之中,數(shù)據(jù)在URL中可以看到。
(2)POST方式
向服務(wù)器提交數(shù)據(jù)的一種請求,將數(shù)據(jù)放置在HTML HEADER內(nèi)提交。從安全性講,POST方式相對于GET方式較為安全,畢竟GET方式是直接將請求數(shù)據(jù)以明文的形式展現(xiàn)在URL中。
5. cookies處理
cookies是某些網(wǎng)站為了辨別用戶身份、進(jìn)行session跟蹤而儲存在用戶本地終端上的數(shù)據(jù)(通常經(jīng)過加密)。
6. 瀏覽器偽裝
(1)原理
瀏覽器偽裝是防屏蔽的方法之一,簡言之,其原理就是在客戶端在向服務(wù)端發(fā)送的請求中添加報(bào)頭信息,告訴服務(wù)器“我是瀏覽器”
(2)如何查看客戶端信息?
通過Chrome瀏覽器按F12==》選擇Network==》刷新后點(diǎn)擊Name下任一個(gè)地址,便可以看到請求報(bào)文和相應(yīng)報(bào)文信息。以下是在百度上搜索簡書的請求報(bào)文信息,在爬蟲中我們只需添加報(bào)頭中的User-Agent便可實(shí)現(xiàn)瀏覽器偽裝。
7. 代理服務(wù)器
(1)原理
代理服務(wù)器原理如下圖,利用代理服務(wù)器可以很好處理IP限制問題。
個(gè)人認(rèn)為IP限制這一點(diǎn)對爬蟲的影響是很大的,畢竟我們一般不會花錢去購買正規(guī)的代理IP,我們一般都是利用互聯(lián)網(wǎng)上提供的一些免費(fèi)代理IP進(jìn)行爬取,而這些免費(fèi)IP的質(zhì)量殘次不齊,出錯(cuò)是在所難免的,所以在使用之前我們要對其進(jìn)行有效性測試。
(2)實(shí)戰(zhàn)——代理服務(wù)器爬取百度首頁
- import urllib.requestdef use_proxy(url,proxy_addr,iHeaders,timeoutSec):
- '''
- 功能:偽裝成瀏覽器并使用代理IP防屏蔽
- @url:目標(biāo)URL
- @proxy_addr:代理IP地址
- @iHeaders:瀏覽器頭信息
- @timeoutSec:超時(shí)設(shè)置(單位:秒)
- '''
- proxy = urllib.request.ProxyHandler({"http":proxy_addr})
- opener = urllib.request.build_opener(proxy,urllib.request.HTTPHandler)
- urllib.request.install_opener(opener)
- try:
- req = urllib.request.Request(url,headers = iHeaders) #偽裝為瀏覽器并封裝request
- data = urllib.request.urlopen(req).read().decode("utf-8","ignore")
- except Exception as er:
- print("爬取時(shí)發(fā)生錯(cuò)誤,具體如下:")
- print(er)
- return data
- url = "http://www.baidu.com"
- proxy_addr = "125.94.0.253:8080"
- iHeaders = {"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0"}
- timeoutSec = 10
- data = use_proxy(url,proxy_addr,iHeaders,timeoutSec)
- print(len(data))
8. 抓包分析
(1)Ajax(異步加載)的技術(shù)
網(wǎng)站中用戶需求的數(shù)據(jù)如聯(lián)系人列表,可以從獨(dú)立于實(shí)際網(wǎng)頁的服務(wù)端取得并且可以被動(dòng)態(tài)地寫入網(wǎng)頁中。簡單講就是打開網(wǎng)頁,先展現(xiàn)部分內(nèi)容,再慢慢加載剩下的內(nèi)容。顯然,這樣的網(wǎng)頁因?yàn)椴挥靡淮渭虞d全部內(nèi)容其加載速度特別快,但對于我們爬蟲的話就比較麻煩了,我們總爬不到我們想要的內(nèi)容,這時(shí)候就需要進(jìn)行抓包分析。
(2)抓包工具
推薦Fiddler與Chrome瀏覽器
(3)實(shí)戰(zhàn)
請轉(zhuǎn)《5.2 爬取基于Ajax技術(shù)網(wǎng)頁數(shù)據(jù)》。
9. 多線程爬蟲
一般我們程序是單線程運(yùn)行,但多線程可以充分利用資源,優(yōu)化爬蟲效率。實(shí)際上Python 中的多線程并行化并不是真正的并行化,但是多線程在一定程度上還是能提高爬蟲的執(zhí)行效率,下面我們就針對單線程和多線程進(jìn)行時(shí)間上的比較。
(1)實(shí)戰(zhàn)——爬取豆瓣科幻電影網(wǎng)頁
- '''多線程'''import urllibfrom multiprocessing.dummy import Poolimport timedef getResponse(url):
- '''獲取響應(yīng)信息'''
- try:
- req = urllib.request.Request(url)
- res = urllib.request.urlopen(req)
- except Exception as er:
- print("爬取時(shí)發(fā)生錯(cuò)誤,具體如下:")
- print(er)
- return resdef getURLs():
- '''獲取所需爬取的所有URL'''
- urls = []
- for i in range(0, 101,20):#每翻一頁其start值增加20
- keyword = "科幻"
- keyword = urllib.request.quote(keyword)
- newpage = "https://movie.douban.com/tag/"+keyword+"?start="+str(i)+"&type=T"
- urls.append(newpage)
- return urls def singleTime(urls):
- '''單進(jìn)程計(jì)時(shí)'''
- timetime1 = time.time()
- for i in urls:
- print(i)
- getResponse(i)
- timetime2 = time.time()
- return str(time2 - time1) def multiTime(urls):
- '''多進(jìn)程計(jì)時(shí)'''
- pool = Pool(processes=4) #開啟四個(gè)進(jìn)程
- timetime3 = time.time()
- pool.map(getResponse,urls)
- pool.close()
- pool.join() #等待進(jìn)程池中的worker進(jìn)程執(zhí)行完畢
- timetime4 = time.time()
- return str(time4 - time3) if __name__ == '__main__':
- urls = getURLs()
- singleTimesingleTimes = singleTime(urls) #單線程計(jì)時(shí)
- multiTimemultiTimes = multiTime(urls) #多線程計(jì)時(shí)
- print('單線程耗時(shí) : ' + singleTimes + ' s')
- print('多線程耗時(shí) : ' + multiTimes + ' s')
(2)結(jié)果:
單線程耗時(shí) : 3.850554943084717 s
多線程耗時(shí) : 1.3288819789886475 s
10. 數(shù)據(jù)存儲
- 本地文件(excel、txt)
- 數(shù)據(jù)庫(如MySQL)
備注:具體實(shí)戰(zhàn)請看5.1
11. 驗(yàn)證碼處理
在登錄過程中我們常遇到驗(yàn)證碼問題,此時(shí)我們有必要對其進(jìn)行處理。
(1)簡單驗(yàn)證碼識別
利用pytesser識別簡單圖形驗(yàn)證碼,
(2)復(fù)雜驗(yàn)證碼識別
這相對有難度,可以調(diào)用第三方接口(如打碼兔)、利用數(shù)據(jù)挖掘算法如SVM
接下篇文章《一名合格的數(shù)據(jù)分析師分享Python網(wǎng)絡(luò)爬蟲二三事(綜合實(shí)戰(zhàn)案例)》
【本文是51CTO專欄機(jī)構(gòu)“豈安科技”的原創(chuàng)文章,轉(zhuǎn)載請通過微信公眾號(bigsec)聯(lián)系原作者】