目錄
引言
一、獲取節點信息
1.1 獲取屬性
1.2 獲取文本值
1.3 獲取ID、位置、標簽名、大小
二、切換Frame
三、延時等待
3.1 隱式等待
3.2 顯式等待
四、前進后退
五、Cookies
六、選項卡管理
七、異常處理
引言
??????? 這一節我們繼續講解Selenium的使用下篇,在利用Selenium進行網頁操作時,獲取節點信息以及應對各種相關場景是關鍵環節。很多人起初習慣通過獲取網頁源代碼,再用解析庫提取信息,但其實Selenium自身就蘊含著諸多更便捷的方式。接下來,我們就深入了解下如何借助Selenium直接獲取節點信息,以及處理Frame切換、等待、Cookies操作等一系列實用技巧。
一、獲取節點信息
????????前文提到,通過page_source屬性可獲取網頁源代碼,進而使用正則表達式、Beautiful Soup、pyquery等解析庫提取信息。
????????然而,既然Selenium已提供選擇節點的方法,且返回的是WebElement類型,那么它必然也具備直接提取節點屬性、文本等信息的方法和屬性。如此一來,我們便無需通過解析源代碼來獲取信息,操作更加便捷。
????????接下來,讓我們看看如何獲取節點信息。
1.1 獲取屬性
????????如果想要獲取網頁中某個節點的屬性值,我們可以運用`get_attribute()`方法來達成這一目的。不過需要注意的是,在使用這個方法之前,必須要先將目標節點選中才行。下面為你展示具體的示例:?
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChainsbrowser = webdriver.Chrome()
url = 'https://www.baidu.com/'
browser.get(url)# 舊版API
# logo = browser.find_element_by_id('zh-top-link-logo')
# 新版API
logo = browser.find_element(By.ID, 'lg')print(logo)
print(logo.get_attribute('class'))
當執行這段代碼時,程序將自動啟動瀏覽器并訪問百度頁面。隨后,程序會在頁面中定位到百度的logo節點,進而獲取并打印出該節點的相關信息,以及它的`class`屬性值。在控制臺中呈現的輸出結果如下:
????????使用`get_attribute()`方法時,只要在調用該方法的過程中傳入你期望獲取的節點屬性名稱,就可以順利地獲取到該屬性所對應的值。?
1.2 獲取文本值
????????對于每一個`WebElement`類型的節點而言,它們都具備`text`這一屬性。在實際操作中,我們僅需直接調用該屬性,就能夠獲取到這個節點內部所包含的文本信息。這種獲取文本信息的方式,和`Beautiful Soup`庫中的`get_text()`方法,以及`pyquery`庫中的`text()`方法的作用類似。下面給出具體的示例來進行說明:?
from selenium import webdriver
from selenium.webdriver.common.by import By
import timebrowser = webdriver.Chrome()
url = 'https://www.zhihu.com/signin?next=%2F'
browser.get(url)# 等待頁面加載
time.sleep(2)# 新版API
# 不能處理帶空格,對于包含多個類名的元素,我們應該使用 CSS 選擇器。
#button = browser.find_element(By.CLASS_NAME, '')# 新版API - 使用CSS選擇器
button = browser.find_element(By.CSS_SELECTOR, '.Button.SignFlow-submitButton')print(button.text)
????????這段代碼的執行邏輯是這樣的:首先,它會啟動并打開知乎的頁面。頁面加載完畢后,代碼會在頁面中找到“登錄注冊”按鈕對應的節點。在成功獲取該節點之后,代碼便會將這個節點的文本值打印輸出。
控制臺打印結果為:
登錄/注冊
1.3 獲取ID、位置、標簽名、大小
????????除了前面提到的屬性之外,`WebElement`節點還擁有一些其他非常實用的屬性。就像`id`屬性,通過它可以獲取到節點對應的`id`標識;`location`屬性則專門用來獲取該節點在網頁頁面中所處的相對位置;`tag_name`屬性能夠讓我們得知節點的標簽名稱;而`size`屬性可以獲取到節點的尺寸大小,也就是它的寬度和高度信息。下面通過具體的示例來展示這些屬性的使用方法:?
from selenium import webdriver
from selenium.webdriver.common.by import By
import timebrowser = webdriver.Chrome()
url = 'https://www.zhihu.com/signin?next=%2F'
browser.get(url)# 等待頁面加載
time.sleep(2)# 新版API
# 不能處理帶空格,對于包含多個類名的元素,我們應該使用 CSS 選擇器。
#button = browser.find_element(By.CLASS_NAME, '')# 新版API - 使用CSS選擇器
button = browser.find_element(By.CSS_SELECTOR, '.Button.SignFlow-submitButton')print(button.text)
print(button.id)
print(button.location)
print(button.tag_name)
print(button.size)
輸出結果:
????????上述代碼首先獲取“登錄注冊”按鈕這個節點,接著調用其`id`、`location`、`tag_name`、`size`屬性來獲取相應的屬性值。通過這些屬性,我們能更全面地了解節點在頁面中的特征和位置信息,有助于更精準地進行頁面元素的操作和分析 。
二、切換Frame
????????在網頁里,有一種節點叫做`iframe`,也就是子Frame。它就像是頁面里嵌套的子頁面,有著和外部網頁一樣的結構。當使用Selenium打開網頁時,默認是在父級Frame的環境下進行操作的。要是頁面中有子Frame,直接操作的話,是沒辦法獲取到子Frame里面的節點的。要是想操作子Frame里的內容,就得用`switch_to.frame()`方法來切換Frame。下面是具體的示例:?
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementExceptionbrowser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)browser.switch_to.frame('iframeResult')
try:# 舊版API# logo = browser.find_element_by_class_name('logo')# 新版APIlogo = browser.find_element(By.CLASS_NAME, 'logo')
except NoSuchElementException:print('NO LOGO')
browser.switch_to.parent_frame()# 舊版API
# logo = browser.find_element_by_class_name('logo')
# 新版API
logo = browser.find_element(By.CLASS_NAME, 'logo')print(logo)
print(logo.text)
控制臺輸出:
????????上述代碼以之前演示動作鏈操作的網頁作為示例。一開始,利用`switch_to.frame()`方法進入子Frame,接著嘗試查找子Frame里的`logo`節點。但實際上,子Frame中并沒有這個節點,這就會觸發`NoSuchElementException`異常。捕獲到這個異常后,程序會輸出`NO LOGO`。之后,使用`switch_to.parent_frame()`方法返回父級Frame,再次去獲取`logo`節點,這時就能夠成功獲取到該節點,并且打印出節點及其文本內容。?
????????由此可知,當網頁包含子Frame時,如果要獲取子Frame內的節點,一定要先使用`switch_to.frame()`方法切換到相應的Frame,之后才能繼續進行后續操作。不然,可能會因為找不到節點而致使操作失敗。?
三、延時等待
????????在Selenium里,`get()`方法會在網頁框架加載結束時執行完畢。不過,這時獲取的`page_source`,有可能不是瀏覽器完全加載好的頁面內容。要是頁面有額外的Ajax請求,僅靠網頁源代碼或許無法成功獲取相關數據。所以,得設置延時等待,保證所需的節點都已經加載出來。
????????延時等待的方式主要有兩種,分別是隱式等待和顯式等待。?
?
3.1 隱式等待
?
????????當運用隱式等待開展測試工作時,要是Selenium在文檔對象模型(DOM)里找不到指定的節點,它不會馬上停止,而是會繼續等待。要是超過了預先設定的時間,還是沒找到節點,就會拋出節點未找到的異常。簡單來講,在查找節點時,如果這個節點沒有立刻出現,隱式等待會讓程序等待一段時間之后,再去查找DOM中的節點。需要注意的是,隱式等待的默認等待時間是0。下面是具體的示例:?
from selenium import webdriver
from selenium.webdriver.common.by import By
import timebrowser = webdriver.Chrome()
browser.implicitly_wait(10)
url = 'https://www.zhihu.com/signin?next=%2F'
browser.get(url)# 新版API
# 不能處理帶空格,對于包含多個類名的元素,我們應該使用 CSS 選擇器。
#button = browser.find_element(By.CLASS_NAME, '')# 新版API - 使用CSS選擇器
button = browser.find_element(By.CSS_SELECTOR, '.Button.SignFlow-submitButton')print(button)
????????在上述代碼里,借助`implicitly_wait()`方法將隱式等待時間設定成了10秒。這意味著當Selenium查找節點卻未能立即找到時,會持續等待10秒,若10秒內節點加載出來則繼續執行后續操作,若超過10秒仍未找到節點,就會拋出節點未找到的異常。?
3.2 顯式等待
????????隱式等待在實際應用中效果并非總能盡如人意,這是因為它所設置的等待時間是固定的。然而,頁面的加載時間會受到網絡狀況、服務器響應等多種因素的影響,固定的等待時間難以適配各種復雜情況。?
????????相比之下,顯式等待就要靈活得多。它允許我們明確指定要查找的節點,同時設定最長的等待時間。在規定的時間范圍內,如果節點成功加載完成,程序就會返回該節點;但要是超過了設定時間,節點依舊沒有加載出來,程序便會拋出超時異常。下面通過一個示例來具體說明:?
?
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 ECbrowser = webdriver.Chrome()
browser.get('https://www.taobao.com/')# 等待頁面加載
wait = WebDriverWait(browser, 10)# 定位搜索輸入框
# 使用ID定位更準確
input = wait.until(EC.presence_of_element_located((By.ID, 'q')))# 定位搜索按鈕
# 使用更精確的CSS選擇器
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'button.btn-search.tb-bg')))print("搜索框:", input)
print("搜索按鈕:", button)
print("搜索框類型:", input.get_attribute('type'))
print("搜索按鈕文本:", button.text)
?
????????在這段代碼里,首先引入了`WebDriverWait`對象,并且把最長等待時間設定成了10秒。接著調用`until()`方法,同時傳入等待條件`expected_conditions`。就拿`presence_of_element_located`這個條件來說,它代表節點已經完成加載,其參數是節點的定位元組,這里指的是ID為`q`的搜索框節點。也就是說,在10秒的時間內,要是ID為`q`的節點成功加載出來,程序就會返回這個節點;要是超過10秒還沒加載好,就會拋出異常。
?
????????對于按鈕節點,等待條件被設置為`element_to_be_clickable`(節點可點擊),也就是去查找CSS選擇器為`.btn - search`的按鈕。若在10秒內這個按鈕變得可以點擊(意味著已經成功加載),程序就會返回該按鈕節點;若超過10秒按鈕還是不可點擊(即未加載完成),同樣會拋出異常。
????????當網絡狀況良好時,運行這段代碼能夠成功加載并輸出這兩個節點。具體的控制臺輸出如下:
控制臺輸出如下:
搜索框: <selenium.webdriver.remote.webelement.WebElement (session="958f257d68f99e09f2e00835aa7eea4d", element="f.EF316286D5F2BC448BCF689697FBF6B0.d.ECC671338A30A18D014C352C3583444B.e.2")>
搜索按鈕: <selenium.webdriver.remote.webelement.WebElement (session="958f257d68f99e09f2e00835aa7eea4d", element="f.EF316286D5F2BC448BCF689697FBF6B0.d.ECC671338A30A18D014C352C3583444B.e.12")>
搜索框類型: text
搜索按鈕文本: 搜索
????????一旦網絡狀態不好,在那預先設定的10秒時間內,節點無法順利完成加載,這種情況下程序就會拋出`TimeoutException`異常。
???????? 此外,等待條件的類型其實是多種多樣的,除了前面提到的那些,還可以用來判斷網頁的標題內容,或者查看某個節點內部是不是包含特定的文字信息等等。所有的等待條件以及它們對應的含義,都詳細地羅列在了下表當中。?
等待條件及其含義
????????若你想了解更多關于等待條件參數的詳細信息以及它們的具體用法,可查閱官方文檔,鏈接為:(http://selenium - python.readthedocs.io/api.html#module - selenium.webdriver.support.expected_conditions)。在該文檔中你能獲取更全面和深入的講解。?
四、前進后退
????????在日常使用瀏覽器的過程中,前進和后退功能是我們經常會用到的。而Selenium也提供了對這些操作的支持。在Selenium里,通過調用`back()`方法就能實現瀏覽器頁面的后退操作,使用`forward()`方法則可以實現頁面的前進操作。下面為你展示具體的示例代碼:
import time
from selenium import webdriverbrowser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
browser.get('https://www.taobao.com/')
browser.get('https://www.python.org/')
browser.back()
time.sleep(1)
browser.forward()
browser.close()
????????在上述代碼里,程序首先會依次訪問百度、淘寶以及Python官網這三個網頁。接著調用`back()`方法,此時瀏覽器會向后退回到淘寶頁面。在等待1秒鐘之后,再調用`forward()`方法,瀏覽器便會前進到Python官網頁面,最后關閉瀏覽器。借助這樣的操作方式,我們能夠在Selenium所驅動的瀏覽器中模擬用戶在瀏覽網頁時前進和后退的操作。?
五、Cookies
????????借助Selenium,我們可以非常方便地對Cookies進行各類操作,像獲取Cookies信息、添加新的Cookies以及刪除指定的Cookies等。下面是具體的示例代碼,它將展示如何使用Selenium來完成這些操作:
from selenium import webdriverbrowser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())
????????在代碼執行時,第一步是訪問知乎頁面。當頁面加載完畢后,瀏覽器會自動生成Cookies。此時調用`get_cookies()`方法,就能獲取到頁面所有的Cookies信息并打印輸出。
????????隨后,使用`add_cookie()`方法來添加一個新的Cookie,該方法需要傳入一個字典,字典里要包含`name`(名稱)、`domain`(域)和`value`(值)等必要信息。再次調用`get_cookies()`方法時,輸出結果中會顯示新增了剛剛添加的那個Cookie。
????????最后,執行`delete_all_cookies()`方法,這個操作會把所有的Cookies都刪除。當再次獲取Cookies時,得到的結果就會為空。
????????下面是控制臺可能出現的輸出情況:
????????沒錯,利用上述提到的`get_cookies()`、`add_cookie()`和`delete_all_cookies()`等方法,可輕松對瀏覽器里的Cookies進行管理和操作。無論是在測試環境中模擬用戶登錄狀態,還是在數據采集時處理特定的用戶標識信息,這些操作都能靈活滿足不同場景下的數據處理需求,提升自動化測試與數據處理的效率和準確性。
六、選項卡管理
????????在日常瀏覽網頁過程中,我們經常會同時打開多個選項卡來提高瀏覽效率。在Selenium里,也具備對選項卡進行操作的能力。下面是一個示例,為你展示如何在Selenium中操作選項卡:?
import time
from selenium import webdriverbrowser = webdriver.Chrome()
browser.get('https://www.baidu.com')# 打開新窗口
browser.execute_script('window.open()')
print(browser.window_handles)# 切換到新窗口
# 舊版API
# browser.switch_to_window(browser.window_handles[1])
# 新版API
browser.switch_to.window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(1)# 切換回第一個窗口
# 舊版API
# browser.switch_to_window(browser.window_handles[0])
# 新版API
browser.switch_to.window(browser.window_handles[0])
browser.get('https://python.org')
控制臺輸出如下:
????????在上述代碼中,整體操作模擬了用戶在瀏覽器中對多個選項卡進行管理的場景。具體而言,一開始打開百度頁面,之后借助`execute_script()`方法執行`window.open()`這條JavaScript語句,從而新開一個選項卡。`window_handles`屬性發揮了重要作用,調用它可以獲取當前所有已打開選項卡的信息,它會返回一個包含各選項卡代號的列表。
??????? 要想實現選項卡之間的切換,新版switch_to.window方法就派上用場了,只需把目標選項卡的代號作為參數傳入即可。在示例里,先將第二個選項卡的代號傳入該方法,成功切換到第二個選項卡后,在這個選項卡中打開了淘寶頁面。等待1秒之后,又把第一個選項卡的代號傳入switch_to.window 方法,從而切換回第一個選項卡,并在該選項卡中打開了Python官網頁面。
????????通過這樣的操作方式,能夠在Selenium中靈活地對多個選項卡進行管理,完美模擬了用戶在實際瀏覽網頁時于不同頁面間進行切換的操作。
七、異常處理
????????在運用Selenium開展自動化測試或者網頁操作時,各種異常情況難以避免,像超時異常、節點未找到異常等錯誤都可能出現。一旦碰到這類錯誤,程序往往會停止運行。為了防止程序因為異常而中斷,我們可以借助`try - except`語句來捕獲并處理各種異常。?
????????下面先通過一個節點未找到的異常示例,來看看如何運用`try - except`語句進行異常處理:?
from selenium import webdriver
from selenium.webdriver.common.by import Bybrowser = webdriver.Chrome()
browser.get('https://www.baidu.com')# 舊版API
# browser.find_element_by_id('hello')
# 新版API
browser.find_element(By.ID, 'hello')
????????上述代碼在打開百度頁面之后,試圖去查找一個ID為`hello`的節點,然而這個節點實際上并不存在于頁面中,所以會觸發異常。當代碼運行起來,控制臺就會輸出相應的錯誤信息,下面是具體的輸出情況:?
????????從輸出情況能夠看出,代碼拋出了`NoSuchElementException`異常,一般而言,這意味著在頁面中沒有找到指定的節點。為了避免程序因為這樣的異常而中斷運行,我們可以采用`try - except`語句來捕獲并處理異常。下面是具體的示例代碼:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementExceptionbrowser = webdriver.Chrome()
try:browser.get('https://www.baidu.com')
except TimeoutException:print('Time Out')
try:browser.find_element(By.ID, 'hello')
except NoSuchElementException:print('No Element')
finally:browser.close()
????????在這段代碼里,`try - except`語句發揮了重要作用,它能夠捕獲不同操作時可能出現的異常。針對`get()`方法,代碼捕獲`TimeoutException`異常,一旦出現超時情況,就會輸出`Time Out`。而對于`find_element_by_id()`方法,捕獲的是`NoSuchElementException`異常,要是沒有找到指定的節點,便會輸出`No Element`。?
????????另外,代碼里的`finally`塊保證了無論是否有異常發生,瀏覽器最終都會被關閉。下面是控制臺可能出現的輸出內容:?
No Element
????????要是還想了解更多Selenium里的異常類,能去參考官方文檔,鏈接是:(http://selenium - python.readthedocs.io/api.html#module - selenium.common.exceptions) 。?
????????上面把Selenium的各項功能都介紹了一遍,到這兒,咱們對它的常用操作方法也都清楚了。有了Selenium,處理JavaScript動態渲染的頁面就簡單多了。在網頁數據抓取、自動化測試這些工作中,Selenium可是個既強大又好用的工具。不管是做簡單的頁面操作,還是獲取復雜的動態數據,Selenium都能派上大用場,讓我們高效完成任務。往后,大家可以根據自己實際的需求,進一步去研究和使用Selenium的更多高級功能,來解決不同場景下網頁數據處理和測試的問題 。?
?