最近的一個項目是寫一個爬蟲框架,這個框架主要采用Master-Slave的結構,Master負責管理要爬取的Url和已經爬取過的Url,Slave可以有多個,主要負責爬取網頁內容,以及對爬取下來的網頁內容進行持久化的工作。整個項目用Thrift作為RPC通信框架。
1. 爬蟲流程
如果是一個單機版的爬蟲,其實代碼非常簡單:
Initialize: UrlsDone = ∅ UrlsTodo = {‘‘yahoo.com/index.htm’’, ..} Repeat: url = UrlsTodo.getNext() ip = DNSlookup( url.getHostname() ) html = DownloadPage( ip , url.getPath() ) UrlsDone.insert( url ) newUrls = parseForLinks( html ) For each newUrl If not UrlsDone.contains( newUrl ) then UrlsTodo.insert( newUrl )
如果需要將UrlsDone和UrlsToDo這兩個數據結構交由一個Master來管理,Master的接口可以定義成如下的:
public interface SpiderManager extends Closeable { /** * 獲取一個待爬取的URL * @return URL */ URLData poll(); /** * 將一個待爬取的URL交給Manger */ void offer(URLData url); /** * 將一個已經爬取的URL返回給Manager */ void done(URLData url); /** * 判斷一個URL是否已經爬取過 * @param url * @return */ boolean isDone(URLData url); /** * 已經處理過的URL數量 */ long doneSize(); /** * 待處理的URL數量 */ long toDoSize(); boolean usingAck(); }
2.分布式爬蟲框架要解決的問題
上述單機的版的爬蟲,在數據量不大和數據更新頻率要求不高的情況下,可以很好的工作,但是當需要爬取的頁面數量過多,或者網站有反爬蟲限制的時候,上述代碼並不能很好的工作。
例如通用的搜索爬蟲需要爬取很多網頁的時候,就需要多個爬蟲來一起工作,這個時候各個爬蟲必然要共享上述兩個數據結構。
其次,現在很多網站對於爬蟲都有限制,如果要是爬取的過於頻繁,會被封Ip,為了應對這種情況,對應的策略是休眠一段時間,這樣的話,又浪費了CPU資源。
最后,當要求實現不同的爬取策略,或者統一管理爬蟲作業生命周期的時候,必然要一個Master來協調各個Slave的工作。
3. 設計實現
3.1 Master:
我們框架的主節點稱為WebCrawlerMaster,針對不同的爬蟲任務,WebCrawlerMaster會生成不同的WebCrawlerManager,WebCrawlerManger的功能是管理UrlsToDo和UrlsDone兩個數據結構。Master主要的功能是管理WebCrawlerManager的實例,並且將不同的請求路由到對應的WebCrawlerManager上去。
對於Master來說,最主要的組件是一個叫做MetaDataHolder的成員,它主要用來管理元數據信息。為了加強系統的健壯性,這部分信息是一定需要持久化的,至於持久化的選擇,可以是Redis,或者關系型數據庫,甚至寫文件都可以。如果用Mysql來做持久化的工作,則需要做應用層的cache(通常用一個HashMap來實現)。
3.2 數據結構
對於一個CrawlManager,它主要管理兩個數據結構UrlToDo,和UrlDone,前者可以抽象成一個鏈表,棧或者有優先級的隊列,后者對外的表現是一個Set,做去重的工作。當定義出ADT(abstract data type)以后,則可以考慮出怎么樣的去實現這個數據結構。這樣的設計方法其實和實現一個數據結構是一樣的,只不過當我們實現數據結構的時候,操作的對象是內存中的數組和列表,而在這個項目中,我們操作的對象是各種存儲中提供給我們的功能,例如Redis中的List、Set,關系型數據庫中的表等等。
4. 后記
這次的爬蟲框架,從最開始的偽代碼來看,是很簡單的事情,但是一旦涉及到分布式的環境和系統的可擴展性,要真的實現起來,還是需要考慮到一些額外的東西,例如並發狀態下共享數據結構的讀寫、系統的高可用等等,但是我覺得這個項目真正讓我滿意的地方,是通過合理的數據結構行為層面的抽象,讓這個爬蟲系統有着很強的擴展性。例如現在默認的UrlToDo是一個FIFO的隊列,這樣的話,爬蟲實際上是按照BSF的策略去爬取的。但是當UrlToDo配置成一個LIFO的stack以后,爬蟲實際上按照DSF的策略去爬取的,而這樣的變化,只需要的更改一下請求新的WebCrawlerManager的參數,爬蟲的業務代碼並不需要任何的修改。