隨著網頁技術的發展,動態網頁的比例越來越高,原來抓取靜態網頁的許多方法變得不再適用;再加上越來越多的網站添加了各種複雜的反爬蟲策略,導致直接通過網路請求的方式去抓取頁面的方式已經有些落伍了。

而Selenium可以通過模擬瀏覽器的真實行為來訪問網頁並將頁面源碼緩存下來,從而實現所見即所得的效果。Selenium本身廣泛應用於測試領域,但是它所見即所得的特性基本上滿足了我們抓取絕大多數頁面的需求,因此今天我們就看一下如何通過Selenium訪問頁面並通過不同的方式定位到我們需要的元素,從而完成頁面抓取。

我們以今日頭條的官網首頁(toutiao.com)來進行演示。

一、通過ID定位

首先我們看下如何通過元素id來定位。在頭條首頁,有一個右邊欄,通過檢查頁面元素我們發現,這是一個id="rightModule"<div>標籤,那麼我們就通過這個ID來定位到這個元素,並且列印出該元素的class屬性。

# -*- coding:utf-8 -*-
from selenium import webdriver

if __name__ == __main__:
driver = webdriver.Chrome()
driver.get(https://www.toutiao.com)
element_class = driver.find_element_by_id(rightModule).get_attribute(class)
print(element_class)

輸出為:

bui-right index-right-bar

在這裡,我們先通過driver = webdriver.Chrome()啟動一個Chrome Driver,這裡需要保證我們的環境變數目錄中已經包含了與我們的Chrome瀏覽器版本對應的chromedriver,大家可以自行搜索下載並安裝。

然後我們通過driver.get(https://www.toutiao.com來控制瀏覽器打開頭條的首頁;接下來,find_element_by_id(rightModule)的作用就是在頁面源碼中定位到包含了id="rightModule"屬性的元素,也就是上邊提到的右邊欄對應的元素;最後我們使用get_attribute(class)方法,取出該元素class屬性的值。

很簡單,不是嗎?

另外提一句,所有的find_element_by_XXX方法都會返回定位到的第一個元素,加個s後,即所有的find_elements_by_XXX方法則會以列表形式返回符合條件的所有元素。比如上邊這個例子,如果我們改成driver.find_elements_by_id(rightModule)則會以列表形式返回所有符合條件的元素(當然,在這個例子中,事實上列表的長度也只有1)。

二、通過name定位

ID類似,selenium提供了find_element_by_namefind_elements_by_name方法來通過name屬性定位元素。

頭條首頁右側,有一個淘寶廣告的模塊,裡邊有一些輪播圖,接下來我們就通過name屬性定位到它,並列印出它的ad_name屬性的值。

element_ad_name = driver.find_element_by_name(home_right*top_1)
.get_attribute(ad_name)
print(element_ad_name)

輸出為:

h_300*250_TB_314

使用方法和find_element_by_id如出一轍。

三、通過class定位

聰明如你可能會猜測,通過class定位元素的方法應該是find_element_by_classfind_elements_by_class,那麼,「恭喜你,答錯了!」(這一句是我上學的時候最痛恨的被老師嘲諷的話,今天說出來給別人,感覺很爽!)

事實上,這兩個方法應該是find_element_by_class_namefind_elements_by_class_name,比我們猜測的多了個後綴(是的,最開始我也猜錯了),他們會定位到class取值包含某個字元串的所有元素。

我們注意到頭條首頁左側有一個邊欄,列出了一些頻道,那麼我們如何定位並列印出這些頻道名稱呢?

觀察頁面源碼,我們發現頻道名稱隱藏在一個<span>標籤中,而這個<span>標籤,有一個class="channel-item"<a>父標籤。同時我們也注意到,第一個頻道——「推薦」的標籤屬性與其他的頻道標籤屬性不同,它的class取值為channel-item active

channel_element_list = driver.find_elements_by_class_name(channel-item)
channel_list = [x.text for x in channel_element_list]
print(channel_list)

輸出為:

[推薦, 陽光寬頻, 熱點, 圖片, 科技, 娛樂, 遊戲, 體育, 汽車, 財經, 搞笑, 更多, , , , , , , , , , , ]

我們看到除了頁面上看到的頻道以外,還有一些空的字元串。這是因為「更多」這裡在懸浮的時候會產生更多的可選頻道,我們在沒有進行懸浮操作的時候,暫時看不到這些頻道。這個問題以後我們可以通過一系列的動作等方式來解決。

四、通過tag名稱定位

通過標籤名稱定位的方法為find_element_by_tag_namefind_elements_by_tag_name,這個方法可以直接選擇標籤。

仍以上例來說明,我們看到,上邊的例子中,我們直接對選擇到的<a>標籤選擇了它的text屬性,並獲取了文本,但事實上文本存在於<a>的子標籤<span>中。那麼我們為什麼能成功呢?這是因為.text屬性查詢的是所有子孫節點中的文本。

考慮另一種情況,假如上述的<a>標籤有多個子孫標籤,且都有不同的文本,而我們只要想<span>標籤中的文本時,應該如何操作?

text = driver.find_elements_by_class_name(channel-item)[5].find_element_by_tag_name(span).text
print(text)

輸出為:

娛樂

可以看到,我們在定位到<a>標籤後,選擇了它的子標籤<span>並成功列印出對應的文本。

五、通過XPath定位

這是一大利器,XPath在定位頁面元素上的強大不容置疑,不瞭解XPath的同學可以閱讀我的另一篇文章:《Python3使用Xpath解析網易雲音樂歌手頁面》。這篇文章通過實戰演練,講解了如何快速上手XPath並解析網易雲音樂的歌手頁面。

selenium中提供了對XPath的支持,我們可以通過find_element_by_xpathfind_elements_by_xpath來靈活地運用XPath進行元素定位。

頭條首頁中間,有一些內容列表,那我們就看看如何獲取這些內容的標題。

觀察頁面源碼,可以看到這些內容都藏在一個class="title-box"<div>標籤的子標籤<a>中。

title_elements = driver.find_elements_by_xpath(//div[@class="title-box"]/a)
titles = [x.text for x in title_elements]
print(titles)

輸出為(輸出較長,故隱藏部分內容):

[中共中央政治局召開會議 習近平主持, 五次出席G20峯會,習近平提出哪些「中國主張」?, ...]

事實上,XPath是支持直接獲取文本列表的,但是目前seleniumXPath的支持還不夠豐富。想要體驗更強大的XPath的朋友可以讀一下上邊提到的那篇文章,十分鐘即可入門。

其他定位方式

目前selenium支持的元素定位方式還有三種: - CSS選擇器:find_element_by_css_selectorfind_elements_by_css_selector,支持以層疊樣式表(CSS)的方式定位元素; - Link:find_element_by_link_textfind_elements_by_link_text,支持通過鏈接文本來定位元素; - Partial Link:find_element_by_partial_link_textfind_elements_by_partial_link_text,支持通過鏈接文本的一部分來定位元素。

不過這三種我自己用的比較少,它們的使用也非常簡單,有CSS基礎或者對這三種方法感興趣的朋友可以自行檢索資料學習。事實上,上述的五種方法已經足夠我們選取任何頁面元素了。


推薦閱讀:
相關文章