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