Selenium 等待机制 #
为什么需要等待? #
问题场景 #
text
┌─────────────────────────────────────────────────────────────┐
│ 常见的等待问题 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题 1: 元素尚未加载 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 点击按钮 ──> 触发 AJAX ──> 元素加载中... ──> 元素出现 │ │
│ │ │ │ │ │
│ │ └── 代码尝试定位元素 ──> 失败! ✗ ──────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 问题 2: 元素存在但不可见 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 元素在 DOM 中 ──> 但 display: none ──> 点击失败 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 问题 3: 元素被遮挡 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 元素可见 ──> 但被其他元素覆盖 ──> 点击失败 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 问题 4: 动画进行中 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 元素正在动画 ──> 位置不断变化 ──> 操作失败 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
解决方案对比 #
| 等待类型 | 特点 | 推荐度 |
|---|---|---|
| 强制等待 | 固定等待时间,简单但低效 | ⭐ |
| 隐式等待 | 全局生效,元素查找时等待 | ⭐⭐⭐ |
| 显式等待 | 针对特定条件,灵活高效 | ⭐⭐⭐⭐⭐ |
强制等待 #
time.sleep() #
python
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com")
# 强制等待 3 秒
time.sleep(3)
# 然后操作元素
button = driver.find_element(By.ID, "submit")
button.click()
driver.quit()
问题 #
python
# 问题 1: 等待时间不够
time.sleep(1) # 可能元素还没出现
# 问题 2: 等待时间过长
time.sleep(10) # 元素可能 1 秒就出现了,浪费 9 秒
# 问题 3: 维护困难
# 需要在每个可能需要等待的地方添加 sleep
隐式等待 #
基本用法 #
python
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
# 设置隐式等待时间(秒)
driver.implicitly_wait(10)
driver.get("https://example.com")
# 如果元素不存在,会等待最多 10 秒
element = driver.find_element(By.ID, "dynamic-element")
element.click()
driver.quit()
工作原理 #
text
┌─────────────────────────────────────────────────────────────┐
│ 隐式等待工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ driver.implicitly_wait(10) │
│ │
│ find_element() 调用时: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 尝试 1: 查找元素 │ │
│ │ │ │ │
│ │ ├── 找到 ──> 返回元素 ✓ │ │
│ │ │ │ │
│ │ └── 未找到 ──> 等待一小段时间 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 尝试 2: 查找元素 │ │
│ │ │ │ │
│ │ ├── 找到 ──> 返回元素 ✓ │ │
│ │ │ │ │
│ │ └── 未找到 ──> 继续等待... │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ... 重复直到超时 ... │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 超时 (10秒) ──> 抛出 NoSuchElementException ✗ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
注意事项 #
python
from selenium import webdriver
driver = webdriver.Chrome()
# 隐式等待是全局的,影响所有 find_element 调用
driver.implicitly_wait(10)
# 问题 1: 不能针对特定条件等待
# 隐式等待只等待元素出现在 DOM 中,不等待可见、可点击等
# 问题 2: 与显式等待混用可能导致不可预测的行为
# 不建议同时使用隐式等待和显式等待
# 问题 3: 影响性能
# 每次查找元素都可能等待
driver.quit()
显式等待 #
基本用法 #
python
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 EC
driver = webdriver.Chrome()
driver.get("https://example.com")
# 创建 WebDriverWait 对象
wait = WebDriverWait(driver, 10) # 最长等待 10 秒
# 等待元素出现
element = wait.until(
EC.presence_of_element_located((By.ID, "dynamic-element"))
)
# 等待元素可点击
button = wait.until(
EC.element_to_be_clickable((By.ID, "submit"))
)
button.click()
driver.quit()
WebDriverWait 参数 #
python
from selenium.webdriver.support.ui import WebDriverWait
# 基本参数
wait = WebDriverWait(
driver, # WebDriver 实例
timeout=10, # 最长等待时间(秒)
poll_frequency=0.5, # 轮询间隔(秒),默认 0.5
ignored_exceptions=None # 忽略的异常
)
# 忽略特定异常
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException
wait = WebDriverWait(
driver,
timeout=10,
ignored_exceptions=[NoSuchElementException, StaleElementReferenceException]
)
Expected Conditions #
常用条件 #
python
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 EC
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
# 元素存在(在 DOM 中)
element = wait.until(EC.presence_of_element_located((By.ID, "element")))
# 元素可见(displayed)
element = wait.until(EC.visibility_of_element_located((By.ID, "element")))
# 元素可点击
element = wait.until(EC.element_to_be_clickable((By.ID, "button")))
# 元素被选中
element = wait.until(EC.element_to_be_selected((By.ID, "checkbox")))
# 元素包含特定文本
element = wait.until(EC.text_to_be_present_in_element((By.ID, "status"), "Success"))
# 元素属性包含特定值
element = wait.until(EC.text_to_be_present_in_element_value((By.ID, "input"), "value"))
# 元素不可见
wait.until(EC.invisibility_of_element_located((By.ID, "loading")))
# 元素消失(不存在或不可见)
wait.until(EC.staleness_of(element))
# 标题包含特定文本
wait.until(EC.title_contains("Dashboard"))
# 标题等于特定文本
wait.until(EC.title_is("Home Page"))
# URL 包含特定文本
wait.until(EC.url_contains("dashboard"))
# URL 等于特定文本
wait.until(EC.url_to_be("https://example.com/home"))
# 弹窗出现
alert = wait.until(EC.alert_is_present())
# frame 可用并切换
wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID, "iframe")))
driver.quit()
多元素条件 #
python
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 EC
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
# 至少一个元素存在
elements = wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, "item")))
# 所有元素可见
elements = wait.until(EC.visibility_of_all_elements_located((By.CLASS_NAME, "item")))
# 至少一个元素可见
element = wait.until(EC.visibility_of_any_elements_located((By.CLASS_NAME, "item")))
# 元素数量
elements = wait.until(lambda d: len(d.find_elements(By.CLASS_NAME, "item")) >= 5)
driver.quit()
元素状态条件 #
python
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 EC
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
element = driver.find_element(By.ID, "checkbox")
# 元素被选中
wait.until(EC.element_to_be_selected(element))
# 元素未被选中
wait.until(EC.element_located_to_be_selected((By.ID, "checkbox")))
# 元素可见
wait.until(EC.visibility_of(element))
driver.quit()
自定义等待条件 #
使用 lambda 表达式 #
python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
# 等待元素文本不为空
element = wait.until(lambda d: d.find_element(By.ID, "result").text != "")
# 等待元素属性等于特定值
element = wait.until(
lambda d: d.find_element(By.ID, "input").get_attribute("value") == "expected"
)
# 等待元素数量大于 5
elements = wait.until(lambda d: len(d.find_elements(By.CLASS_NAME, "item")) > 5)
# 等待元素 CSS 属性
element = wait.until(
lambda d: d.find_element(By.ID, "box").value_of_css_property("display") == "block"
)
driver.quit()
自定义等待函数 #
python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
def element_has_text(driver, locator, text):
"""等待元素包含特定文本"""
element = driver.find_element(*locator)
return text in element.text
def element_is_enabled(driver, locator):
"""等待元素可用"""
element = driver.find_element(*locator)
return element.is_enabled()
def page_fully_loaded(driver):
"""等待页面完全加载"""
return driver.execute_script("return document.readyState") == "complete"
def ajax_complete(driver):
"""等待 AJAX 完成(jQuery)"""
return driver.execute_script("return jQuery.active == 0")
# 使用自定义条件
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
# 使用自定义函数
element = wait.until(lambda d: element_has_text(d, (By.ID, "status"), "Success"))
element = wait.until(lambda d: element_is_enabled(d, (By.ID, "button")))
wait.until(page_fully_loaded)
wait.until(ajax_complete)
driver.quit()
自定义 Expected Condition 类 #
python
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 EC
class element_has_class:
"""等待元素包含特定 class"""
def __init__(self, locator, class_name):
self.locator = locator
self.class_name = class_name
def __call__(self, driver):
element = driver.find_element(*self.locator)
classes = element.get_attribute("class").split()
if self.class_name in classes:
return element
return False
class element_attribute_contains:
"""等待元素属性包含特定值"""
def __init__(self, locator, attribute, value):
self.locator = locator
self.attribute = attribute
self.value = value
def __call__(self, driver):
element = driver.find_element(*self.locator)
attr_value = element.get_attribute(self.attribute)
if attr_value and self.value in attr_value:
return element
return False
class url_matches:
"""等待 URL 匹配正则表达式"""
import re
def __init__(self, pattern):
self.pattern = self.re.compile(pattern)
def __call__(self, driver):
return self.pattern.search(driver.current_url) is not None
# 使用自定义 Expected Condition
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
# 等待元素包含特定 class
element = wait.until(element_has_class((By.ID, "button"), "active"))
# 等待元素属性包含特定值
element = wait.until(element_attribute_contains((By.ID, "link"), "href", "dashboard"))
# 等待 URL 匹配正则
wait.until(url_matches(r".*/dashboard/\d+"))
driver.quit()
等待策略最佳实践 #
组合等待策略 #
python
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 EC
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
# 等待加载动画消失
wait.until(EC.invisibility_of_element_located((By.ID, "loading")))
# 然后等待目标元素出现
element = wait.until(EC.visibility_of_element_located((By.ID, "content")))
# 再等待元素可点击
button = wait.until(EC.element_to_be_clickable((By.ID, "submit")))
button.click()
driver.quit()
页面加载等待 #
python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
driver = webdriver.Chrome()
# 设置页面加载超时
driver.set_page_load_timeout(30)
# 等待页面完全加载
def wait_for_page_load(driver, timeout=30):
wait = WebDriverWait(driver, timeout)
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
driver.get("https://example.com")
wait_for_page_load(driver)
driver.quit()
AJAX 等待 #
python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
# 等待 jQuery AJAX 完成
def wait_for_jquery_ajax(driver, timeout=10):
wait = WebDriverWait(driver, timeout)
wait.until(lambda d: d.execute_script("return jQuery.active == 0"))
# 等待 Angular 请求完成
def wait_for_angular(driver, timeout=10):
wait = WebDriverWait(driver, timeout)
wait.until(lambda d: d.execute_script(
"return angular.element(document.body).injector().get('$http').pendingRequests.length === 0"
))
# 点击触发 AJAX
driver.find_element(By.ID, "load-data").click()
# 等待 AJAX 完成
wait_for_jquery_ajax(driver)
driver.quit()
等待工具类 #
python
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 EC
from selenium.common.exceptions import TimeoutException
class WaitHelper:
def __init__(self, driver, timeout=10):
self.driver = driver
self.wait = WebDriverWait(driver, timeout)
def wait_for_element(self, locator, timeout=None):
"""等待元素存在"""
wait = WebDriverWait(self.driver, timeout) if timeout else self.wait
return wait.until(EC.presence_of_element_located(locator))
def wait_for_visible(self, locator, timeout=None):
"""等待元素可见"""
wait = WebDriverWait(self.driver, timeout) if timeout else self.wait
return wait.until(EC.visibility_of_element_located(locator))
def wait_for_clickable(self, locator, timeout=None):
"""等待元素可点击"""
wait = WebDriverWait(self.driver, timeout) if timeout else self.wait
return wait.until(EC.element_to_be_clickable(locator))
def wait_for_invisible(self, locator, timeout=None):
"""等待元素不可见"""
wait = WebDriverWait(self.driver, timeout) if timeout else self.wait
return wait.until(EC.invisibility_of_element_located(locator))
def wait_for_text(self, locator, text, timeout=None):
"""等待元素包含文本"""
wait = WebDriverWait(self.driver, timeout) if timeout else self.wait
return wait.until(EC.text_to_be_present_in_element(locator, text))
def wait_for_url_contains(self, text, timeout=None):
"""等待 URL 包含文本"""
wait = WebDriverWait(self.driver, timeout) if timeout else self.wait
return wait.until(EC.url_contains(text))
def wait_for_page_load(self, timeout=30):
"""等待页面加载完成"""
wait = WebDriverWait(self.driver, timeout)
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
def wait_until(self, condition, timeout=None):
"""等待自定义条件"""
wait = WebDriverWait(self.driver, timeout) if timeout else self.wait
return wait.until(condition)
def wait_for_any(self, locators, timeout=None):
"""等待任意一个元素出现"""
wait = WebDriverWait(self.driver, timeout) if timeout else self.wait
for locator in locators:
try:
element = wait.until(EC.presence_of_element_located(locator))
return element
except TimeoutException:
continue
raise TimeoutException(f"None of the elements found: {locators}")
# 使用示例
driver = webdriver.Chrome()
driver.get("https://example.com")
wait_helper = WaitHelper(driver)
# 使用工具类等待
element = wait_helper.wait_for_visible((By.ID, "content"))
button = wait_helper.wait_for_clickable((By.ID, "submit"))
wait_helper.wait_for_invisible((By.ID, "loading"))
driver.quit()
超时处理 #
异常捕获 #
python
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 EC
from selenium.common.exceptions import TimeoutException
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
try:
element = wait.until(EC.visibility_of_element_located((By.ID, "slow-element")))
element.click()
except TimeoutException:
print("元素等待超时,执行备选方案")
# 执行备选操作
driver.execute_script("...")
driver.quit()
重试机制 #
python
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 EC
from selenium.common.exceptions import TimeoutException, StaleElementReferenceException
import time
def wait_with_retry(driver, locator, max_retries=3, timeout=10):
"""带重试的等待"""
for attempt in range(max_retries):
try:
wait = WebDriverWait(driver, timeout)
element = wait.until(EC.visibility_of_element_located(locator))
return element
except (TimeoutException, StaleElementReferenceException) as e:
print(f"尝试 {attempt + 1}/{max_retries} 失败: {e}")
if attempt < max_retries - 1:
time.sleep(1)
else:
raise
# 使用示例
driver = webdriver.Chrome()
driver.get("https://example.com")
element = wait_with_retry(driver, (By.ID, "dynamic-element"))
element.click()
driver.quit()
实战示例 #
登录流程等待 #
python
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 EC
def login_with_wait():
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)
try:
driver.get("https://example.com/login")
# 等待登录表单可见
wait.until(EC.visibility_of_element_located((By.ID, "login-form")))
# 输入用户名密码
driver.find_element(By.ID, "username").send_keys("user")
driver.find_element(By.ID, "password").send_keys("pass")
# 等待登录按钮可点击
login_button = wait.until(EC.element_to_be_clickable((By.ID, "login-button")))
login_button.click()
# 等待加载动画消失
wait.until(EC.invisibility_of_element_located((By.ID, "loading")))
# 等待跳转到主页
wait.until(EC.url_contains("dashboard"))
# 等待欢迎消息出现
welcome = wait.until(EC.visibility_of_element_located((By.ID, "welcome")))
print(f"登录成功: {welcome.text}")
finally:
driver.quit()
login_with_wait()
动态列表加载 #
python
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 EC
def load_dynamic_list():
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 10)
try:
driver.get("https://example.com/list")
# 点击加载更多
load_more = wait.until(EC.element_to_be_clickable((By.ID, "load-more")))
items_count = 0
while True:
# 等待新项目加载
wait.until(lambda d: len(d.find_elements(By.CLASS_NAME, "item")) > items_count)
items = driver.find_elements(By.CLASS_NAME, "item")
items_count = len(items)
print(f"已加载 {items_count} 个项目")
# 检查是否还有更多
load_more = driver.find_element(By.ID, "load-more")
if "disabled" in load_more.get_attribute("class"):
break
load_more.click()
# 等待加载动画消失
wait.until(EC.invisibility_of_element_located((By.ID, "loading")))
print(f"加载完成,共 {items_count} 个项目")
finally:
driver.quit()
load_dynamic_list()
下一步 #
掌握了等待机制后,接下来学习 高级处理 了解更多复杂场景的处理方法!
最后更新:2026-03-28