0x00
近寫爬蟲分析灰色網站,要使用無頭瀏覽器動態加載網頁,使用selenium+PhantomJS, 自己研究的時候遇到了一些比較有意思的坑,和大家分享一下。
0x01
先說一下架構,在大規模爬取網頁內容的時候,為了提高性能,降低存儲和計算開銷,單個PhantomJS進程往往需要連續處理大量的URL,那么針對單個PhantomJS進程在連續處理不同URL的時候,往往會出現一些意想不到的問題。
例如,由于我們需要通過
from selenium import webdriver
d = webdriver.PhantomJS()
d.set_page_load_timeout('10')
d.implicitly_wait('10')
d.get(url)
d.current_url
這樣的方式記錄給予Phantomjs引擎的原始URL和PhantomJS動態加載后的URL,但是當單進程PhantomJS大量處理URL時,我們發現有許多原始URL和動態加載后的URL完全無法對應。這會是什么原因導致的呢?
目前遇到的情況而言,大體分為兩種情況導致:
網頁內部存在onbeforeunload事件調用使得網頁無法被正常關閉。
待訪問資源不可用。
0x02 onbeforeunload
我們來看一個比較有意思的網頁,在爬取到該網頁所在的URL之后,phantomjs”停止了工作“, 所有后續給phantomjs處理的URL 其調用current_url返回的URL值都是這個特殊網頁的URL,以下是該網頁內含部分HTML代碼:
<BODY onbeforeunload="return('你確定仔細閱讀此文章了嗎?')" style="margin:0px;"><iframe border="0" name="lantk" width="0" height="0" allowtransparency="" scrollbars="yes" frameborder="0"></iframe>
形如 <ELEMENT onbeforeunload="handler"> 的代碼會注冊一個事件,當瀏覽器引擎卸載當前HTML文檔之前拋出一個對話框,用戶可以確認是否他要離開這個網頁。 對應在Javascript中,可相應的對該事件進行處理:
object.onbeforeunload = handler;
object.addEventListener("beforeunload", handler, useCapture);
事件是DOM事件的簡稱,根據w3.org DOM的文檔 1.4.2 Complete list of event types:unload事件類型的實現將從環境中移除該網頁文檔自身和其附帶的所有資源,包括圖片、CSS、和Javascript腳本。在運行這樣的事件之后文檔將被卸載。
那DOM的設計者們設計onbeforeunload事件的本意是什么?onbeforeunload存在的意義在于,你無法通過javascript腳本形式對其進行處理,如果用戶想要離開某個網頁,那么你無法阻止他離開這個網頁,你也無法在用戶點擊關閉網頁(觸發unload事件時)進行其他阻止用戶離開的活動,為了安全性考慮,用戶的自由是第一位的,網頁編寫者無法將用戶”囚禁“在網頁應用程序中。
在IE瀏覽器中,可以通過自定義字段來建立自定義消息,如上述代碼中的 return(‘你確定仔細閱讀此文章了嗎?' ,而在其他瀏覽器中,這個事件不會顯示自定義的消息。
那么在我們的案例中,原因在于phantomjs接收到URL并加載了網頁DOM之后,受困于onbeforeunload事件,導致代碼中沒有拋出異常,然而引擎也永遠停留在了當前頁面,對于后續接收到的任務不予處理。
URL無法訪問的其他情況
除了上述的情況導致phantomjs異常以外,還有一種由于URL無法訪問導致current_url異常的情況,這種情況可以在如下三種子條件下觸發:
DNS查詢返回域名不存在,無論是本地hosts還是遠程DNS服務器的返回都可以。
URL的無法返回正常HTTP響應。
單獨通過phantomjs訪問上述兩種情況的URL,current_url將會返回 about:blank , 然而如果同一phantomjs進程曾經處理過其他URL, 則由于上述兩種情況,phantomjs driver沒有真正去處理第二個URL。
我們可以把phantomjs的狀態簡單的視為一個狀態機模型,由于在處理一系列URL時候狀態是連續傳遞下去的,通過輸入,會改變狀態機的狀態,而滿足上述任意一個條件的輸入,將導致phantomjs狀態停留在原位,當用戶簡單將輸入和輸出進行對應的時候會出現問題。
0x03 解決方案
針對onbeforeunload的問題,可以在獲取網頁源碼后加入對使用onbeforeunload的網頁使用phantomjs自身對網頁中注入js腳本進行處理。然而這樣會造成一定的性能下降。
針對URL無法訪問的情況,有兩種方案: 1.可以在phantomjs處理每個任務之前先進行DNS查詢,對于無DNS解析記錄的可以不用提交phantomjs處理。然而這種方案對于IP類URL沒有很好的控制。 2。可以在phantomjs每個任務之間插入 driver.get('http://about:blank') 這樣可以間接避免上一個URL的狀態污染到下一個任務。
然而,這兩種方法都是治標不治本,根本解決方案應該是將每phantomjs會話之間的執行完全隔離,我們使用selenium連接phantomjs后端其實使用的是detro開發的ghostdriver,是一個Remote WebDriver Wire protocol的Phantomjs實現,根據 Github上的鏈接 ,以及作者ghostdriver庫github頁首的簡介,項目開發者由于生活的壓力已經兩年沒有更新了。。你們說Github是不是應該開一個打賞機制?