首先感謝能收到文章推送的同學們的不「取關」之恩。第一篇文章出來的比較慢,而且寫的很。。。「千呼萬喚shi出來」。如果你竟然真的看完了,請再次接受我的感謝,並誠摯的希望收到你的品評、意見和建議。
======= 我是分割線 =======
摘要
曾在搜索領域發揮著重要的作用的web crawler,伴隨著大數據的興起,從搜尋引擎走入更多的系統架構中。本文是在我經歷了一次爬蟲項目後的一個資料整理,包含常規爬蟲結構,存在的問題,以及已有的商業化方案等。篇幅有限,僅做概要解析。
大綱
1. 常見業務場景2. 爬蟲的結構抽象3. 爬蟲組件職責和方案解析
4. 自研 or 開源框架?5. 已公開的企業工程實踐賞析
考慮內容較多,文章拆分為兩篇。本文為(上)篇,包含1、2、3部分,偏理論;4、5偏實踐,乾貨眾多,將擇良辰吉日推送,敬請關注~ 廢話打住,以下正文。
1. 常見業務場景Web crawler對於大多數網際網路人來說應該都不陌生,不過我們還是從現實出發,梳理一下爬蟲所服務的大多數業務分類。
搜尋引擎。應該是最典型的業務場景,並且因其規模巨大,對爬蟲的要求最高,對爬蟲系統演進的貢獻也是巨大的。爬蟲類型一般分為unfocused topic(Google、百度等泛搜尋引擎),和focused topic(垂直搜索,如商品/社交/網盤搜索等)。
數據服務SaaS。一般都是有目的性的focused topic抓取,抓取到的原始數據需要抽取和再加工做進一步分析、聚合等。例如AppAnnie,Semantics3等。
用戶產品。有以做信息流為主的資訊抓取整合等,例如Flipboard,今日頭條。也有基於UGC(用戶產生內容)的拓展信息抓取,例如Pinterest。
無論是哪一種業務場景,在web crawler上的抽象模型都是共同的,有差別的一般是抓取任務觸發方式、URL seed的構建、抓取的內容篩選、存儲等方面,因業務而各異。因此我們從此拋開具體業務問題,看看常規的web crawler是怎樣抽象的。
2. 爬蟲的結構抽象文字蒼白,看圖說話:
爬蟲架構圖千千萬,大多神似。上圖是我抽象整理的版本,後文也會面向這個結構做解析。另外,當你能夠有足夠耐心看完全文後,再回來看看這張圖,療效更佳。
這個圖我們從Extrator後的new URL開始看起:
一個種子URL進入隊列,表明自己需要被抓取。
URL被URL Frontier拉取,這部分可以簡單理解為爬蟲的大腦,負責根據抓取策略選擇下一個需要下載的URL給到downloader。
Downloader抓取URL相應的頁面。
Extractor抽取頁面中的URL作為新的URL放入URL隊列,抽取想要的內容,或完整頁面,放入item隊列等待被存儲。
重複1~4直到沒有新的URL進入待抓取隊列。
所以,看上去實現一個爬蟲其實SO EASY,Google一下可以得到很多幾行Python代碼搞定的小片段(別問我為什麼不百度一下…)。
但如果我們需要實現的是一個要上線生產的工程級別的爬蟲,其實會有諸多挑戰,例如與JavaScript作鬥爭,與反爬蟲策略作鬥爭,等等。假設這些都被解決了,等待我們的還有如何更快、更高、更強,如何聚集千千萬萬的小🕷為我們並行作戰。
下面我們以責任劃分的方式,逐一分析每一組件要解決的問題。
3. 爬蟲組件職責和方案解析Downloader組件Downloader的唯一職責就是下載指定的URL。但由於各種客觀原因,downloader通常是爬蟲系統內最不穩定的部分。Downloader主要解決這幾類問題:
HTTP協議實現與頁面渲染
Downloader本質上就是一個遵循HTTP(s)的客戶端,因此實現完整的HTTP協議是最基本的要求。各個語言都有功能強大的開源庫可用,例如Python的Requests庫。基於這些庫可以自動或半自動處理例如redirect、gzip、chartset/encode,以及login等問題。
另外,現代網頁一個特點是由大量的JavaScript動態渲染頁面內容,因此我們期望downloader更像一個具備JS引擎的瀏覽器,可以拿到最終渲染好的頁面。針對這一需求,已經有一些比較成熟的方案,例如:
Selenium
- http://docs.seleniumhq.org/- 更多用於webdriver自動化測試,提供與真實瀏覽器的自動化交互能力。能給downloader提供最真實的頁面訪問能力。- 需要基於此做二次開發,自行構建適合爬蟲接入使用的服務。
PhantomJS
- http://phantomjs.org/- 同樣是較多用於面向JS的自動化測試。提供完備的JS執行能力。- 同樣需要一定的開發工作。
Scrapy-splash
- https://github.com/scrapinghub/splash- 以HTTP API + Scrapy middleware的方式提供Scrapy抓取過程中的頁面渲染。- 與Scrapy抓取框架(Python)結合緊密(畢竟都是scrapinghub出品)。
Antispam攻防戰
網站或API的構建中,一般都會涉及到antispam策略,無非是:我數據寶貴不想被抓,滿足一定條件才能抓,或者我扛不太住不能頻繁被抓。對於網站本身性能不好的case,我們之後說。 對於前兩類,一般都會有如下策略以及解決方案:
1) User-Agent的檢查
這個最直白,把UA長成這樣的:
「curl/7.29.0「」Wget/1.14 (linux-gnu)」
統統拒之門外,長成這樣的:
「Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36」
看著像好人,放進來。所以應對方法很簡單,構建一個足夠大的UA池,以一定選擇策略在HTTP請求頭中使用模擬UA。
2) 基於cookie / UA+IP的頻次控制
說普通話就是網站主覺得正常「人類」用戶請求網站是低於一定頻次,且分布相對均勻的。所以異類就要被燒死。判斷異類也大體是基於這個原則,基於統計的一定時間窗口內的頻次計數。
應對這用策略,一般可以:
頻次統計首先會基於cookie,因為這個是網站自己種下的,最精確,所以一般也會從cookie下手。
針對基於UA + IP的統計,除了需要前文提到的UA池外,還需要一個IP池,也就是有足夠多出口IP的proxy,模擬出sum(UA) * sum(IP)個完全不同的「用戶」,讓抓取請求看起來像人類訪問。
3) 區域IP限制
這個不多見,但是也是一個有效的方法。簡單說就是網站對不同區域的用戶,返回不同的內容。例如Google Play,當改變請求的IP為不同國家的IP時,返回的也是相應國家的App、榜單等信息。
這種場景,簡單的IP池是不夠的,我們需要的是一個可以定向地域的IP池。
業內對於泛IP池和定向IP池有不少成熟的免費/付費方案,例如:
Free proxy
- 對爬蟲要求不高的話,網上可以搜到大把的free proxy。- 通常不穩定,連通性不好,持續可用性和性能都很弱。
Tor (The Onion Router)
- 洋蔥網絡,https://en.wikipedia.org/wiki/Onion_routing。基於TCP,一種在計算機網絡上進行匿名通信的技術。每個Tor用戶也是Tor網絡中的一個節點。- Tor用戶訪問網絡時,經過2~5層的隨機節點路由,最終訪問網頁時為一隨機出口IP。- 需要自建HTTP服務:HAProxy(HTTP) <-> Polipo(HTTP <=> TCP) <-> Tor (TCP)。- 延遲大,不穩定,特徵強,容易被ban。
Pay proxy
- Luminati:https://luminati.io/- Crawlera:https://scrapinghub.com/crawlera/- 都屬於付費proxy服務,支持指定國家地區的出口IP。Crawlera比Luminati略便宜。- 原理上都是將自己的「肉雞」管理起來,分門別類,作為出口使用。- 「肉雞」主要指使用了相關免費產品,並在知情或不知情的情況下變成代理機器。例如使用Hola是免費產品,Luminati是同公司付費產品。
DNS cache
DNS解析是「眾所周知」的爬蟲系統中的瓶頸之一,並且在眾多實現中,DNS的查找過程是同步的,multithread blocking的,因此一般要求高性能的web crawler系統中都會有一個自己實現的DNS cache服務。這部分沒什麼可說的,write the d**n code。
Extractor組件看過downloader之後,我們看看相對簡單的extractor。Extractor職責主要是從抓取到的頁面抽取信息,使用的技術一般就是regrex、lxml、xpath + cssselector等。工程上主要有兩種實現思路:
只抽取所有的outlink,也就是當前頁面指向的其他頁面URL。頁面內容不做處理,丟到存儲。
- 思路1,主要用於泛搜尋引擎的爬蟲,保存原始頁面數據,將爬取和分析頁面分開。對頁面的後續處理方式靈活可選。- 缺點在於職責切分後複雜度提高,不適合於小型項目。
同時抽取outlink和內容中需要抓取的item,例如抓圖書,要抽取書名、作者、價格等,作為一個item發給下遊處理。
- 思路2,focused topic的爬蟲的選擇,同時做outlink抽取和簡單的信息抽取,開發效率高。- 缺點就是缺少職責隔離,容易出現抓取後丟棄原始數據的設計,想回溯或重放時無從下手。
Queue組件Queue無論在爬蟲還是其他系統模型中,都是提供分布式的平行擴展能力的關鍵組件。
對於單機爬蟲,以Scrapy為例,使用的就是Python的Queue實現,簡單好用。對於多機,http://queues.io/,各種口味供君挑選。最常用的應該就是Redis和Kafka了。
引入分布式queue之後,整個爬蟲系統可以簡單看成是supervisor-worker模型。這裡畫了個小圖說明:
更多關於web crawler scalable的方案,稍後會提到。
前面說了,frontier堪稱爬蟲大腦。我們先看看正經的定義:
A crawl frontier is the system in charge of the logic and policies to follow when crawling websites, and plays a key role in more sophisticated crawling systems. It allows us to set rules about what pages should be crawled next, visiting priorities and ordering, how often pages are revisited, and any behaviour we may want to build into the crawl.
熟悉Scrapy結構的同學應該能對上號,frontier在Scrapy中大致就是Scheduler的角色。
Frontier理論上覆蓋爬蟲的整個生命周期,知道每一個請求和應答的詳細信息,自身內部維護很多狀態和統計。其職責較多,主要包括:
去重(duplicate eliminate)
這個容易理解,同一個URL不應該被短時間內反覆抓取,這樣也可以避免loop。實現上一般用set去重或bloom filter去重的方式,後者空間利用率上遠優於前者。
URL normalization/canonicalization
簡單解釋:
- https://www.amazon.cn/ 和 https://www.amazon.cn/ref=nav_logo 是同一個URL。- http://z.cn 和 https://www.amazon.cn/ 是同一頁面。
同一個URL有多種表達方式,以及多URL到達同一頁面的情況,給1的去重帶來麻煩,因此無論是去重前還是系統內部對某URL內容的查詢前,一般要做URL的normalization/canonicalization。
優先級(priority)
在frontier內部,每個URL都可以有一個優先級,可以保證在worker資源有限的競爭時優先被抓取。例如社交數據抓取中,草根大號、熱門大V、意見領袖們的信息流的抓取優先級是比較高的,保證熱門數據先被抓取,對業務價值最大。實現上就是個優先級隊列模型。
頻次控制 & 訪問控制(rate/access limit)
前文我們提到過antispam,有基於cookie/UA+IP的訪問頻次策略,該策略的應對一般放在全局大腦frontier中,而非各個downloader。
另外,有被爬取經驗的網站一般都有自己robots.txt和sitemap.xml,指導爬蟲的行為。例如:可抓取的URL白名單/禁止抓取的URL黑名單,更新/抓取頻次等參考信息,並且面向不同的搜尋引擎爬蟲,策略也會不盡相同。理論上,爬蟲要嚴格遵循這些「約定」,保護被抓取網站。
因此,出於對爬蟲「條款」的遵循,frontier也要緩存這些約束信息,定期更新,並指導downloader的行為。
Storage組件Storage和queue一樣,選擇眾多。就爬蟲系統的存儲的特點,在選擇時需要考慮:
Scalability。抓取結果隨時間積累而線性增加,單機存儲是絕對無法滿足條件的。
Schema free。網頁或從其中抽取的item結構是易變的,需要存儲可以靈活應變。
Graph supported。在PageRank中,一個頁面的inlink和outlink是重要的rank因素之一,在其他一些業務場景中,頁面間的關係形成的圖譜則可能會作為最終產品面向用戶。因此在這些類場景中,圖支持是需要重點考慮的。
(上)篇完。
p.s. 在我自己讀到這的時候,也會覺得乾貨太少,整個文章戛然而止的感覺,體驗不好。無奈(下)篇內容太多,放在一起我擔心自己都看不下去,所以生生將它們分開了。相信我,(下)篇有乾貨,有乾貨,有乾貨。敬請期待。