一、為什么需要等待機制?
網頁是動態加載的,元素出現的時間不確定。如果腳本在元素還沒加載完成時就嘗試操作它,就會拋出?NoSuchElementException
?異常。
三種等待方式:
強制等待:
time.sleep()
?- 簡單但低效隱式等待:
driver.implicitly_wait()
?- 全局設置顯式等待:
WebDriverWait
?+?expected_conditions
?-?最推薦的方式
二、強制等待 (不推薦但需了解)
import timetime.sleep(5) # 強制等待5秒
優點:簡單易用
缺點:
效率低下(總是等待固定時間)
不可靠(網絡慢時可能不夠,網絡快時浪費時間)
三、隱式等待 (Implicit Wait)
設置一個全局的等待時間,對所有元素查找操作都生效。
from selenium import webdriverdriver = webdriver.Chrome()
driver.implicitly_wait(10) # 設置隱式等待時間為10秒driver.get("https://example.com")
# 所有find_element操作都會最多等待10秒
element = driver.find_element(By.ID, "some-element")
工作原理:在查找元素時,如果立即沒找到,會輪詢DOM直到找到元素或超時
優點:一次設置,全局生效
缺點:
不夠靈活,無法針對特定條件等待
可能會影響腳本性能
四、顯式等待 (Explicit Wait) -?重點推薦
顯式等待允許你設置特定條件,只在需要的地方等待,更加靈活和高效。
1. 基本用法
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By# 創建WebDriverWait實例,設置最長等待時間10秒
wait = WebDriverWait(driver, 10)# 等待直到元素可見
element = wait.until(EC.visibility_of_element_located((By.ID, "myDynamicElement")))# 等待直到元素可點擊
element = wait.until(EC.element_to_be_clickable((By.ID, "submit-btn")))# 然后進行操作
element.click()
2. 常用的 Expected Conditions
Selenium 提供了許多預定義的條件,以下是最常用的幾種:
元素存在與可見性
presence_of_element_located(locator)
?- 元素出現在DOM中(不一定可見)visibility_of_element_located(locator)
?- 元素可見(有寬度和高度)invisibility_of_element_located(locator)
?- 元素不可見或不存在
元素可交互性
element_to_be_clickable(locator)
?- 元素可見且可點擊element_to_be_selected(locator)
?- 復選框/單選框可被選中
文本內容
text_to_be_present_in_element(locator, text)
?- 元素包含特定文本text_to_be_present_in_element_value(locator, text)
?- 元素的value屬性包含特定文本
頁面狀態
title_is(title)
?- 頁面標題完全匹配title_contains(partial_title)
?- 頁面標題包含特定文本url_to_be(url)
?- URL完全匹配url_contains(partial_url)
?- URL包含特定文本
元素選擇狀態
element_located_to_be_selected(locator)
?- 元素被選中element_located_selection_state_to_be(locator, is_selected)
?- 元素選中狀態符合預期
元素數量
presence_of_all_elements_located(locator)
?- 至少找到一個元素number_of_elements_to_be(locator, number)
?- 找到特定數量的元素number_of_elements_to_be_less_than(locator, number)
?- 元素數量少于指定值number_of_elements_to_be_more_than(locator, number)
?- 元素數量多于指定值
3. 自定義等待條件
如果預定義條件不滿足需求,你可以創建自定義等待條件:
from selenium.webdriver.support.ui import WebDriverWait# 自定義等待函數 - 等待元素包含特定類名
def element_has_class(locator, class_name):def predicate(driver):element = driver.find_element(*locator)if class_name in element.get_attribute("class").split():return elementreturn Falsereturn predicate# 使用自定義等待條件
wait = WebDriverWait(driver, 10)
element = wait.until(element_has_class((By.ID, "my-element"), "active"))
4. 高級用法:設置輪詢頻率和忽略異常
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException# 設置等待時間10秒,每0.5秒檢查一次,忽略NoSuchElementException異常
wait = WebDriverWait(driver, timeout=10, poll_frequency=0.5,ignored_exceptions=[NoSuchElementException]
)element = wait.until(EC.visibility_of_element_located((By.ID, "my-element")))
五、混合使用等待策略
最佳實踐是結合使用隱式等待和顯式等待:
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import Bydriver = webdriver.Chrome()
# 設置一個較短的隱式等待作為后備
driver.implicitly_wait(5)driver.get("https://example.com")try:# 使用顯式等待處理關鍵元素wait = WebDriverWait(driver, 10)login_button = wait.until(EC.element_to_be_clickable((By.ID, "login-btn")))login_button.click()# 等待頁面跳轉完成(URL變化)wait.until(EC.url_contains("dashboard"))# 等待歡迎消息出現welcome_message = wait.until(EC.visibility_of_element_located((By.CLASS_NAME, "welcome-msg")))print("登錄成功:" + welcome_message.text)finally:driver.quit()
六、實戰示例:處理動態加載內容
假設有一個頁面,點擊按鈕后通過AJAX加載內容:
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keysdriver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)driver.get("https://example.com/dynamic-content")# 點擊加載更多按鈕
load_more_btn = wait.until(EC.element_to_be_clickable((By.ID, "load-more")))
load_more_btn.click()# 等待新內容加載完成(等待特定元素出現)
# 方法1:等待新增的元素
new_item = wait.until(EC.presence_of_element_located((By.XPATH, "//div[@class='item'][last()]"))
)# 方法2:等待加載指示器消失
wait.until(EC.invisibility_of_element_located((By.ID, "loading-spinner"))
)# 方法3:等待特定數量的元素
wait.until(EC.number_of_elements_to_be((By.CLASS_NAME, "item"), 10)
)print("新內容加載完成!")
driver.quit()
七、常見問題與解決方案
1. 等待超時怎么辦?
from selenium.common.exceptions import TimeoutExceptiontry:element = wait.until(EC.visibility_of_element_located((By.ID, "slow-element")))
except TimeoutException:print("元素加載超時,執行備用方案")# 執行其他操作或重新加載頁面driver.refresh()# 再次嘗試等待element = wait.until(EC.visibility_of_element_located((By.ID, "slow-element")))
2. 處理StaleElementReferenceException
當元素不再附加到DOM時會發生此異常,常見于頁面刷新或AJAX更新后:
from selenium.common.exceptions import StaleElementReferenceExceptiondef wait_for_non_stale_element(locator, timeout=10):wait = WebDriverWait(driver, timeout)return wait.until(lambda d: d.find_element(*locator))element = wait_for_non_stale_element((By.ID, "dynamic-element"))
3. 等待多個條件
# 使用lambda表達式組合多個條件
wait.until(lambda d: d.find_element(By.ID, "element1").is_displayed() and d.find_element(By.ID, "element2").is_enabled()
)
八、最佳實踐總結
優先使用顯式等待:針對特定條件等待,更加精確和高效
合理設置超時時間:根據網絡速度和頁面復雜度設置
使用合適的預期條件:根據具體需求選擇最匹配的條件
避免混合使用隱式和顯式等待:可能導致不可預測的等待時間
處理異常:使用try-except塊處理可能的超時異常
編寫自定義等待條件:當內置條件不滿足需求時