版權聲明:本文由文智原創文章,轉載請注明出處:
文章原文鏈接:https://www.qcloud.com/community/article/139
來源:騰雲閣 https://www.qcloud.com/community
引子:
文智平台是利用並行計算系統和分布式爬蟲系統,並結合獨特的語義分析技術,為滿足用戶NLP、轉碼、抽取、全網數據抓取等中文語義分析需求的一站式開放平台。其中分布式爬蟲系統是獲取海量數據的重要手段,給文智平台提供了有效的大數據支撐。
如果簡化網絡爬蟲(Spider)架構,只留下一個模塊,那么這個模塊就是抓取器Crawler,它在整個Spider架構中就相當於一個嘴巴,這個嘴巴永遠在web的海量數據世界中尋找食物。最簡單的Crawler就是一個實現了HTTP協議的客戶端,它接收一系列URL,向網站發起抓取請求,輸出一系列網頁Page,如圖1所示。
圖1:Crawler的工作過程
對於一些小的抓取任務,wget就是一個很不錯的選擇,例如學校里面搞搜索引擎研究,就經常使用wget或基於wget源碼做修改來滿足需求。對單次網頁下載來說,通常大部分時間都消耗在等待對方網站響應上。如果下載的並發量小,機器和帶寬資源就很難得到充分利用,抓取速度上不去。作為商業搜索引擎來說,我們每天抓取數百萬甚至千萬數量級的網頁,那么使用wget性能就遠遠不能滿足需求。因此我們需要擁有一個高性能、高並發的輕量級抓取器。
隨着工作的深入,特別是文智中文語義平台的提出,對數據的需求更加精細化、多元化,簡單的HTTP抓取已經不能完全滿足需求。
目前,互聯網上頁面的實現方式多種多樣,越來越多的站點使用JavaScript部署,例如在http://www.tudou.com/albumcover/Lqfme5hSolM.html 這個頁面中,其中關於劇集列表信息(如圖2所示)就是利用JavaScript技術來填充的,如果想抓取這個信息,傳統的Crawler就無能為力;有些頁面抓取需要Post信息(登錄等),隨着Ajax技術使用,在抓取前后需要與頁面進行交互,例如一些新聞的評論頁面,其中的評論信息是通過點擊“評論”鏈接后利用Ajax技術來異步抓取的,這個信息傳統的Crawler也無法滿足抓取需求,例如http://news.sina.com.cn/c/2014-11-26/184331207293.shtml 這個頁面,如圖3所示。
這些現狀都給web頁面的抓取收錄帶來了困難,也對傳統Crawler提出了挑戰。所以對於Crawler來說,除了高性能、高並發的要求外,還有如下需求:
- 抓取AJAX頁面、模擬網頁操作,進行表單提交;
- 通過javascript動態實現網頁跳轉;
- 對內嵌frame頁進行抓取拼接;
- 多媒體文檔:音、視頻、圖片等內容的抓取;
圖2:通過Js技術填充的劇集列表信息
圖3:通過Ajax異步加載的評論信息
這些數據就是海量數據世界中的更美味的食物,而美味的食物總是包裹着厚實的外殼。所以Crawler必須擁有強大的牙齒來破殼取食,而這個牙齒對於Crawler來說就是WebKit。
一.Webkit簡介
WebKit是由Apple公司開發的開源瀏覽器內核,WebKit的發展具體可見文檔[1],這里不再贅述。WebKit主要分為三個模塊:WebCore、JavaScriptCore、平台應用相關Port。WebCore是最核心的部分,負責HTML、CSS的解析和頁面布局渲染,JavaScriptCore負責JavaScript腳本的解析執行,通過bindings技術和WebCore進行交互,Port部分的代碼結合上層應用,封裝WebCore的行為為上層應用提供API來使用,如圖4所示。
圖4:WebKit框架
一個網頁的加載過程從用戶請求一個URL開始,首先判斷是否有本地cache資源可用,如果沒有則通過platform/network調用平台相關的下載模塊完成HTML和其他資源的下載,HTML字符串經過HTML解析器生成HTML DOM樹,並將每個DOM節點注冊為JavaScript Object供JS腳本調用,在生成DOM樹每個節點的同時,同步生成Layout樹的每個節點,其中保存了布局信息,和CSS樣式信息,系統繪制時觸發page模塊中的Paint操作,使用platform/graphics調用平台相關的圖形庫完成實際繪制,整個過程如圖5所示。本文對WebKit內核不做很多介紹,如果感興趣,請參考技術文檔[2]。
圖5:WebKit加載網頁過程
二.WebKit編譯以及裁剪
Spider這里使用的是Qt中集成的WebKit,所用Qt的版本是Qt-4.7.4中的通用版本,下載地址見文檔[3]。WebKit所在位置為qt-everywhere-opensource-src-4.7.4/src/3rdparty/webkit。這里選擇的是單獨編譯QtWebKit。通過QMAKE命令編譯產生MakeFile文件。編譯過程是在接觸過的源碼中屬於比較難編譯的,需要注意的是QtWebKit依賴QtScript,單獨編譯QtWebKit的話,需要單獨編譯QtScript,具體的編譯過程參考文檔[4]。
由於Spider不需要最終渲染出網頁,只需要WebKit執行之后的網頁內容。同時為了提高WebKit的執行速度(爬蟲對於性能的要求非常高),這里對WebKit進行了一些裁剪。裁剪包括去除SVG以及一些可選組件和去除WebKit的渲染網頁(Render和Layout)的過程。
其中WebKit中的可選組件包括對DATABASE的支持組建、對ICONDATA的支持組建、XPATH、XSLT、XBL和SVG的支持組件。這些組件不再一一介紹,有興趣的可以Google之。組件的裁剪過程比較簡單,通過修改編譯使用的PRO文件來進行,例如裁剪掉SVG組件,只需要找到WebCore目錄下的WebCore.pro文件,將其中的“qt-port: !contains(DEFINES, ENABLE_SVG=.): DEFINES += ENABLE_SVG=1
”修改為“ENABLE_SVG=0
”,然后使用qmake生成新的makefile編譯即可。
去除WebKit的渲染和排版(Render和Layout)的過程比較繁瑣,首先需要確定WebKit中進行頁面繪制和渲染的入口,通過閱讀源碼和GDB調試得知:FrameView::layout
操作實現繪制前的排版工作,文檔繪制的入口是Frame::paint
函數。經過分析驗證,頁面顯示過程中的繪制(paint)的函數入口就是Frame::paint
,它的繪制動作的觸發來自於上層的動作。作為最靠近Qt的函數入口,只需要把這個函數注釋掉,所有的繪制動作就不會再發生。layout的動作是由於FrameView
的layout動作引起的。注釋掉函數Frame::paint
和FrameView::layout
之后,就堵住了絕大多數的繪制和排版動作,從而節省了WebKit加載網頁的時間。
三.WebKit在Spider中的應用
如前所述,WebKit為Spider提供了更強大的數據抓取的能力,其中它作為一個單獨的服務模塊來處理需要WebKit加載的頁面,目前采用比較簡單的CGI接口來與上游服務對接,與上游服務模塊之間通過HTTP協議進行交互。后期隨着業務復雜度的提升和接口數據的復雜化,不排除使用自定義協議的可能,服務模型如圖6所示。
圖6:WebKit CGI服務
為了使WebKit作為一個類庫應用於服務器的運行中,首要的問題就是去除WebKit中所有關Qt圖形化的部分,然后才可以考慮去掉WebKit中有關Qt的其他工具類的應用。這樣才能夠在在非圖形化的方式下獲得頁面Load之后的內容,而這一內容同時也包括了頁面中的非交互式JS代碼所生成的內容。本文檔所描述的去圖形化步驟為:
- 去除WebKit中所有有關QWidget的代碼;
- 在去除了QWidget的基礎上,修改WebKit代碼中有關QWidget屬性的獲取和設置部分;
- 去除WebKit中有關QApplication的相關代碼。
但是目前存在的問題是QApplication必須在main函數中初始化並使用的,而通過Qt的文檔也可以看出每一個GUI Qt程序都必須初始化一個QApplication對象,該對象主要管理整個Qt程序的資源以及處理分發Qt程序運行中的事件。這種應用模式是不能夠滿足作為一個獨立類庫來使用的,因為QCoreApplication只能在main函數中初始化,並且必須調用app.exec()
才能夠進入事件處理的循環。目前只有搞清楚WebKit中的整個執行流程,完全去除Qt,這一方法需要了解整個WebKit中的功能,搞清楚目錄WebKit/qt、目錄WebCore/platform中所有有關文件中的Qt部分的功能,以及與WebCore和JavaScriptCore結合的方式。這種方法優勢是可以完成一個獨立的類庫,將來的服務器運行效率要高,劣勢是需要人力和時間去研究上述代碼,時間周期長,所以目前還是保留app.exec()
。
由於Spider下載需要考慮外網權限和網站封禁等策略,這里使用重寫QNetworkProxyFactory類中queryProxy來實現網絡代理,首先配置可以訪問外網的機器列表,通過對URL串計算MD5值,然后根據MD5值計算hash值,以決定使用哪台外網機器來下載數據。
WebKit不僅會加載URL對應的HTML文檔,同時會下載HTML文檔中的那些圖片數據以及CSS、JS數據等。但是對於Spider來說,目的是能夠發現更多的優質URL,對於網頁渲染的樣式和圖片數據並不關心,所以下載這些數據對於Spider來說是一種額外的負擔。這里通過對QNetworkAccessManager中的createRequest進行重寫,對於后綴是css、png、gif、jpg、flv的URL返回一個不可到達的request,這個request直接返回一個錯誤,並不會發起真正的網絡請求,這樣就減少了網絡IO,加快網頁的加載速度.
目前基於WebKit,Spider實現了抓取AJAX網頁、模擬點擊后抓取需求。抓取AJAX頁面比較簡單,WebKit在load網頁之后,會執行頁面中JS腳本,實現異步拉取數據,然后重新拼裝頁面,webframe在收到loadfinsh信號之后,即可獲得加載異步數據之后的頁面。模擬點擊也比較類似,通過JS代碼嵌入到網頁中,然后通過evaluateJavaScript函數觸發JS代碼執行,執行完再獲取網頁數據即可。目前正在開發支持JS實現網頁跳轉(一般瀏覽器訪問一條URL發生跳轉時,地址欄的URL會改變,捕獲到這種改變,即能拿到所有跳轉的URL。在應用層監聽QWebFrame的urlChanged信號,當地址欄的URL發生改變時觸發自定義的onUrlChanged槽函數,通過這個槽函數來實現自動跳轉,獲得跳轉后的頁面)、支持多協議抓取等功能,相信WebKit會在Spider中的應用越來越廣泛,能夠爬取更多殼內的寶貴數據。
參考文檔:
[1] http://web.appstorm.net/general/opinion/the-histoy-of-webkit
[2] https://www.webkit.org/coding/technical-articles.html
[3] http://download.qt-project.org/archive/qt/4.7/qt-everywhere-opensource-src-4.7.4.tar.gz
[4] http://trac.webkit.org/wiki/BuildingQtOnLinux