Java 月薪25K的爬蟲工程師對爬蟲的流程做了一個非常全面的總結!

青笙發表於2018-08-21

爬蟲是一個比較容易上手的技術,也許花5分鐘看一篇文件就能爬取單個網頁上的資料。但對於深度爬蟲,完全就是另一回事,並不是1*n這麼簡單,還會衍生出許多別的問題。

這裡雙手奉上業內爬蟲流程圖一份

很拉風的樣子

先檢查是否有API

API是網站官方提供的資料介面,如果通過呼叫API採集資料,則相當於在網站允許的範圍內採集,這樣既不會有道德法律風險,也沒有網站故意設定的障礙;不過呼叫API介面的訪問則處於網站的控制中,網站可以用來收費,可以用來限制訪問上限等。整體來看,如果資料採集的需求並不是很獨特,那麼有API則應優先採用呼叫API的方式。

資料流分析

對於要批量爬取的網頁,往上一層,看它的入口在哪裡;這個是根據採集範圍來確定入口,比如若只想爬一個地區的資料,那從該地區的主頁切入即可;但若想爬全國資料,則應更往上一層,從全國的入口切入。一般的網站網頁都以樹狀結構為主,找到切入點作為根節點一層層往裡進入即可。

值得注意的一點是,一般網站都不會直接把全量的資料做成列表給你一頁頁往下翻直到遍歷完資料,比如鏈家上面很清楚地寫著有24587套二手房,但是它只給100頁,每頁30個,如果直接這麼切入只能訪問3000個,遠遠低於真實資料量;因此先切片,再整合的資料思維可以獲得更大的資料量。顯然100頁是系統設定,只要超過300個就只顯示100頁,因此可以通過其他的篩選條件不斷細分,只到篩選結果小於等於300頁就表示該條件下沒有缺漏;最後把各種條件下的篩選結果集合在一起,就能夠儘可能地還原真實資料量。

深圳二手房from鏈家

明確了大規模爬蟲的資料流動機制,下一步就是針對單個網頁進行解析,然後把這個模式複製到整體。對於單個網頁,採用抓包工具可以檢視它的請求方式,是get還是post,有沒有提交表單,欲採集的資料是寫入原始碼裡還是通過AJAX呼叫JSON資料。

同樣的道理,不能只看一個頁面,要觀察多個頁面,因為批量爬蟲要弄清這些大量頁面url以及引數的規律,以便可以自動構造;有的網站的url以及關鍵引數是加密的,這樣就悲劇了,不能靠著明顯的邏輯直接構造,這種情況下要批量爬蟲,要麼找到它加密的js程式碼,在爬蟲程式碼上加入從明文到密碼的加密過程;要麼採用下文所述的模擬瀏覽器的方式。

 

解析工具

原始碼下載後,就是解析資料了,常用的有兩種方法,一種是用BeautifulSoup對樹狀HTML進行解析,另一種是通過正規表示式從文字中抽取資料。

BeautifulSoup比較簡單,支援Xpath和CSSSelector兩種途徑,而且像Chrome這類瀏覽器一般都已經把各個結點的Xpath或者CSSSelector標記好了,直接複製即可。以CSSSelector為例,可以選擇tag、id、class等多種方式進行定位選擇,如果有id建議選id,因為根據HTML語法,一個id只能繫結一個標籤。

正規表示式很強大,但構造起來有點複雜,需要專門去學習。因為下載下來的原始碼格式就是字串,所以正規表示式可以大顯身手,而且處理速度很快。

對於HTML結構固定,即同樣的欄位處tag、id和class名稱都相同,採用BeautifulSoup解析是一種簡單高效的方案,但有的網站混亂,同樣的資料在不同頁面間HTML結構不同,這種情況下BeautifulSoup就不太好使;如果資料本身格式固定,則用正規表示式更方便。比如以下的例子,這兩個都是深圳地區某個地方的經度,但一個頁面的class是long,一個頁面的class是longitude,根據class來選擇就沒辦法同時滿足2個,但只要注意到深圳地區的經度都是介於113到114之間的浮點數,就可以通過正規表示式"11[3-4].d+"來使兩個都滿足。

寫入資料庫

如果只是中小規模的爬蟲,可以把最後的爬蟲結果匯合成一張表,最後匯出成一張表格以便後續使用;但對於表數量多、單張表容量大的大規模爬蟲,再匯出成一堆零散的表就不合適了,肯定還是要放在資料庫中,既方便儲存,也方便進一步整理。

寫入資料庫有兩種方法,一種是通過Pandas的DataFrame自帶的to_sql()方法,好處是自動建表,對於對錶結構沒有嚴格要求的情況下可以採用這種方式,不過值得一提的是,如果是多行的DataFrame可以直接插入不加索引,但若只有一行就要加索引否則報錯,雖然這個認為不太合理;另一種是利用資料庫引擎來執行SQL語句,這種情況下要先自己建表,雖然多了一步,但是表結構完全是自己控制之下。Pandas與SQL都可以用來建表、整理資料,結合起來使用效率更高。

寫入資料庫有兩種思路,一種是等所有的資料都爬完,集中一次向量化清洗,一次性入庫;另一種是爬一次資料清洗一次就入庫。表面上看前者效率更高,但是對於大規模爬蟲,穩定性也是要考慮的重要因素,因為在長久的爬蟲過程中,總不可避免會出現一些網路錯誤,甚至如果出現斷網斷電的情況,第一種情況下就全白費了,第二種情況下至少已入庫的不會受影響,並且單次的清洗和入庫是很快的,基本不怎麼費時間,所以整體來看推薦第二種思路。

爬蟲效率提升

對於大規模爬蟲,效率是一個核心問題。單個網頁爬取可能很大,一旦網頁數量級大增之後,任務量也會大增,同時方式下的耗時也會大增。沒有公司或人個願意爬個幾十萬上百萬的頁面還要等幾個月,因此優化流程、提高效率是非常必要的。

儘量減少訪問次數。單次爬蟲的主要耗時在於網路請求等待響應,所以能減少訪問就少訪問,既減少自己的工作量,也減輕網站的壓力,還降低被封的風險。首先要做的就是流程優化,儘可能精簡流程,一些資料如果可以在一個頁面內獲取而不必非要在多個頁面下獲取,那就只在一個頁面內獲取。然後去重也是非常重要的手段——網站並不是嚴格意義的互不交叉的樹狀結構,而是多重交叉的網狀結構,所以從多個入口深入的網頁會有很多重複,一般根據url或者id進行唯一性判別,爬過的就不再繼續爬了。最後,值得深思的一點就是,是不是所有的資料都需要爬?對於那些響應慢,反爬機制很嚴格的網站,爬少量的都困難,爬大量的時間成本就會高到難以接受,這種情況下怎麼辦?舉一個例子,對於氣象資料,已知的一點是時間、空間越接近的地方資料就越接近,那麼你爬了一個點的氣象資料之後,100米以內的另一個點就可以不用再爬,因為可預期一定是跟之前的點差不多;這個時候就可以採用機器學習的方法,爬取一部分資料作為訓練資料,其他的進行預測,當對資料的準確性要求不是特別高,當模型的效能比較好,採用機器學習模型預測就可以省下大部分爬蟲的工作。雖然專業的爬蟲工程師懂機器學習的可能不多,但這正是複合型人才的優勢。

大量爬蟲是一個IO阻塞的任務,因此採用多程式、多執行緒或者協程的併發方式可以有效地提高整理速度。個人推薦用協程,速度比較快,穩定性也比較好。

即使把各種法子都用盡了,單機單位時間內能爬的網頁數仍是有限的,面對大量的頁面佇列,可計算的時間仍是很長,這種時候就必須要用機器換時間了,這就是分散式爬蟲。首先,分散式不是爬蟲的本質,也不是必須的,對於互相獨立、不存在通訊的任務就可手動對任務分割,然後在多臺機器上分別執行,減少每臺機器的工作量,耗時就會成倍減少。比如有100W個頁面待爬,可以用5臺機器分別爬互不重複的20W個頁面,相對單機耗時就縮短了5倍。但是如果存在著需要通訊的狀況,比如一個變動的待爬佇列,每爬一次這個佇列就會發生變化,即使分割任務也就有交叉重複,因為各個機器在程式執行時的待爬佇列都不一樣了——這種情況下只能用分散式,一個Master儲存佇列,其他多個Slave各自來取,這樣共享一個佇列,取的時候互斥也不會重複爬取。scrapy-redis是一款用得比較多的分散式爬蟲框架。

資料質量管理

大量的頁面往往不會是結構完全一樣,而且大量的訪問也總會出現該訪問成功卻訪問不成功的情況,這些都是非常常見的狀況,因此單一的邏輯無法應對各種不可預知的問題,反映在結果上就是爬取的資料往往會有錯漏的情況。

try...except是Python中常用的異常診斷語句,在爬蟲中也可充分應用。一方面,同樣的欄位可能在有的網頁上有,另外的網頁上就是沒有,這樣爬取該欄位的語句就會出錯,然而這並不是自己邏輯或程式碼的錯,用診斷語句就可以繞過這些網站的坑;另一方面,大規模爬蟲是一個耗時較長的過程,就像是千軍萬馬衝鋒,不能因為中間掛了幾個而停止整體程式,所以採用這個語句可以跳過中間出現的各種自己產生或者網站產生的錯誤,保證爬蟲整體的持續進行。

斷點續傳也是流程設計是重要的一塊。一個一旦啟動就必須要等它跑完,如果中途中斷就前功盡棄的爬蟲系統是非常不健壯的,因為誰也無法預料中間會因各種原因中斷,而且估計也沒有誰會喜歡這種類似於被綁架的感覺。健壯的爬蟲系統應該是隨時都可以啟動,而且每次啟動都是爬剩下的而不是從頭開始重複爬,其實這個流程設計也比較簡單,如下圖所示:所有待爬的網頁total_urls分為兩部分,一部分是已爬過的gotten_urls(初始化之前為空),total_urls與gotten_urls的差集remained_urls就是剩餘要爬的網頁。total_urls是固定的,每執行一次爬蟲,gotten_urls就會增加,下一次啟動爬蟲程式計算的remained_urls就減少了,當remained_urls為空表示完成全部爬蟲任務。這樣的斷點續傳流程設計可使爬蟲程式可以隨時停下,隨時啟動,並且每次啟動都不會做重複勞動。

錯漏校驗可以入庫之後進行,這一步就是把爬蟲過程中產生錯漏的記錄篩選出來清掉重新爬,這一步也很重要,保證資料質量才能繼續後續的流程。錯漏校驗就要結合業務自己來寫一套資料清洗流程。對於欄位為空的情況,有兩種產生原因:一是該網頁本來就沒有這個欄位,這不是錯誤;另一種是由於網路出錯沒有獲取到該欄位,這是錯誤,要篩選出來清除——一般情況下可以通過status_code是否為200來判斷網路訪問是否出錯來判斷空欄位是否是由於網路出錯的原因造成的,對於特殊的status_code為200仍不返回正常資料的就需特殊分析了。此外,可以通過某些欄位固定的屬性來作為篩選條件,比如名稱不能為空(或者為空就捨棄)、深圳地區的經度介於113和114之間等條件來過濾掉缺漏或者是網站反爬惡意傳回的錯誤資料。清洗邏輯越全面複雜,資料質量越高,後續使用資料時產生的問題就越少;這也是一塊需要深入思考的部分。

有的網站必須要登入才能訪問,才能爬蟲。以知乎為例,知乎的模擬登入必較簡單,甚至現在都沒有對帳號和密碼加密,直接明文post就可以。請求頭的cookie含有登入資訊,而知乎的cookie壽命較長,所以可以直接在網站上人工登入然後把cookie複製到程式碼中;知乎目前的反爬機制是如果判斷是機器人就封帳號但不封IP——封IP是同樣的機器無法訪問,但卻可以用同樣的帳號在其他機器上訪問;封號是同樣的帳號在各種終端上都無法訪問,但同一臺機器上卻可以換號訪問。基於這種機制,爬知乎就不需要IP代理池而需要的是帳號池。舉另一個例子,騰訊有一個子網站,它也要求必須QQ登入,而且cookie只有6分鐘的壽命,而且一個帳號一天只能訪問130次超過就封號,無論爬得再慢——這種情況下只能搞大量的QQ號進行自動登入並不斷切換。

如果有的網站的反爬機制實在太過喪心病狂,各種JS程式碼邏輯十分複雜艱深,那隻能模擬瀏覽器了。模擬瀏覽器其實就是一種自動的瀏覽器訪問,與正常的使用者訪問很類似,所以可以跳過大部分的反爬機制,因為你裝得實在太像正常使用者;不過缺點也很明顯,就是慢。所以可以用requests搞定的優先用requests,實在沒有辦法了再考慮模擬瀏覽器。

驗證碼。驗證碼一出就蛋疼了……Python有自動識別影像的包,不過對於大部分網站的驗證碼都無能為力。寫一個自動識別驗證碼的程式理論上不是不行,但是這種複雜的機器學習專案一點都不比爬蟲系統本身難度低,從成本的角度考慮實在是得不償失——何況對於有些網站如谷歌,驗證碼識別是非常困難的。所以對於驗證碼問題,首先是躲過去儘量不要觸發驗證碼,實在觸發了只能乖乖人工去填驗證碼。

各種各樣的反爬機制也算是因垂斯聽,只有身經百戰,爬得多了,才能談笑風生,爬蟲水平不知道高到哪去了。有哪些有趣的反爬蟲手段?

爬蟲目前在法律上尚屬灰色地段,但爬別的網站用於自己的商業化用途也可能存在著法律風險。非法抓取使用“新浪微博”使用者資訊 “脈脈”被判賠200萬元,這是國內的一條因爬蟲被判敗訴的新聞。所以各商業公司還是悠著點,特別是爬較為隱私的資料。

爬蟲有風險,想爬要慎重哦,哈哈 謝謝大大的文章,今天借花獻佛。希望大家能喜歡。

 

 

相關文章