Selenium元素定位初探(以今日頭條首頁為例)
隨著網頁技術的發展,動態網頁的比例越來越高,原來抓取靜態網頁的許多方法變得不再適用;再加上越來越多的網站添加了各種複雜的反爬蟲策略,導致直接通過網路請求的方式去抓取頁面的方式已經有些落伍了。
而Selenium可以通過模擬瀏覽器的真實行為來訪問網頁並將頁面原始碼快取下來,從而實現所見即所得的效果。Selenium本身廣泛應用於測試領域,但是它所見即所得的特性基本上滿足了我們抓取絕大多數頁面的需求,因此今天我們就看一下如何通過Selenium訪問頁面並通過不同的方式定位到我們需要的元素,從而完成頁面抓取。
我們以今日頭條的官網首頁(https://www.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_name
和find_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_class
和find_elements_by_class
,那麼,“恭喜你,答錯了!”(這一句是我上學的時候最痛恨的被老師嘲諷的話,今天說出來給別人,感覺很爽!)
事實上,這兩個方法應該是find_element_by_class_name
和find_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_name
和find_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_xpath
和find_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
是支援直接獲取文字列表的,但是目前selenium
對XPath
的支援還不夠豐富。想要體驗更強大的XPath
的朋友可以讀一下上邊提到的那篇文章,十分鐘即可入門。
其他定位方式
目前selenium
支援的元素定位方式還有三種:
- CSS選擇器:
find_element_by_css_selector
和find_elements_by_css_selector
,支援以層疊樣式表(CSS)的方式定位元素; - Link:
find_element_by_link_text
和find_elements_by_link_text
,支援通過連結文字來定位元素; - Partial Link:
find_element_by_partial_link_text
和find_elements_by_partial_link_text
,支援通過連結文字的一部分來定位元素。
不過這三種我自己用的比較少,它們的使用也非常簡單,有CSS基礎或者對這三種方法感興趣的朋友可以自行檢索資料學習。事實上,上述的五種方法已經足夠我們選取任何頁面元素了。