最近在做項目的時候有一個需求:從網頁面抓取數據,要求是首先抓取整個網頁的html源碼(后期更新要使用到)。剛開始一看這個簡單,然后就稀里嘩啦的敲起了代碼(在這之前使用過Hadoop平台的分布式爬蟲框架Nutch,使用起來是很方便,但是最后因為速度的原因放棄了,但生成的統計信息在后來的抓取中使用到了),很快holder.html和finance.html頁面成功下載完成,然后解析完holder.html頁面之后再解析finance.html,然后很沮喪的發現在這個頁面中我需要的數據並沒有在html源碼中,再去瀏覽器查看源碼果然是這樣的,在源碼中確實沒有我需要的數據,看來不是我程序寫錯了,接下來讓人身心疲憊的事情來了---獲取包含動態內容的html頁面。
在所謂的中國最強搜索引擎---百度上面行走了好長的時間,發現大部分的人都在將使用WebDriver和HttpUnit(其實前者已經包含了后者),這個高興,終於找到了解決辦法。懷着萬分的激動使用WebDriver,我要想罵人了。
下面是關於WebDriver的吐槽
WebDriver是一個測試框架,原本設計的時候就不是用來服務爬蟲的,但是我想說的是:八字就差一撇了,你就不能多往前做一步嗎?為什么網上還有那么多的人推薦WebDriver呢?我想這些人沒有從實際出發,甚至還有的人狂言WebDriver可以解析完成后的頁面返回給想要爬去整個頁面的人(包含動態生成的內容),對,WebDriver可以完成這個任務,但是看到作者寫的代碼,我想說的是:哥們,你的代碼局限性太大了,解析自己寫的js代碼,而且js代碼簡單,這樣WebDriver當然是毫無壓力的完成任務。WebDriver在解析動態內容是要看js代碼的復雜性和多樣性。
什么是復雜性?
先貼一段代碼
WebDriver driver = newInternetExplorerDriver (); HtmlPage page = driver.get(url); System.out.println(page.asXml());
這一段代碼的意思是相信大家都看懂,上面使用的IE內核,當然還有FirefoxDriver, ChromeDriver,HtmlUnitDriver,這些driver的使用原理都是一樣的,先開啟瀏覽器(這個要時間的),然后加載url並完成動態解析,然后通過page.asXml()就可以得到完成的html頁面,其中HtmlUnitDriver模擬無界面瀏覽器,java中有執行js的引擎rhino,HtmlUnitDriver使用的就是rhino來解析js的,由於不會去啟動有界面的瀏覽器,所以HtmlUnitDriver的速度比前面的三者都快。無論是什么Driver,避免不了的是解析js,這是需要時間的,而且不用的內核對js的支持程序又是不同,比如說HtmlUnitDriver對於帶有滾動的js代碼支持很差,在執行時會報錯(親自體驗了)。js代碼的復雜的意思就是:對於不同的內核他們支持的js是不完全相同的,這個應該根據具體情況來定,鄙人好久沒有研究js了,所以關於各內核對js的支持就不說了。
什么是多樣性
前面說了,瀏覽器解析js是需要時間的。對於只嵌入少數的js代碼的頁面來說,通過page.asXml()來獲取完整的頁面時沒有問題的。但是對於嵌入比較多的js代碼的頁面,解析js是需要很多時間的(對於jvm來說),那么此時通過page.asXml()來獲取的頁面中大多數時候是不包含有動態生成的內容的。問題就來了,這樣的話為什么還說WebDriver可以獲得包含有動態內容的html頁面呢?網上有人說在driver.get(url)之后需要是當前線程等待一下才能獲取完成的頁面,也就是類似於下面的形式
WebDriver driver = new InternetExplorerDriver(); HtmlPage page = dirver.get(url); Thread.sleep(2000); System.output.println(page.asXml());
我按照這個想法去嘗試以下,呀,真的是可以。但是問題不正好也擺在那里了么?怎么樣去確定等待時間?類似於數據挖掘中確定閥值時的憑經驗的方法?,還是盡可能的是時間長一點。我覺得這些都不是很好的辦法,時間代價比較大。我就想在driver應該可以捕獲解析js完成后的狀態,於是我去找啊,找啊,可是根本就沒有這個方法,所以我說WebDriver的設計者為什么不再往前走一步,讓我們可以在程序中獲取到driver解析js完成后的狀態,這樣的話就不用使用Thread.sleep(2000)這樣的不確定性代碼了,可惜的是怎么也找不到,真是讓我心痛了一場。FirefoxDriver, ChromeDriver,HtmlUnitDriver也有同樣的問題,可以說使用WebDriver來輔助爬去動態生成的網頁所得到的結果是很不穩定的。這一點我是深有體會,使用IEDriver的時候,同一個頁面兩次爬取的結果會出現不一樣,而且甚至有時候IE直接掛掉,你說這樣的東西你們敢用在爬蟲程序中嗎?我是不敢的。
另外還有就是有人推薦使用HttpUnit,其實WebDirver中HtmlUnitDriver在內部使用的就是httpUnit,所以使用HttpUnit也會遇到同樣的問題,我也做了實驗,確實是這樣。通過Thread.sleep(2000)來等待js的解析完成,我覺得不可取的辦法。不確定性太大了,特別是在大型的抓取工作中。
總結一下,WebDriver是為測試而設計的框架,雖然按照其原理理論上可以用來輔助爬蟲獲取包含有動態內容的html頁面,但是在實際的應用中是不取的,不確定性太大了,穩定性太差,速度太慢,我們還是讓框架各盡其值吧,不要折煞了他們的優點。
我的工作沒有完成,所以繼續去網上需找辦法,這次找到了一個穩定的,確定性高的輔助工具---phantomjs,目前我還不完全了解這個東西。但是目前已經用它來實現了我想要的功能。在java中通過runtime.exec(arg)來調用phantomjs得到解析js后的頁面。我還是把代碼貼出來吧
phantomjs端要執行的代碼
system = require('system') address = system.args[1];//獲得命令行第二個參數 接下來會用到 //console.log('Loading a web page'); var page = require('webpage').create(); var url = address; //console.log(url); page.open(url, function (status) { //Page is loaded! if (status !== 'success') { console.log('Unable to post!'); } else { //此處的打印,是將結果一流的形式output到java中,java通過InputStream可以獲取該輸出內容 console.log(page.content); } phantom.exit(); });
java端執行的代碼
public void getParseredHtml(){ String url = "www.bai.com"; Runtime runtime = Runtime.getRuntime(); runtime.exec("F:/phantomjs/phantomjs/phantomjs.exe F:/js/parser.js "+url); InputStream in = runtime.getInputStream(); //后面的代碼省略,得到了InputStream就好說了 }
這樣的話在java端就可以獲得解析完成后的html頁面了,而不是像WebDriver中需要使用Thread.sleep()這樣的不確定性的代碼來獲取可能完成的代碼。有一點需要說明:在phantomjs端的js代碼千萬不要要語法錯誤,否則js代碼編譯不同的話,java端就一直等待着,並不會拋異常。再就是由於在使用phantomjs.exe的時候,java端每次都要去開啟一個phantomjs進程,時間上消耗還是比較大的。但是最起碼來說結果是穩定的。當然最后我還沒有使用phantomjs,我直接download需要的數據,並沒有去抓取整個完整的頁面,主要是速度方面的問題(其實,我不敢用是因為phantomjs不熟悉,所以我慎用)。
折騰了幾天,雖然沒有解決我的問題,但是見識長了不少,后期的工作是熟悉phantomjs,看能不能再速度方面提升,要是能打破速度的框框,以后再爬去網頁的時候就得心應手了,再者就是Nutch這個框架,我佩服着哥們在使用的時候方便性,所有后期很有必要研究下如何優化Nutch在Hadoop上的抓取速度,另外,Nutch原始的功能中也不會抓取動態生成的頁面內容,但是可以使用Nutch和WebDirver結合,說不定抓取的結果穩定了,哈哈,這些只是構想,但是不嘗試怎么知道呢?
如果園友對於使用WebDriver輔助爬蟲所得到的結果的穩定性方面有要說的,歡迎各位啊,因為我確實沒有找相關的資料來穩定爬去結果。