盧守東 盧明俊


關鍵詞:數據采集;異步加載;Ajax;Selenium;Python
中圖分類號:TP311 文獻標識碼:A
文章編號:1009-3044(2024)03-0001-03
0 引言
大數據與人工智能等新一代信息技術的快速發展與廣泛應用,催生了對各類大量數據的強烈需求。而Web應用的持續發展與普遍應用,促使互聯網逐漸成為各類大量數據的發源地與匯集地。因此,只要編寫出相應的網絡爬蟲程序,即可從互聯網中的有關Web站點(即網站)自動采集到所需要的數據[1]。
要從網頁中采集數據,關鍵是要成功獲取包含有相應數據的網頁源代碼。然而,為了改善應用的性能,并提高用戶的體驗,目前許多Web 站點均采用Ajax技術以實現頁面數據的異步加載,其本質為Ja?vaScript動態渲染,即通過執行JavaScript腳本將異步請求返回的數據動態添加或更新到頁面中。對于此類Ajax頁面,若采用傳統的常規方法,只能獲取原始頁面源代碼,即數據異步加載之前的頁面的源代碼。在原始頁面源代碼中,并無加載完成后才呈現到頁面中的數據。
那么,如何才能順利地采集到Ajax頁面中的異步加載數據呢?在此,將以Windows 7+ Python 3.8.18+ Se?lenium 4.13.0為開發環境,介紹一種基于Selenium的適用于Ajax異步加載頁面的數據采集技術,供大家參考。
1 Selenium 簡介
Selenium是目前常用的一種開源、免費的Web應用程序自動化測試工具[2],具有多平臺(Linux、Win?dows、Macintosh等)、多瀏覽器(IE、Chrome、Firefox、Sa?fari、Opera、Edge等)與多語言(Python、Java、JavaScript、C++、C#、Ruby等)支持的特點。基于Selenium,可控制瀏覽器的運行,模擬用戶在瀏覽器中的實際操作,并獲取數據加載完畢后的頁面源代碼,因此可用于各種頁面(包括Ajax頁面)數據的采集。
Selenium 在Python 中是作為一個第三方庫提供的,其安裝方法很簡單,只須執行“pip install sele?nium”命令即可。為提高安裝的速度,可在執行該命令時通過選項“-i”指定相應的國內鏡像(如https://pypi.tuna.tsinghua.edu.cn/simple) [3]。
Selenium必須與瀏覽器結合方可使用,因此還要安裝相應的瀏覽器驅動。以Chrome為例,其驅動的下載地址為https://chromedriver.storage.googleapis.com/index. html。獲取與Chrome 版本相對應的驅動chromedriver.exe后,只須將其置于某一目錄,然后再將該目錄添加到環境變量Path中即可[4]。在此,建議將其放至Python的安裝目錄。
2 主要技術
2.1 瀏覽器的啟動與退出
為啟動Chrome瀏覽器,只須調用selenium庫web?driver模塊的Chrome()函數即可。該函數的返回值為一個瀏覽器對象。反之,通過調用瀏覽器對象的quit() 方法,即可退出瀏覽器。
2.2 網頁的訪問
以指定的網址作為參數調用瀏覽器對象的get() 方法,即可實現對相應網頁的訪問。反之,直接調用瀏覽器對象的close()方法,即可關閉當前頁面。成功打開網頁后,可通過瀏覽器對象的page_source屬性獲取當前頁面的源代碼。必要時,也可調用瀏覽器對象的maximize_window()或minimize_window()方法,以實現瀏覽器窗口的最大化或最小化。例如:
from selenium import webdriver
browser=webdriver.Chrome()
browser.maximize_window()
browser.get('https://fanyi.baidu.com')
print(browser.page_source)
browser.quit()
2.3 頁面元素的查找
頁面中的元素多種多樣。只有準確獲取到頁面中的有關元素,才能順利采集到其中所包含的數據。可喜的是,Selenium提供了一系列元素查找方法,可靈活實現頁面元素的獲取或定位。
例如,調用瀏覽器對象的find_element()方法,可根據元素的ID、Name、Xpath路徑、標簽名、鏈接文本、部分鏈接文本、CSS類名、CSS選擇器在當前頁面中查找元素,并返回相應的一個元素對象。若將該方法名中的element改為復數形式elements,則可查找到符合指定條件的所有元素,并返回相應的一個元素對象列表。需要注意的是,在調用這兩個方法前,應先從se?lenium.webdriver.common.by模塊導入By類,以便利用該類的相應屬性指定查找方式。By類的元素查找方式屬性共有8 個,分別為ID、NAME、XPATH、TAG_NAME、LINK_TEXT、PARTIAL_LINK_TEXT、CLASS_NAME與CSS_SELECTOR。
若所使用的Selenium版本較低,find_element()方法的功能還可分別通過相應的find_element_by_xxx() 方法實現(其中的xxx表示id、name、xpath、tag_name、link_text、partial_link_text、class_name或css_selector) 。若將此類方法名中的element改為elements,則可實現find_elements()方法的相應功能。
基于已獲取到的元素對象,必要時可進一步調用相應的元素查找方法以查找與其相關的其他元素,包括父元素、子元素、兄弟元素等。
2.4 元素的信息獲取
通常,獲取頁面的源代碼后,還須借助某個解析庫來提取其中的數據。但對于Selenium來說,無須額外解析庫的支持,也可順利獲取頁面中有關元素的信息。
在Selenium中,元素對象的類型為WebElement。對于元素對象,只須訪問其text屬性,即可獲取相應頁面元素的文本信息[5]。必要時,還可訪問其id、tag_name、location與size等屬性,以便獲取相應元素的ID、標簽名、位置與大小等信息。此外,根據屬性名調用元素對象的get_attribute()方法,即可獲取相應頁面元素指定屬性的值。
2.5 元素的交互操作
在瀏覽網頁的過程中,通常還需要執行相應的操作,如輸入內容、單擊鏈接、提交表單等。為模擬諸如此類的交互操作,Selenium針對元素對象也提供了相應的交互方法,包括send_keys()、click()、submit()等。其中,send_keys()方法可模擬向元素輸入指定的內容,click()可模擬單擊元素,submit()方法可模擬提交表單。
例如,在百度主頁的左上角有一個“新聞”鏈接,相應元素的Xpath路徑為“//*[@id="s-top-left"]/a[1]”。為獲取該鏈接的文本與目標地址,然后再單擊之,關鍵代碼如下:
browser.get('https://www.baidu.com')
news=browser. find_element(By. XPATH, '//*[@id="s-top-left"]/a[1]')
print(news.text)
print(news.get_attribute('href'))
news.click()
2.6 頁面的自動滾動
對于Ajax頁面來說,隨著頁面的滾動或下拉,才會有更多的數據動態加載并顯示出來。因此,如何實現頁面的自動滾動功能也是頗為關鍵的。
通常,頁面的自動滾動可通過執行相應的JavaS?cript腳本實現。為執行JavaScript腳本,在Selenium中只須調用瀏覽器對象的execute_script()方法即可[5]。例如,為將頁面右側滾動條的位置設定為0,從而將頁面滾動至最上方,代碼如下:
browser. execute_script("document. documentEle?ment.scrollTop=0") 在此,若增大scrollTop屬性的值,即可將頁面向下滾動至相應位置。
在Selenium中,還可以通過其他方式實現頁面的自動滾動。其中,最簡單易行的辦法就是模擬向頁面發送PageUp、PageDown、Home、End等按鍵。為此,應先獲取頁面的body元素,然后再調用其send_keys()方法。此外,為指定相應的按鍵,須從selenium.web?driver.common.keys模塊導入Keys類,該類的有關屬性代表的就是相應的按鍵。例如,為將頁面滾動至底部,關鍵代碼如下:
browser. find_element(By. TAG_NAME, 'body'). send_keys(Keys.END)
2.7 頁面的互相切換
有時瀏覽器會同時打開多個頁面,因此頁面之間的互相切換是不可避免的。在Selenium中,通過調用瀏覽器對象switch_to屬性的window ()方法,即可切換至指定的頁面窗口。為指定頁面窗口,可利用瀏覽器對象的window_handles屬性,該屬性將返回當前會話中所打開的所有頁面窗口所構成的一個列表[5]。例如:
from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.execute_script('window.open()')
browser.switch_to.window(browser.window_handles[1])
browser.get('http://www.hao123.com')
browser.switch_to.window(browser.window_handles[0])
browser.get('https://fanyi.baidu.com')
browser.quit()
3 Ajax 頁面數據采集實例——今日頭條財經類新聞標題及其鏈接地址的采集
今日頭條是一個典型的Ajax 網站,其網址為https://www.toutiao.com。在瀏覽器中打開今日頭條主頁(如圖1所示),并借助開發者工具進行分析,可知“財經”鏈接所對應的元素為:
其Xpath路徑為:
//*[@id= "root"]/div/div[5]/div[1]/div/div/div/div[1]/div/ul/li[5]/div/div
在今日頭條主頁中單擊“財經”鏈接,即可顯示相應的財經類新聞。若下拉頁面,將會有更多的新聞動態加載并顯示出來。經分析,可知各條新聞的有關信息均置于相應的div元素中,其父元素亦為一個div元素,Xpath 路徑為“//*[@id="root"]/div/div[5]/div[1]/div/div/div/div[2]”。而在各條新聞所對應的div元素中,新聞標題鏈接所對應的a元素的Xpath路徑為“./div/div[@class="feed-card-article-l"]/a”。
根據以上分析結果,即可編程實現今日頭條財經類新聞標題及其鏈接地址的采集,代碼如下:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys import time
browser=webdriver.Chrome()
browser.maximize_window()
browser.get("https://www.toutiao.com")
browser.implicitly_wait(10)
browser.find_element(By.XPATH,\
'//*[@id= "root"]/div/div[5]/div[1]/div/div/div/div[1]/div/ul/li[5]/div/div').click()
for i in range(5):
browser. find_element(By. TAG_NAME, 'body').send_keys(Keys.END)
time.sleep(5)
div_element0=browser.find_element(By.XPATH,\
'//*[@id="root"]/div/div[5]/div[1]/div/div/div/div[2]')
div_elements=div_element0. find_elements(By.XPATH,'./div')
for div_element in div_elements:
a_element=div_element.find_element(By.XPATH,\
'./div/div[@class="feed-card-article-l"]/a')
title=a_element.text
url=a_element.get_attribute('href')
with open('今日頭條財經新聞.txt','a',encoding='utf8') as f:
f.write(title+'|||'+url+'\n')
browser.quit()
運行該程序,所采集到的財經類新聞的標題與鏈接地址將以“|||”為分隔添加到文本文件“今日頭條財經新聞.txt”中,且每條新聞獨占一行,如圖2所示。
4 結束語
對于Ajax頁面的數據采集來說,如何獲取與異步加載完成后呈現在瀏覽器中的頁面相一致的包含有具體數據的源代碼(即當前頁面源代碼)是至關重要的。由于Selenium支持JavaScript動態渲染,能夠成功獲取瀏覽器當前所呈現的頁面的源代碼,同時提供相應的HTML解析功能,因此基于Selenium的Ajax頁面數據采集方案是切實可行、頗為通用的,可以實現“所見即可爬”。
【通聯編輯:謝媛媛】