python爬蟲入門


基礎知識

HTTP協議

我們瀏覽網頁的瀏覽器和手機應用客戶端與服務器通信幾乎都是基於HTTP協議,而爬蟲可以看作是一個另類的客戶端,它把自己偽裝成瀏覽器或者手機應用客戶端,按照自己的邏輯貪婪的向服務器索取數據,如何向服務器索取數據,所以了解HTTP協議就顯得很有必要了。

HTTP協議中文名稱是超文本傳輸協議,是一個基於請求與響應模式的、無狀態的、應用層的協議,常基於TCP的連接方式。請求和響應模式很好理解,客戶端發送請求,服務器響應客戶端的請求,就像學校食堂打菜一樣,你和打菜阿姨說要哪份菜,她才會給你盛哪份菜。

HTTP請求響應

無狀態是指協議對於事務處理沒有記憶能力。缺少狀態意味着如果后續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快。形象點說,可以把服務器看成是沒有記憶的大學食堂打飯打菜,在每次請求中,阿姨並不知道你之前有沒有打過菜,也不知道你是不是合法的學生,所以你只能一邊舉着學生證一邊和阿姨說我要這個菜,阿姨看到你的學生證后才會給你打菜,而這個學生證就是你每次需要重傳的數據信息。

當我們在瀏覽器地址欄中輸入http://www.bilibili.com 並敲入回車后,瀏覽器會構造HTTP請求發送到服務器,在收到服務器的HTTP響應后,瀏覽器會解析頁面,繼續向服務器請求圖片、視頻、js腳本等數據,直到頁面加載完成,最終展示給我們的就是B站主頁了。這是我用Fiddler抓的包,展示的是HTTP最原生的面貌,接下來我就根據這張圖具體的講解HTTP協議,以及寫爬蟲需要關注的一些點。

Fiddler抓包

HTTP請求由三部分組成,分別是: 請求行、消息報頭、請求正文。 在接收和解釋請求消息后,服務器返回一個HTTP響應消息,HTTP響應也是由三個部分組成,分別是:狀態行、消息報頭、響應正文。

HTTP方法

HTTP請求的請求行以一個方法符號開頭,以空格分開,后面跟着請求的URI和協議的版本。請求方法都是大寫,有很多種,常見的有GET POST DELETE PUT,各種方法之間的區別不大。

HTTP方法

這里羅列了一些常用的方法,一般來講,GET表示向服務器請求URI對應的資源,POST表示向服務器提交數據,DELETE表示刪除數據,PUT表示修改數據。但這都是一種約定,沒有強制的要求,如果你碰見用DELETE方法提交數據也沒必要大驚小怪。在實際寫爬蟲的過程中,我們只需要按照抓包請求構造數據即可,沒有必要在意用了什么方法。

報頭字段

重點講解幾個寫爬蟲需要關注的字段

  • User-Agent 出現在請求報頭中,表示客戶端的操作系統、瀏覽器型號版本等信息。服務器可以根據此報頭向客戶端返回不同的頁面以適應客戶端。有些網站(知乎)會校驗此報頭,不填寫或者不主流的報頭都不能拿到正常的頁面。因此自己在寫爬蟲的時候最好將從瀏覽器中拷貝到代碼中。
  • Cookie 出現在請求抱頭中,前面我們說過HTTP是基於請求與響應模式並且無狀態的協議,之前舉了打菜阿姨的例子,Cookie就相當於每次請求中的學生證,它可以記錄用戶的身份信息。當我們自己寫爬蟲的時候,如果需要登陸,並且登陸又有驗證碼或者短信驗證時,最簡單的方法就是從瀏覽器中把cookie拷貝到爬蟲中,就可以騙過服務器了。
  • Set-Cookie 出現在響應抱頭中,讓客戶端更新頁面關聯的Cookie,還是拿食堂阿姨的例子,如果你的響應報頭有這個字段,意思就是阿姨重新給你了一個學生證,下次打飯你得用最新的學生證,原來的學生證不好使。如果你在模擬瀏覽器或者客戶端登陸,需要將此報頭更新已有的Cookie,不過Scrapy和requests都可以自動更新,因此不需要你再手動設置。
  • Content-Type 標明請求正文或者響應正文的格式,客戶端或者服務器會根據此字段選擇合適的方式解析正文內容,以下是一些常見的值

ContentType

  • Content-Length 標明請求正文或者響應正文的長度,在使用requests構造請求的時候,我們不需要顯式的加上此字段,requests會根據請求正文自動計算添加。
  • Content-Encoding 在某些情況下,正文會講過壓縮后傳輸,此字段會指明壓縮的類型(gzip和壓縮參數)
  • Transfer-Encoding 如果正文內容過長,HTTP協議允許將此字段設置為chunked,然后分塊傳輸響應正文
  • Connection 在HTTP1.1之前的版本,不支持持久連接,所謂的持久鏈接意思就是:HTTP協議一般通過TCP協議實現,客戶端和服務器經過TCP三次握手建立連接,在請求和響應結束之后,此連接繼續保持,當之后還有請求的時候,就不需要重新通過三次握手再建立連接,這樣可以極大的降低客戶端和服務器的IO負載。

在自己寫爬蟲的時候,我們可以根據瀏覽器的抓包數據有選擇的添加一些請求報頭,其實大部分情況下都可以直接使用瀏覽器中的請求頭,為了避免不必要的麻煩,盡可能像的模仿瀏覽器總是沒有錯的。

響應碼

響應消息的第一行的狀態行包括HTTP的協議版本、狀態碼、狀態碼含義。按照約定

  • 2xx表示請求成功
  • 3xx表示重定向
  • 4xx表示客戶端錯誤(403 Forbiden 404 Not Found)
  • 5xx表示服務器錯誤(502 網關錯誤)

更多HTTP參考:

爬蟲開發

一般來說開發爬蟲的過程是這樣的

  1. 抓包分析獲取數據的URL
  2. 通過python從上一步的URL獲取數據
  3. 從上一步獲取的HTML頁面或者JSON數據中解析出感興趣的數據
  4. 存儲數據

下面就講解這四個關鍵點

抓包發包工具

寫爬蟲的第一步就是分析想要的數據瀏覽器是通過什么URL拿到的,抓包也就在所難免。最好用的抓包工具當然是谷歌瀏覽器了,右鍵檢查,選中網絡,重新刷新頁面就可以看到加載此網頁所有的HTTP請求了,如果此鏈接有跳轉地址,跳轉之前的HTTP請求會被清掉,所以記得選上preserve log,尤其是登陸的時候,一般都會有跳轉。

谷歌瀏覽器抓包

再介紹另外兩個HTTP抓包工具——Fiddler和Charles,分別在windows和macos使用。它們可以為我們展示更多HTTP的細節,將請求和響應都調至Raw模式下,我們就可以一睹HTTP請求和響應的真實面貌。

通過抓包分析出具體的URL后,想進一步確認自己構造的參數和報頭能否正確獲取到數據,應該怎么做呢?不怕,postman可以幫你,你可以很輕松的選擇方法,定義header,添加各種類型的body。

Postman發包

python請求數據

講完了基本的HTTP協議知識后,大家可能會疑問那我該如何模仿瀏覽器或者手機客戶端去向服務器發送HTTP請求呢?python的原生庫urllib、第三方庫requests、pycurl等都支持HTTP協議,既然有這么多工具可以用,大家可能就又有疑問該選擇哪個工具了。在此我特地安利大家用一下requests,它讓爬蟲變得如此簡單,讓你再也不用為字符編碼、重定向、cookie、響應解壓縮煩惱了。如果你堅持用原生的庫,那么有以下問題需要你一一解決,這些都是當年自己趟過的坑,絕非危言聳聽。

  1. 需要自己判斷服務器返回數據的編碼格式,如果這個地方你不能正確判斷,那恭喜你之后的每一步,你都必須面對亂碼的問題
  2. 重定向,urllib不能自動判斷重定向,需要自己解析重定向的鏈接並重新請求
  3. 如果模擬登陸,你必須要手動保證Cookie正確更新和發送
  4. 很多情況下響應正文是壓縮過的,需要做解壓處理
  5. 對於比較長的響應正文,服務器會將正文分段傳輸,所以還需要你做拼接操作
  6. 原生的urllib對HTTPS和持久連接都支持不好

當你花了一整天,寫了好幾百行的代碼終於解決上面的問題后,而你旁邊的同事可能早已經把數據下載完並愉快的約妹子去了。所以用requests吧,兄弟們用了都說好。下面我用兩個例子講解一下如何用requests獲取想要的數據,並教你如何解決這些問題:

  • 如何發送不同方法的請求
  • 如何保存cookie
  • 如何添加代理
  • 如何處理編碼問題

B站

假如我想下載B站里面某位小姐姐所有上傳的視頻,應該怎么辦呢?首先你需要找到這位小姐姐的視頻主頁

小姐姐視頻主頁

但是通過谷歌瀏覽器右鍵查看頁面源碼,沒有從html中找到這些視頻的播放信息,唯一的可能就是視頻數據是通過js腳本調用服務器獲取,然后生成的這張頁面。爬蟲小白可能會疑問,難道我需要像瀏覽器一樣分析js腳本,然后模擬js執行嗎?其實不用這么復雜,只需要簡單的分析抓包結構,就可以找到請求URL了。

小姐姐主頁抓包

獲取視頻的URL: http://space.bilibili.com/ajax/member/getSubmitVideos?mid=79415852&pagesize=30&tid=0&page=1&keyword=&order=senddate

那么問題又來了,這個URL的其他參數是干啥的呢?憑經驗,mid肯定是這位小姐姐的用戶id,page和pagesize是負責分頁用的,keyword和是用來搜索的關鍵字,order是排序選項,剩下的tid是干啥的呢?其實寫爬蟲很多時候都會遇到這種問題,不知道某個參數的含義,也不確定正確的取值范圍,需要一些嘗試和運氣,這里我們不管它就好。而返回的字段中有一個aid,那肯定是視頻的id,有這個就可以自己拼接出播放鏈接了。

小姐姐視頻python請求

是不是很簡單,通過response.ok查看請求是否正確返回,因為此接口的數據為json格式,直接通過response.json()就可以直接拿到格式化的數據。

知乎

雖然現在知乎對未登錄用戶展示的內容越來越多,但是仍會有一些限制,用爬蟲模擬登陸可以之后再去爬取數據,可以避免很多不必要的麻煩,現在就講一講如何用requests模擬用戶登陸。

還是和之前一樣,在登陸頁面打開谷歌瀏覽器的抓包窗口,輸入用戶名和密碼點擊確定,然后在茫茫請求中找到發送登陸信息的那個HTTP請求即可,功夫不負有心人,我們終於找到了登陸的請求。

知乎登陸抓包

等等,請求里面還有一個_xsrf,這是一個什么鬼參數,其實呢這是一個防止跨站請求偽造而生成的一個隨機數,可以通過解析https://www.zhihu.com/#signin 頁面獲取,這一部分我在下面會講解如何HTML獲取數據,現在假設我們已經拿到這個數據了,如何將用戶名和密碼登陸呢?

知乎python登陸

如果我們想要自動保存Cookie信息,只需要生成一個Session對象,之后所有的請求通過此對象完成,requests會像瀏覽器一樣自動更新cookie信息,並在每次請求的時候加上cookie,因此在成功的發送post登陸請求之后,就可以用session在保持登陸狀態請求數據了。需要注意的是在請求的時候我特意去掉了Cookie和Content-Length報頭,因為requests會自動加上,所以不需要我們特意關注。

更多關於requests的使用可以查看官方文檔:

英文: http://docs.python-requests.org/en/master/
中文: http://cn.python-requests.org/zh_CN/latest/user/quickstart.html

python解析數據

因為個人在解析數據的時候遇到過很多編碼的坑,所以在繼續講解之前告訴大家一些如何避免編碼問題的方法。python2中有兩種字符串:unicode和str,它們分別對應python3中的str和bytes。如何定義這兩種類型的變量在下圖中給大家列出來了。

Unicode和Str

以python3為例講解這兩種類型的區別。python3中的str每一個字符可以存儲一個英文字母、一個漢字甚至一個emoji表情,它可以通過特定的編碼方式,例如utf-8或者gbk生成bytes,在不同的編碼格式下,可能需要2-3個字符常能表示一個漢字。bytes可以指定解碼格式解碼生成str,如果指定的解碼格式不匹配,就會導致亂碼問題。為了避免亂碼問題,最好的方式就是使用str,等到需要寫入文件或者數據庫的時候,再指定寫入的編碼格式,用好這個准則,我們可以避免百分之九十的編碼問題。

HTTP響應的數據格式有很多,例如文本、json、html,對應的解析方式也很多。通用一點,用python內置庫正則匹配找到想要的數據,但是這種方法相對來說比較麻煩,而且不好維護,比較適合文本類型的數據,但HTTP響應正文基本都是json和HTML,這種方式適用面比較窄。

當請求的數據是json格式時,我們可以很方便的用requests反序列化返回內容,取出感興趣的數據。但是當HTTP返回的數據是html的時候,我們該如何操作,就像剛才知乎登陸的例子中,如何快速從html中解析想要的數據呢?

專門用來解析html的第三方庫有很多,例如beautifulsoup、pyquery。個人推薦使用pyquery,因為它可以使用jquery的方式選取元素,而且支持xpath,以后上手scrapy會很容易。繼續上面登陸知乎的例子,登陸時需要的_xsrf實際上在 https://www.zhihu.com/#signin 頁面里面,只要先請求到這個頁面,然后解析出_xsrf,配合之前的登陸請求,我們就可以完整的實現用python模擬瀏覽器登陸知乎了。

知乎_xsrf

python解析_xsrf

使用起來是不是相當的簡單,我們只要通過谷歌瀏覽器找到對應DOM元素,根據屬性名就可以非常快速的找到想要的數據。需要注意的是response.content和response.text,這都是返回的body正文,但是前者是bytes,后者是str,requests已經幫助我們把響應正文按照正確的編碼格式進行了解碼,根據我們之前的闡述的原則,盡量使用str,所以26這個地方我用的是response.text。

更多關於pyquery的使用可以參考官方文檔: https://pythonhosted.org/pyquery/

存儲數據

根據數據量的不同以及數據類型的不同,數據的存儲選擇也很多。如果爬取的是圖片、音頻、視頻等多媒體文件,直接按照文件形式存儲就好了。如果是一些文本,數字等數據,一般有這么幾種選擇:

  • 輸出到屏幕
  • 寫入文件(txt csv)
  • 寫入數據庫 (mysql sqlite mongo)

如果數據量非常小,可以選擇直接輸出到屏幕(這種情況貌似也不需要爬蟲),因為終端存儲的數據量很少,而且因為沒有持久化,關閉窗口就意味着數據丟失,不建議使用。

在數據量小且不願意折騰數據庫的情況下,可以把爬取的數據寫入文件,但是這種情況不能隨取隨用,也不方便做數據分析,需要手動處理。

當數據量較多,而且需要快捷的分析數據,推薦使用數據庫存儲數據,大型的數據庫mysql, mongo功能齊全,可以分方便的進行數據分析,而且也很容易實現分布式擴展,當你需要多進程甚至多機器運行爬蟲的時候,這些數據庫可能是最好的選擇。sqlite相對來說功能要少很多,python原生支持,依賴少,數據量不算太大的情況下可以考慮使用。

因為這部分內容太多太深,感興趣的童鞋如果想深入了解一些,這里列出一些文檔供大家參考:

python操作sqlite: http://www.runoob.com/sqlite/sqlite-python.html
python操作mysql: http://www.runoob.com/python/python-mysql.html
python操作mongo: https://www.oschina.net/question/54100_27233

爬蟲示例

下面給出一個簡單的例子,為大家展示如何使用上述python庫實現一個完整的爬蟲。一些熱門的知乎話題最多有1000條精華回答,這個例子就是爬取這些精品答案。圖示頁面就是回答列表,每頁有二十個答案,最多有五十頁。但是此頁面沒有完整的回答信息,需要通過顯示全部對應的鏈接進入回答詳情頁才能獲取完整的答案,所以我們的爬蟲策略就是通過回答列表找到所有精華回答鏈接,再通過回答鏈接獲取內容。而且這些頁面不需要登陸也能訪問,因此可以省去模擬登陸。

知乎話題精華列表

開發環境

python是跨平台語言,但不同平台不同版本的python略微有一些差異,考慮到大家使用windows平台的較多,我在windows和ubuntu的python3.5驗證過此代碼,其他平台和其他python版本下可能需要做一些修改。集成開發環境推薦使用Pycharm,這是一個跨平台良心IDE,各大操作系統下都有免費的社區版本可以下載。

運行代碼

代碼鏈接

https://github.com/LiuRoy/sakura/blob/master/spider/crawl.py
https://github.com/LiuRoy/sakura/blob/master/spider/tables.sql

安裝依賴庫:

pip install requests pyquery SQLAlchemy

運行代碼:

python scrawl.py

代碼解釋

通過谷歌瀏覽器抓包分析,可以通過 https://www.zhihu.com/topic/19553155/top-answers?page=2 頁面獲取每個話題不同分頁下的回答鏈接 https://www.zhihu.com/question/27189372/answer/38712987 ,在此頁面中就可以獲取問題、回答、點贊數、標簽等信息。

因為數據量不大,采用sqlite存儲,可以很方便的用命令行或者桌面客戶端查看數據。

sqlite爬蟲數據

反爬蟲策略和應對方式

稍微大一些的網站都會有一些反爬蟲策略,簡單一點的根據User-Agent過濾,例如知乎,我們只需要設置為和瀏覽器相同即可。復雜一點的會限制非登陸用戶,也只需要按照之前例子中的方式登陸,或者干脆先用瀏覽器登陸好,然后在第一次訪問的時候帶上瀏覽器中的cookie就好,實現起來難度不大。但是有不少網站,例如豆瓣和github,在檢測到某一客戶端頻繁訪問后,會直接封鎖ip,這個問題解決起來就相當的棘手和蛋疼了。

解決方法也挺簡單,我們只需要找到足夠多的代理ip就可以了,只要策略得當,短時間內不要過度頻繁的使用同一ip代碼,或者當某一ip地址被封鎖后馬上切換到其他的ip代理,這樣就可以保證高效的爬取數據了。那如何找到代理ip並且如何使用了,其實免費的代理ip很多,我們用百度搜索代理ip就可以找到很多網站,例如:http://www.ip181.com/。

代理ip檢測平台

python使用代理ip

找到代理ip后,就可以用上面的方式很輕松的使用代理ip了,但是網上免費的代理ip質量不好,很多不可用,而且速度慢、不穩定,請求的時候最好設置一下超時時間。我之前在爬github的時候,會專門寫一個爬蟲從這些網站搜集代理ip地址,一旦檢測到被github封鎖,就隨機選取一個代理ip使用,如果發現代理ip不可用,不斷的更換知道可用的代理ip為止,每個代理ip使用的次數也會有一定的限制,保證爬蟲在整個執行期間不會因為ip封鎖而不可用。

異常及性能

曾經我遇到過這樣的狀態,寫好並運行爬蟲一個小時之后,因為網絡抖動或者某一種特殊的頁面導致解析失敗,整個爬蟲運行終止,這就蛋疼了。如果日志打印不充分,我連運行失敗的原因都清楚,更別說修復問題,即使修復好重新運行,又要花一個小時才能到剛才的進度,費時費力。

爬蟲出現異常的情況實在是太多:網絡抖動、解析頁面失敗、反爬蟲被屏蔽、寫入數據異常、代碼邏輯錯誤等等都會導致進程終止,而一般爬蟲需要數小時甚至數天數周的運行,每一次的運行失敗都是時間巨大的浪費。因此一定需要在可能出現異常的地方接住異常,保證爬蟲不會終止,並記錄日志,這些錯誤日志不僅可以快速的幫助我們定位錯誤,在出錯不多的情況下,我們甚至不需要修改代碼重新運行,只需要人肉補全這些數據就好。

如果發現自己的爬蟲運行效率太低,爬取速度太慢,並發對於提升爬蟲速度是一個不錯解決方案,因為GIL的存在,多進程並發模式對於python提速更優,我們可以使用生產者消費者的模式將爬蟲任務進行拆分,從而實現多進程或者分布式。一般來說可以將HTTP請求的數據、解析數據、存儲數據分別用不同的進程實現,這些進程之間通過消息隊列進行通信,保證每個進程無狀態,就可以非常容易的實現多進程擴展。即使某一類進程出現異常,也不需要重新啟動所有的進程,只需要修復好代碼重新啟動,就可以實現斷點續爬了。

一些常用的分布式工具:redis、rabbitmq、scrapy、celery、you-get

redis: http://www.redis.cn/
rabbitmq: http://www.rabbitmq.com/documentation.html
scrapy: http://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/tutorial.html
celery: http://docs.jinkan.org/docs/celery/
you-get: https://github.com/soimort/you-get/wiki/中文說明


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM