淺談網絡爬蟲爬js動態加載網頁


由於別的項目組在做輿情的預言項目,我手頭正好沒有什么項目,突然心血來潮想研究一下爬蟲、分析的簡單原型。網上查查這方面的資料還真是多,眼睛都看花了。搜了搜對於我這種新手來說,想做一個簡單的爬蟲程序,所以HttpClient + jsoup是一個不錯的選擇。前者用來管理請求,后者用來解析頁面,主要是后者的select語法很像jquery,對於用js的我來說方便太多了。

  昨天和他們聊天的時候,他們選用了幾個著名的開源框架使用,聊着聊着就發現原來他們目前還沒有辦法抓取動態的網頁,尤其是幾個重要的數字,例如評論數,回帖數等等。大致了解了一下,例如TRS的爬蟲,對於js的調用,需要寫js腳本,但是分析量巨大,他們的技術人員告訴我們,類似這樣的模板他們配的話,一天也就只能配2到3個,更不要說我們這些半路出家的。正好覺得頗有挑戰,所以昨天答應了他們看看能不能找到一個相對來說簡單的解決辦法,當然,先不考慮效率。

  舉一個簡單的例子,如下圖

  “我有話說”后的1307就是后加載的,但是往往這些數字對於輿情分析來說還是比較重要的。

  大致了解了需求,就分析一下如何解決。通常,我們的一次請求,得到的回應是包含js代碼和html元素的,所以對於jsoup這樣的html解析器在這里就難以發揮優勢,因為它所能拿到的html,1307還沒有生成。這個時候就需要一個可運行js的平台,將運行過js代碼的后的頁面,交由html解析,這樣才能正確獲得結果。

  由於我比較偷懶,寫腳本的方式一開始就被我拋棄了,因為分析一個頁面太痛苦了,代碼亂成一鍋粥,好多還采用壓縮的辦法,滿眼都是a(),b()的方法,看的太累了。所以我最優先想到的是,為什么我不能讓這個地址在某個瀏覽器中運行,然后將運行的結果交給html解析器去解析,那么整個問題不就迎刃而解了嗎。這樣我暫時的解決方案就是在爬蟲服務端,開一個后台的瀏覽器,或者是有瀏覽器內核的程序,將url地址交給它去請求,然后從瀏覽器中將頁面的元素取出,交給html解析器去解析,從而獲取自己想要的信息。

 

沒錯,最后我還是使用了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

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

 

上一篇討論了web driver對動態網頁的抓取與分析,可以很清楚的看出這是一種集中式處理方式,簡單說,就是利用服務器,打開一個真正的brower,然后將需要解析的地址交給瀏覽器,瀏覽器去解析,然后將結果返回。這樣正如網友評論一樣,效率上不好,其實我想說的是,如果質提不上去,可以采用量的方式,比如開多線程處理,多開幾台機器處理,雖然單個不快,量多后,處理速度就上去了。當然這也不是什么特別好的方法。

  先談談他的不好之處:

  首先,依賴瀏覽器的驅動,無論使用ie,firefox,chrome,都需要啟動其driver,才能進行操作。

  其次,對響應結果控制力度不夠自由,比如有些網頁我需要一個東西,有些網頁我需要另一個東西,那么我希望是,可以對響應結果進行解析和過濾。

  最后,他是一個集中式處理的方式,現在啥都講求分布式,當然不能為了分布而分布,不過在利用brower的資源時,分布是一個好的解決方法。

 

  那么,我還能做什么了,今天和同事聊的時候突然想到一個方案,也許會有一些幫助。當然,目前只是一個想法,不過后續我會去試驗可行性,我相信估計也有人這么試過。讓我們重新回歸原點,從上一篇的解決方案中可以看出,其實我們采用的基本是一種類似黑盒的測試方式,也就是說,我們根本沒有分析這些動態網頁的構成,只是單純的將網頁讓瀏覽器去解析,然后我們取得結果,(當然這里先不考慮一些復雜的ajax請求)。這里有兩個要素,一就是瀏覽器,使用的web driver;二就是瀏覽器去解析,並做了請求和響應。

  為什么我不能采用分布式處理呢?利用客戶端的資源來解析動態網頁,這樣不就減輕了壓力,而且可以大大增加處理地址的能力。我的想法是這樣的:

  1、開發一個簡單的網頁,用來訪問。例如一個jsp

  2、將需要進行解析的地址通過參數的形式傳給這個頁面。例如采用xxx.jsp?url='www.sina.com.cn'

  3、后台截獲這個req,然后根據新的url也就是"http://www.sina.com.cn",發起一個新的httprequest,將這個response,write給前面這個res.

  這樣其實就是將sina的respose,交給了我這個jsp。

  4、這樣,我就可以做一些手腳,例如獲取response的時候,采用html解析器,並利用規則過濾掉一些元素或者添加一些我們需要的腳本代碼,並將這個修改后的response,交個瀏覽器去執行。最后獲取執行后的結果,再交給服務器處理或者保存。

  這樣就避免掉一開始的一些問題。首先,與瀏覽器driver無關,也就是說如果用ie訪問,就利用ie引擎;用firefox訪問,就利用firefox引擎。就可以脫離web driver。其次,可以對結果進行自由控制,采用html解析器,就可以按照自己的規則來過濾響應。最后,利用這種方式就是一種分布式的處理,也就是說凡是訪問我頁面的瀏覽器,都可以用來進行頁面解析,而且可以不占用我服務器的帶寬。

  可見的問題在於:

  1、如何控制客戶端去訪問我想要的網址。

  2、如何將訪問后的內容傳回后台處理。

  3、由於篡改了響應,帶來的Cross Domain的問題會不會影響到我的解析結果。

  4、如何知道頁面已經加載完畢。

  5、采用何種解析器解析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM