python網絡數據採集-Ajax和動態HTML

第一部分:基本概念

       到目前爲止,我們與網站服務器的唯一通訊方式,就是發出HTTP請求獲取新頁面。如果提交表單之後,或從服務器獲取信息之後,網站的頁面不需要重新刷新,那麼你訪問的網站就在使用Ajax技術。

       與一些人的印象不大一樣,Ajax其實並不是一門語言,而是用來完成網絡任務(可以認爲它與網絡數據採集差不多)的一系列技術。Ajax的全稱爲Asynchronous JavaScript and XML(異步JavaScript和XML),網站不需要使用單獨的頁面請求就可以與網絡服務器進行交互(收發信息)。需要注意的是:你不應該說“這個網站時Ajax寫的”。正確的說法應該是“這個表單用Ajax與網絡服務器通信”。

       和Ajax一樣,動態HTML(dynamic HTML,DHTML)也是一系列用於解決網絡問題的技術集合。DHTML使用客戶端語言改變頁面的HTML元素(HTML,CSS,或者二者皆被改變)。比如,頁面上的按鈕只有當用戶移動鼠標之後纔會出現,背景色可能每次點擊都會改變,或者用一個Ajax請求觸發頁面加載一段新內容。

       有時你還會發現,網頁用一個加載頁面把你引到另一個頁面上,但是網頁的URL鏈接在這個過程中一直沒有變化。如果你採集過很多網站,很有可能遇到這樣一種情況。你在瀏覽器上看到的內容,與你用爬蟲從網站上採集的內容不一樣。你可能會懷疑自己是不是哪個細節沒處理好,希望找出內容採集不出來的原因。這些都是因爲你的爬蟲不能執行那些讓頁面產生各種神奇效果的JavaScript代碼。如果網站的HTML頁面沒有運行JavaScript,就可能和你的瀏覽器裏看到的樣子完全不同,因爲瀏覽器可以正確執行JavaScript。

       那些使用了Ajax或者DHTML技術改變/加載內容的頁面,可能有一些採集手段,但是用Python解決了這個問題只有兩種途徑:直接從JavaScript代碼裏採集內容,或者用Python的第三方庫運行JavaScript,直接採集你在瀏覽器裏看到的內容

第二部分: 在Python中用Selenium執行JavaScript

       Selenium是一個強大的網絡數據採集工具,其最初是爲網站自動化測試而開發的。近幾年,它還被廣泛用於獲取精確的網站快照,因爲它們可以直接運行在瀏覽器上。Selenium可以讓瀏覽器自動加載頁面,獲取需要的數據,設置頁面截屏,或者判斷網站上某些動作是否發生。  

       Selenium自己不帶瀏覽器,它需要與第三方瀏覽器結合在一起使用。例如,如果在FireFox上運行Selenium,可以直接看到一個FireFox窗口被打開,進入網站,然後執行你代碼中設置的動作。雖然這樣可以看得很清楚,但是我更喜歡後臺運行,所以我這邊使用一個PhantomJS的工具替代真實的瀏覽器。

      PhantomJS是一個“無頭”(headless)瀏覽器。它會把網站將在到內存並執行頁面上的JavaScript,但是他不會向用戶展示圖形界面。把Selenium和PhantomJS結合在一起,就可以運行一個非常強大的網絡爬蟲了,可以處理cookie、JavaScript,header,以及任何你需要做的事情。

     你可以通過PyPI網站(https://pypi.python.org/simple/selenium)下載Selenium庫,也可以用第三方管理器如pip直接安裝。PhantomJS也可以從他的官網下載(http://phantomjs.org/download.html),因爲PhantomJS是一個功能完善(雖然無頭)的瀏覽器,並非一個python庫,所以它不需要像Python的其他庫一樣安裝,也不能用pip安裝。

      雖然很多頁面都用Ajax加載數據(尤其是Google),在http://pythonscraping.com/pages/javascript/ajaxDemo.html建了一個簡單的頁面來運行爬蟲。這個頁面有一些簡單的文字,都是手工敲在HTML代碼裏的,打開頁面兩秒鐘後,頁面就會被替換成一個Ajax生成的內容。如果我們用傳統的方法採集這個頁面,只能獲取加載前的界面,而我們真正需要的信息(Ajax執行後的頁面)卻抓不到。

      Selenium庫是一個在WebDriver上調用的API。WebDriver有點像可以將在網站的瀏覽器,但是它也可以像BeautifulSoup對象一樣用來查找頁面元素,與頁面上的元素進行交互(發送文本、點擊等),以及執行其他動作來運行網絡爬蟲。

     下面的代碼可以獲取前面測試頁面上Ajax“牆”後面的內容:

from selenium import webdriver
import time
driver=webdriver.PhantomJS(executable_path='')
driver.get("http://pythonscraping.com/pages/javascript/ajaxDemo.html")
time.sleep(3)
print(driver.find_element_by_id('context').text)
driver.close()
     這段代碼用PhantomJS庫創建了一個新的Selenium WebDriver,首先用WebDriver加載頁面,然後暫停執行三秒,再查看頁面獲取(希望已加載完成)的內容。

     依據你的PhantomJS安裝位置,在創建新的PhantomJS WebDriver的時候,你需要在Selenium的WebDriver接入點指明PhantomJS可執行文件的路徑:

driver=webdriver.PhantomJS(executable_path='/usr/bin/phantomjs')
執行結果如下:

與瀏覽器查看到的界面一致。雖然頁面裏只有一個元素是HTML按鈕,但是Selenium的.text函數可以獲取按鈕的文本內容,就像它獲取頁面上其他元素內容的方式一樣。

      如果將time.sleep(3)修改爲time.sleep(1),暫停時間由3秒變爲1秒,那麼上面程序採集的文本就會變成:


       雖然這個方法奏效了,但是效率還不高,在處理規模較大的網站時還是可能會出現問題予以,頁面的加載時間是不確定的,具體依賴於服務器某一毫秒的負載情況,以及不斷變化的網速。雖然這個頁面加載可能只需要花兩秒多的時間,但是我們設置了三秒的等待時間以確保頁面完全加載成功。一種更有效的方法時,讓Selenium不斷檢查某個元素是否存在,以確定頁面是否已經完全加載,如果頁面加載成功就執行後面的程序。

     下面的程序用id是loadedButton的按鈕檢查頁面是不是已經完全加載:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver=webdriver.PhantomJS(executable_path='/usr/bin/phantomjs')
driver.get("http://pythonscraping.com/pages/javascript/ajaxDemo.html")
try:
    element=WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID,"loadedButton")))
finally:
    print(driver.find_element_by_id("content").text)
    driver.close()
執行結果如下圖:


從執行結果分析來看,上面的程序也實現了抓取JS執行後的代碼,但是該種方法更加智能,可以根據某個控件是否出現了來判斷頁面是否完全加載。這段程序裏導入了一些新的模塊,最需要注意的就是WebDriverWait和expected_conditions,這兩個模塊組合在一起就構成了Selenium的隱式等待(implicit wait)。

發佈了105 篇原創文章 · 獲贊 238 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章