構建一個高性能的網頁抓取器

作者: 武澤勝  來源: CSDN  發布時間: 2010-12-05 15:22  閱讀: 1563 次  推薦: 2   原文鏈接   [收藏]  

   互聯網的發展,使人類提前進入了信息爆炸的年代,在浩瀚無邊的信息海洋里,如何快速、準確找到對自己有用的信息,就成了一個很有價值的研究課題,于是,搜索引擎應運而生。現在,國內外大大小小的搜索引擎有很多,搜搜也是這搜索引擎大軍中的一員悍將。筆者有幸參與了搜搜研發過程中的一些工作,在這里寫一些自己的理解與看法,權當是拋磚引玉,希望能夠得到業內前輩們的一些指點。

  對于網頁搜索引擎來說,它的基本處理流程,通常可以分為三個步驟:一是對海量互聯網網頁的抓取,也稱下載;二是對已下載的網頁進行預處理,包括抽取正文、建立正向索引、建立逆向索引等;三是向用戶提供檢索服務,包括對最終的檢索結果進行打分排序,展示給用戶等。從上面的介紹中可以看出,網頁數據是網頁搜索引擎最為根本的內容,沒有豐富的網頁數據做支撐的網頁搜索引擎,它所能提供的服務是非常局限的。因此,一個強大的網頁抓取系統(也稱爬蟲系統,又稱Spider系統),對網頁搜索引擎來說,是至關重要的。

      一個強大的Spider系統,不僅要能夠及時發現每天互聯網上新產生的網頁,還要能夠及時地更新已經抓取的網頁,使之最大程度上與互聯網上真實存在的頁面一致。下圖是一個完整的Spider系統的基本結構:

      從上圖可以看出,一個完整的Spider系統,包括調度器(Scheduler)、抓取器(Crawler)、抽取器(Extractor)、Url庫(Url DB)。調度器負責從Url庫中選擇需要抓取的Url列表,它是整個Spider系統的大腦;抓取器收到調度器所發送的待抓取Url列表,對其進行實際的抓取;抽取器收到抓取器下載完成的頁面,對其進行處理,抽取頁面正文,提取頁面里包含的子鏈接;Url庫接收并存儲新發現的子鏈接相關信息,并對已抓取的Url的相關信息進行更新。這就是一個完整的Spider系統的基本的處理主流程。從本文的題目可以看出,本文要講的重點是抓取器(Crawler),并不是整個Spider系統。下面就結合筆者之前所做的一些工作來談談,如何構建一個高性能的抓取器。

      通過上面的介紹,我們知道,抓取器的主要工作就是從互聯網上抓取網頁,看上去好像很簡單,其實任何簡單的事情,要做的特別的好,都不是那么的簡單。首先,我們來看一下一個抓取器的整體的一個結構視圖,如下圖所示:

      從上圖可以看出,一個抓取器總體上可以分為兩層:一層是網絡層(Network Frame),主要負責與上下游模塊的通信交互,以及與Web服務器的交互進,行下載網頁;一層是應用層(App Logic),完成抓取器內部的應用邏輯,比如Url對應的域名的IP的獲取,壓力控制(保證同一時間段內,對同IP的主機不會造成太大的壓力),跳轉識別(包括Http的跳轉和頁面內的跳轉),頁面附件(Js, Css, Frame等)解析及獲取等一系列應用邏輯。因為抓取的網頁內容經過一定的處理后,最終是要展示給用戶的,而用戶是通過瀏覽器來瀏覽頁面的,所以我們希望抓取器所獲取的頁面與用戶用瀏覽器打開的是一致的,因此,抓取器需要模擬一些瀏覽器的行為,做一些相關的工作,比如跳轉識別等。下面分別從網絡層和應用層兩個方面來介紹一下具體的一些內容。

      抓取器的網絡層,具體來說有兩個方面的工作要做,一個方面是與上下游模塊的通信交互,一個方面是與互聯網上的Web服務器進行交互,獲取待抓取Url列表中Url所對應的頁面。一方面要及時響應上游的抓取請求,另一方面要到互聯網上不停地下載待抓取Url列表中Url所對應的頁面,同時還要把抓取完成的頁面經過程一定的處理后發給下游,這就需要我們有一個高性能、大吞吐量、高并發量的網絡層做為支撐。在這里筆者將介紹一個比較常用的異步網絡通訊框架的設計,供感興趣的朋友做參考。下圖所示是該框架的一個線程模型:

      在上圖中,IOMonitor是一個獨立的線程,它的主要任務是檢測網絡IO事件,WorkThread是具體的工作線程。IOMonitor檢測到網絡IO事件后,向WorkThread發送消息進行通知。WorkThread在自己的線程中,不斷地處理的收到的消息,實際的網絡IO是發生在它里面的。WorkThread可以有多個,具體個數可以根據實際的應用需求進行設置。通常做法是與機器的邏輯CPU個數一樣,這樣會更加充分的利用機器的CPU資源。

      在Linux下,IOMonitor可以用Epoll來實現,通過調用epoll_wait()函數來獲取有事件發生的socket及其上下文信息(context),然后把該socket上的事件,通知到處理其IO的WorkThread中去。關于這里Epoll的使用,再多講一點,在這里推薦使用Epoll的ET(Edge Triggered)模式,如果要用LT(Level Triggered)模式,建議使用OneShot方式的LT。至于ET和LT各是什么,這里不贅言,感興趣的朋友可以參考Linux下的Mannual。

      這里需要說明的是,這樣做的目的是想更高效的地利用CPU資源。如果是在非Linux的平臺下,應該也有類似Epoll的機制,原理大同小異,這里也不再贅言。另外,關于socket本身,還有兩點需要說明一下:一是在這里最好用非阻塞的socket,這樣在每一個環節都不會產生阻塞現象,不會產生因為前面的socket的阻塞而影響后面socket上事件的處理的情況;二是socket本身是有狀態的,要在每個socket的上下文信息(context)中要維護該socket的當前狀態,當WorkThread處理到某個socket上的事件時,要根據其狀態采取不同的處理措施,可以采用類似下面的機制:

      應用層的主要任務是有效地組織整個抓取過程、完成相關的應用邏輯,具體包括DNS獲取,IP壓力控制,跳轉識別,附件提取等一系統功能。事實上不同的抓取器的應用層所要做的工作會因整個爬蟲架構的不同而千差萬別。但這里重點不在于此,而是在于介紹一種事件觸發(或者說消息通知)的模型,正是這種模型的運用,才使得我們的抓取器能夠達到每秒幾百甚至上千篇頁面的抓取速度。

      實際上,上一部分中講到的網絡層的模型,也是一種簡單事件觸發的模型,只不過上面提到的事件,只有網絡的可讀(Read)、可寫(Write)和異常(Exception)三種。事件觸發模型的關鍵,首先是要把內部的任務抽象成一個有狀態的Task(比如我們可以把抓取任務抽象為CrawlTask),然后對于每一個Task,它的整個處理流程是一個完整的狀態機(State Machine),而且它的每次狀態轉移,都是由某個事件(Event)來觸發的。這里的事件(Event)也是一種抽象,它可以是來自外部的網絡事件,也可以是內部的某種事件,它的特點就是當事件處理器處理到它的時候,必然會伴隨著某個Task的狀態機的狀態轉移,換言之,Task的狀態機是由事件(Event)來驅動的。通常的事件觸發模型的設計中,會有一個或者多個事件處理器,當有事件發生的時候,其中某個事件處理器就會處理它,首先根據該事件找到它所對應的Task,然后調用該Task在當前狀態下對應于該類型事件的處理函數,最后根據上面的處理結果,對該Task進行狀態轉移。

      以上即是本文的主要內容,最后想談一點對今后的一些想法。記得09年及之前的幾年,互聯網上有一個叫SaaS的概念很火。SaaS的全稱是“軟件即服務(Software as a Service)”,大致的意思是說要把軟件都做成服務,開發者不再向消費者直接賣軟件,而是把軟件做成一種服務,就像日常生活中的水、電一樣,提供給用戶。未來的抓取器,也不再僅僅是一個抓取器,它也可以發展成為一種抓取服務(Crawl Service),它不僅能夠做為搜索引擎的Spider系統中用來抓取網頁的一個單元來完成網頁抓取的任務,還要能夠滿足其他用戶的各種各樣的抓取(下載)需求,因此,我們有理由相信未來的抓取器的發展也將是CaaS------Crawl as a Service。

2
0
 
標簽:網頁抓取
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()