1. 程式人生 > >java抓取動態生成的網頁

java抓取動態生成的網頁

1.WebDriver 和htmlunit

沒錯,最後我還是使用了Selenium,去實現上一篇我所說的問題,別的沒有試,只試了一下firefox的引擎,總體效果對我來說還是可以接受的。

繼續昨天的話題,既然要實現上篇所說的問題,那麼就需要一個可以執行js程式碼的框架。我首先選擇的是htmlunit,先簡單介紹一下htmlunit。下面一段摘自網路。

htmlunit 是一款開源的 java 頁面分析工具,啟動 htmlunit 之後,底層會啟動一個無介面瀏覽器,使用者可以指定瀏覽器型別:firefox、ie 等,如果不指定,預設採用 INTERNET_EXPLORER_7: 
WebClient webClient = new WebClient(BrowserVersion.FIREFOX_3_6); 
通過簡單的呼叫: 
HtmlPage page = webClient.getPage(url); 
即可得到頁面的 HtmlPage 表示,然後通過: 
InputStream is = targetPage.getWebResponse().getContentAsStream() 
即可得到頁面的輸入流,從而得到頁面的原始碼,這對做網路爬蟲的專案來說,很有用。 
當然,也可以從 page 中得更多的頁面元素。 
很重要的一點是,HtmlUnit 提供對執行 javascript 的支援: 
page.executeJavaScript(javascript) 
執行 js 之後,返回一個 ScriptResult 物件,通過該物件可以拿到執行 js 之後的頁面等資訊。預設情況下,內部瀏覽器在執行 js 之後,將做頁面跳轉,跳轉到執行 js 之後生成的新頁面,如果執行 js 失敗,將不執行頁面跳轉。

最後可以取得page.executeJavaScript(javascript).getNewPage(),獲取執行後的頁面。換句話說,javascript需要在這裡人為的執行,顯然與我的初衷不符,另外可能是我水平太差,在抓取sina新聞的頁面時總是出錯,暫時還沒發現錯誤在何處,但按照網路上查詢的結果來分析,極有可能錯誤的原因是在於htmlunit執行某些帶引數的請求時,由於引數的順序或者編碼問題會導致請求失敗而報錯。關鍵是,執行後並沒有得到我需要的結果。

那麼就另尋解決辦法,這個時候就找到了 Selenium WebDriver ,他是我需要的一個解決方案。

參考了資料和例子,就可以開始使用他了。例項程式碼如下。

 1        File pathToBinary = new File("D:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe");
 2         FirefoxBinary ffBinary = new FirefoxBinary(pathToBinary);
 3         FirefoxProfile firefoxProfile = new FirefoxProfile();
 4         FirefoxDriver driver = new FirefoxDriver(ffBinary,firefoxProfile);
5 6 7 driver.get("http://cq.qq.com/baoliao/detail.htm?294064"); 8 9 ArrayList list = new ArrayList(); 10 list.add("http://www.sina.com.cn"); 11 list.add("http://www.sohu.com"); 12 list.add("http://www.163.com"); 13 list.add("http://www.qq.com"); 14 15 long start,end; 16 17 for(int i=0;i<list.size();i++){ 18 start = System.currentTimeMillis(); 19 driver.get(list.get(i).toString()); 20 end = System.currentTimeMillis(); 21 System.out.println(list.get(i).toString() + ":" + (end - start)); 22 } 23 24 driver.close();

使用了firefox的引擎,得到的結果如下,而且確實滿足了我的要求。

http://www.sina.com.cn:6638 
http://www.sohu.com:5796 
http://www.163.com:7567 
http://www.qq.com:9384

可以看見如上的結果時間還是蠻長的,那如何加快速度呢。其實仔細考慮一下,為什麼他要這麼久,就是因為他在下載網頁元素,我們請求一個網站的時候是發起一個req,得到一個res,而res中是隻有元素沒有內容的,換句話說,他不用執行css,js,不用下載圖片,flash,載入廣告等等。而如果我們需要加快效率,那就需要移除一切與我分析無關的東西,那麼仿照瀏覽器一樣,我們需要遮蔽掉css,圖片,flash等等,從而加速網頁的速度,更關心其中的內容。

簡單方法如下:

1 //去掉css        
  firefoxProfile.setPreference("permissions.default.stylesheet", 2);
2 //去掉圖片    
3  firefoxProfile.setPreference("permissions.default.image", 2);
4 //去掉flash        
  firefoxProfile.setPreference("dom.ipc.plugins.enabled.libflashplayer.so",false);

那麼在去除掉所有firefox快取後,再次執行一下,會有什麼結果呢。結果如下

http://www.sina.com.cn:5085 
http://www.sohu.com:3520 
http://www.163.com:3329 
http://www.qq.com:2048

發現確實快了很多。上面只是一個大致的原型,如果真正的要用,還需要封裝。


2.phantomjs

最近在做專案的時候有一個需求:從網頁面抓取資料,要求是首先抓取整個網頁的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輔助爬蟲所得到的結果的穩定性方面有要說的,歡迎各位啊,因為我確實沒有找相關的資料來穩定爬去結果。