一名合格的數據分析師分享Python網絡爬蟲二三事
一、前言
作為一名合格的數據分析師,其完整的技術知識體系必須貫穿數據獲取、數據存儲、數據提取、數據分析、數據挖掘、數據可視化等各大部分。
在此作為初出茅廬的數據小白,我將會把自己學習數據科學過程中遇到的一些問題記錄下來,以便后續的查閱,同時也希望與各路同學一起交流、一起進步。剛好前段時間學習了Python網絡爬蟲,在此將網絡爬蟲做一個總結。
二、何為網絡爬蟲?
1. 爬蟲場景
我們先自己想象一下平時到天貓商城購物(PC端)的步驟,可能就是:
打開瀏覽器==》搜索天貓商城==》點擊鏈接進入天貓商城==》選擇所需商品類目(站內搜索)==》瀏覽商品(價格、詳情參數、評論等)==》點擊鏈接==》進入下一個商品頁面......
這樣子周而復始。當然這其中的搜索也是爬蟲的應用之一。簡單講,網絡爬蟲是類似又區別于上述場景的一種程序。
2. 爬蟲分類
(1)分類與關系
一般最常用的爬蟲類型主要有通用爬蟲和聚焦爬蟲,其中聚焦爬蟲又分為淺聚焦與深聚焦,三者關系如下圖:
(2)區別
通用爬蟲與聚焦爬蟲的區別就在有沒有對信息進行過濾以盡量保證只抓取與主題相關的網頁信息。
(3)聚焦爬蟲過濾方法
- 淺聚焦爬蟲
選取符合目標主題的種子URL,例如我們定義抓取的信息為招聘信息,我們便可將招聘網站的URL(拉勾網、大街網等)作為種子URL,這樣便保證了抓取內容與我們定義的主題的一致性。
- 深聚焦爬蟲
一般有兩種,一是針對內容二是針對URL。其中針對內容的如頁面中絕大部分超鏈接都是帶有錨文本的,我們可以根據錨文本進行篩選。
3. 爬蟲原理
總的來說,爬蟲就是從種子URL開始,通過 HTTP 請求獲取頁面內容,并從頁面內容中通過各種技術手段解析出更多的 URL,遞歸地請求獲取頁面的程序網絡爬蟲,總結其主要原理如下圖(其中紅色為聚焦爬蟲相對通用爬蟲所需額外進行步驟):
4. 爬蟲應用
網絡爬蟲可以做的事情很多,如以下列出:
- 搜索引擎
- 采集數據(金融、商品、競品等)
- 廣告過濾
- ……
其實就我們個人興趣,學完爬蟲我們可以看看當當網上哪種技術圖書賣得比較火(銷量、評論等信息)、看某個在線教育網站哪門網絡課程做得比較成功、看雙十一天貓的活動情況等等,只要我們感興趣的數據,一般的話都可以爬取得到,不過有些網站比較狡猾,設置各種各樣的反扒機制??偠灾?,網絡爬蟲可以幫助我們做很多有趣的事情。
三、網絡爬蟲基礎
個人建議本章除3.3以外,其他內容可以大致先看一下,有些許印象即可,等到后面已經完成一些簡單爬蟲后或者在寫爬蟲過程中遇到一些問題再回頭來鞏固一下,這樣子或許更有助于我們進一步網絡理解爬蟲。
1. HTTP協議
HTTP 協議是爬蟲的基礎,通過封裝 TCP/IP 協議鏈接,簡化了網絡請求的流程,使得用戶不需要關注三次握手,丟包超時等底層交互。
2. 前端技術
作為新手,個人覺得入門的話懂一點HTML與JavaScript就可以實現基本的爬蟲項目,HTML主要協助我們處理靜態頁面,而實際上很多數據并不是我們簡單的右擊查看網頁源碼便可以看到的,而是存在JSON(JavaScript Object Notation)文件中,這時我們便需要采取抓包分析,詳見《5.2 爬取基于Ajax技術網頁數據》。
3. 正則表達式與XPath
做爬蟲必不可少的步驟便是做解析。正則表達式是文本匹配提取的利器,并且被各種語言支持。XPath即為XML路徑語言,類似Windows的文件路徑,區別就在XPath是應用在網頁頁面中來定位我們所需內容的精確位置。
四、網絡爬蟲常見問題
1. 爬蟲利器——python
Python 是一種十分便利的腳本語言,廣泛被應用在各種爬蟲框架。Python提供了如urllib、re、json、pyquery等模塊,同時前人又利用Python造了許許多多的輪,如Scrapy框架、PySpider爬蟲系統等,所以做爬蟲Python是一大利器。
說明:本章開發環境細節如下
- 系統環境:windows 8.1
- 開發語言:Python3.5
- 開發工具:Spyder、Pycharm
- 輔助工具:Chrome瀏覽器
2. 編碼格式
Python3中,只有Unicode編碼的為str,其他編碼格式如gbk,utf-8,gb2312等都為bytes,在編解碼過程中字節bytes通過解碼方法decode()解碼為字符串str,然后字符串str通過編碼方法encode()編碼為字節bytes,關系如下圖:
實戰——爬取當當網
爬取網頁
- In [5]:import urllib.request
- ...:data = urllib.request.urlopen("http://www.dangdang.com/").read()
- #爬取的data中的<title>標簽中的內容如下:
- <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'}
可知爬取到的網頁是GB2312編碼,這是漢字的國標碼,專門用來表示漢字。
解碼
- In [5]:decodeData = data.decode("gbk")
- #此時bytes已經解碼成str,<title>標簽內容解碼結果如下:
- <title>當當—網上購物中心:圖書、母嬰、美妝、家居、數碼、家電、服裝、鞋包等,正品低價,貨到付款</title>
重編碼
- dataEncode = decodeData.encode("utf-8","ignore")
- #重編碼結果
- <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. 超時設置
允許超時
data = urllib.request.urlopen(“http://www.dangdang.com/”,timeout=3).read()
線程推遲(單位為秒)
import timetime.sleep(3)
4. 異常處理
每個程序都不可避免地要進行異常處理,爬蟲也不例外,假如不進行異常處理,可能導致爬蟲程序直接崩掉。
1. 網絡爬蟲中處理異常的種類與關系
(1)URLError
通常,URLError在沒有網絡連接(沒有路由到特定服務器),或者服務器不存在的情況下產生。
(2)HTTPError
首先我們要明白服務器上每一個HTTP 應答對象response都包含一個數字“狀態碼”,該狀態碼表示HTTP協議所返回的響應的狀態,這就是HTTPError。比如當產生“404 Not Found”的時候,便表示“沒有找到對應頁面”,可能是輸錯了URL地址,也可能IP被該網站屏蔽了,這時便要使用代理IP進行爬取數據,關于代理IP的設定我們下面會講到。
(3)兩者關系
兩者是父類與子類的關系,即HTTPError是URLError的子類,HTTPError有異常狀態碼與異常原因,URLError沒有異常狀態碼。所以,我們在處理的時候,不能使用URLError直接代替HTTPError。同時,Python中所有異常都是基類Exception的成員,所有異常都從此基類繼承,而且都在exceptions模塊中定義。如果要代替,必須要判斷是否有狀態碼屬性。
2. Python中有一套異常處理機制語法
(1)try-except語句
- try:
- blockexcept Exception as e:
- blockelse:
- block
- try 語句:捕獲異常
- except語句:處理不同的異常,Exception是異常的種類,在爬蟲中常見如上文所述。
- e:異常的信息,可供后面打印輸出
- else: 表示若沒有發生異常,當try執行完畢之后,就會執行else
(2)try-except-finally語句
- try:
- block except Exception as e:
- blockfinally:
- block
假如try沒有捕獲到錯誤信息,則直接跳過except語句轉而執行finally語句,其實無論是否捕獲到異常都會執行finally語句,因此一般我們都會將一些釋放資源的工作放到該步中,如關閉文件句柄或者關閉數據庫連接等。
4. 自動模擬HTTP請求
一般客戶端需要通過HTTP請求才能與服務端進行通信,常見的HTTP請求有POST與GET兩種。例如我們打開淘寶網頁后一旦HTML加載完成,瀏覽器將會發送GET請求去獲取圖片等,這樣子我們才能看到一個完整的動態頁面,假如我們瀏覽后需要下單那么還需要向服務器傳遞登錄信息。
(1)GET方式
向服務器發索取數據的一種請求,將請求數據融入到URL之中,數據在URL中可以看到。
(2)POST方式
向服務器提交數據的一種請求,將數據放置在HTML HEADER內提交。從安全性講,POST方式相對于GET方式較為安全,畢竟GET方式是直接將請求數據以明文的形式展現在URL中。
5. cookies處理
cookies是某些網站為了辨別用戶身份、進行session跟蹤而儲存在用戶本地終端上的數據(通常經過加密)。
6. 瀏覽器偽裝
(1)原理
瀏覽器偽裝是防屏蔽的方法之一,簡言之,其原理就是在客戶端在向服務端發送的請求中添加報頭信息,告訴服務器“我是瀏覽器”
(2)如何查看客戶端信息?
通過Chrome瀏覽器按F12==》選擇Network==》刷新后點擊Name下任一個地址,便可以看到請求報文和相應報文信息。以下是在百度上搜索簡書的請求報文信息,在爬蟲中我們只需添加報頭中的User-Agent便可實現瀏覽器偽裝。
7. 代理服務器
(1)原理
代理服務器原理如下圖,利用代理服務器可以很好處理IP限制問題。
個人認為IP限制這一點對爬蟲的影響是很大的,畢竟我們一般不會花錢去購買正規的代理IP,我們一般都是利用互聯網上提供的一些免費代理IP進行爬取,而這些免費IP的質量殘次不齊,出錯是在所難免的,所以在使用之前我們要對其進行有效性測試。
(2)實戰——代理服務器爬取百度首頁
- import urllib.requestdef use_proxy(url,proxy_addr,iHeaders,timeoutSec):
- '''
- 功能:偽裝成瀏覽器并使用代理IP防屏蔽
- @url:目標URL
- @proxy_addr:代理IP地址
- @iHeaders:瀏覽器頭信息
- @timeoutSec:超時設置(單位:秒)
- '''
- 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("爬取時發生錯誤,具體如下:")
- 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(異步加載)的技術
網站中用戶需求的數據如聯系人列表,可以從獨立于實際網頁的服務端取得并且可以被動態地寫入網頁中。簡單講就是打開網頁,先展現部分內容,再慢慢加載剩下的內容。顯然,這樣的網頁因為不用一次加載全部內容其加載速度特別快,但對于我們爬蟲的話就比較麻煩了,我們總爬不到我們想要的內容,這時候就需要進行抓包分析。
(2)抓包工具
推薦Fiddler與Chrome瀏覽器
(3)實戰
請轉《5.2 爬取基于Ajax技術網頁數據》。
9. 多線程爬蟲
一般我們程序是單線程運行,但多線程可以充分利用資源,優化爬蟲效率。實際上Python 中的多線程并行化并不是真正的并行化,但是多線程在一定程度上還是能提高爬蟲的執行效率,下面我們就針對單線程和多線程進行時間上的比較。
(1)實戰——爬取豆瓣科幻電影網頁
- '''多線程'''import urllibfrom multiprocessing.dummy import Poolimport timedef getResponse(url):
- '''獲取響應信息'''
- try:
- req = urllib.request.Request(url)
- res = urllib.request.urlopen(req)
- except Exception as er:
- print("爬取時發生錯誤,具體如下:")
- 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):
- '''單進程計時'''
- timetime1 = time.time()
- for i in urls:
- print(i)
- getResponse(i)
- timetime2 = time.time()
- return str(time2 - time1) def multiTime(urls):
- '''多進程計時'''
- pool = Pool(processes=4) #開啟四個進程
- timetime3 = time.time()
- pool.map(getResponse,urls)
- pool.close()
- pool.join() #等待進程池中的worker進程執行完畢
- timetime4 = time.time()
- return str(time4 - time3) if __name__ == '__main__':
- urls = getURLs()
- singleTimesingleTimes = singleTime(urls) #單線程計時
- multiTimemultiTimes = multiTime(urls) #多線程計時
- print('單線程耗時 : ' + singleTimes + ' s')
- print('多線程耗時 : ' + multiTimes + ' s')
(2)結果:
單線程耗時 : 3.850554943084717 s
多線程耗時 : 1.3288819789886475 s
10. 數據存儲
- 本地文件(excel、txt)
- 數據庫(如MySQL)
備注:具體實戰請看5.1
11. 驗證碼處理
在登錄過程中我們常遇到驗證碼問題,此時我們有必要對其進行處理。
(1)簡單驗證碼識別
利用pytesser識別簡單圖形驗證碼,
(2)復雜驗證碼識別
這相對有難度,可以調用第三方接口(如打碼兔)、利用數據挖掘算法如SVM
接下篇文章《一名合格的數據分析師分享Python網絡爬蟲二三事(綜合實戰案例)》
【本文是51CTO專欄機構“豈安科技”的原創文章,轉載請通過微信公眾號(bigsec)聯系原作者】