为什么需要等待?
当你用 Selenium 操作浏览器时,你的代码执行速度远快于浏览器渲染和处理页面的速度,你点击一个按钮,页面需要 2 秒钟才能加载出弹窗,如果你的代码在点击后立即就去查找弹窗中的元素,那么因为弹窗还没出现,代码就会失败,抛出 NoSuchElementException 异常。

等待就是用来解决这个问题的:它让代码在执行下一步操作前,暂停一段时间,直到某个条件(比如元素出现)满足为止。
Selenium 提供了三种主要的等待方式
- 强制等待
- 隐式等待
- 显式等待
下面我们逐一详细介绍。
强制等待
这是最简单、最直接的等待方式,它在代码中插入一个固定的“暂停”。
如何使用
使用 time.sleep() 方法,需要先导入 time 模块。

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://www.example.com")
# 强制等待 5 秒,无论页面是否加载完成
time.sleep(5)
# 在等待 5 秒后,尝试查找页面上的标题
try:
heading = driver.find_element(By.TAG_NAME, "h1")
print(f"标题是: {heading.text}")
except Exception as e:
print(f"发生错误: {e}")
driver.quit()
优点
- 简单易用:一行代码就能搞定。
- 直观:你能明确知道脚本会暂停多久。
缺点
- 非常不灵活:你设置的等待时间可能太长(浪费测试时间),也可能太短(如果网络慢了,依然会失败)。
- 降低脚本的效率:即使页面元素 1 秒后就加载出来了,脚本也必须傻等 5 秒。
只适合在调试、快速验证脚本时使用,不推荐在生产环境的自动化测试脚本中大量使用。
隐式等待
隐式等待是全局性的设置,它告诉 WebDriver 在查找任何元素时,如果没有立即找到,就轮询(每隔一小段时间)查找,直到元素出现或超过设定的最大时间。
如何使用
使用 driver.implicitly_wait() 方法。通常只需要设置一次,在 driver.get() 之前设置即可。
from selenium import webdriver
from selenium.webdriver.common.by import By
# 创建驱动实例
driver = webdriver.Chrome()
# 设置隐式等待,最长等待 10 秒
# 这会对整个 driver 的生命周期生效
driver.implicitly_wait(10)
driver.get("https://www.example.com")
# 当查找元素时,如果找不到,Selenium 会等待最多 10 秒
# 如果页面需要 3 秒才加载出 h1,那么这行代码会等待 3 秒
# 如果超过 10 秒还没出现,才会抛出 NoSuchElementException
heading = driver.find_element(By.TAG_NAME, "h1")
print(f"标题是: {heading.text}")
# 同样适用于查找一组元素
paragraphs = driver.find_elements(By.TAG_NAME, "p")
print(f"找到 {len(paragraphs)} 个段落")
driver.quit()
优点
- 代码简洁:只需要设置一次,后续的元素查找都不需要再写等待逻辑。
- 全局生效:方便管理。
缺点
- 不够精确:它只对
find_element和find_elements方法有效,对于其他操作(如click()、send_keys()等)无效,如果元素找到了但不可点击(比如被其他元素遮挡),click()操作依然会失败。 - 固定等待时间:如果元素 1 秒就加载好了,它还是会等到轮询周期结束(通常几百毫秒),虽然比
time.sleep(10)好,但仍有优化空间。 - 对多个元素无效:如果你等待的是一组元素,隐式等待会在第一个元素出现时就返回,而不是等待所有元素都出现。
比强制等待好很多,适合简单的场景,但对于复杂的、需要精确判断元素状态的场景,显式等待是更好的选择。

显式等待
这是最强大、最灵活、最推荐的等待方式,它允许你等待一个特定的条件(如元素可见、可点击、包含特定文本等)成立,一旦条件满足,脚本就立即继续执行,如果超过了设定的最大时间,才会抛出 TimeoutException 异常。
如何使用
需要导入 WebDriverWait 和 expected_conditions (通常简写为 EC)。
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://www.example.com")
# 创建一个 WebDriverWait 对象
# 参数:1. driver 实例, 2. 最长等待时间(秒)
wait = WebDriverWait(driver, 10)
# 等待条件:ID 为 "myDynamicElement" 的元素变得可见
# 这是最常用的场景之一
try:
# 直到元素可见,才会返回该元素
element = wait.until(
EC.visibility_of_element_located((By.ID, "myDynamicElement"))
)
# 找到元素后,就可以进行操作
element.click()
print("成功点击了元素!")
except Exception as e:
print(f"等待超时或发生错误: {e}")
driver.quit()
expected_conditions 提供了丰富的条件
EC 模块提供了大量预定义的条件,你可以根据需要选择:
| 条件 | 方法 | 描述 |
|---|---|---|
| 元素可见 | visibility_of_element_located((By, 'value')) |
元素存在于 DOM 中,并且是可见的(宽和高都大于0)。 |
| 元素可点击 | element_to_be_clickable((By, 'value')) |
元素可见且是可点击的。 |
| 元素存在 | presence_of_element_located((By, 'value')) |
元素存在于 DOM 中,但不一定可见。 |
| 元素包含特定文本 | text_to_be_present_in_element((By, 'value'), 'text') |
元素的文本包含指定的字符串。 |
| 元素可被选中 | element_to_be_selected(element) |
用于复选框或单选框,检查其是否被选中。 |
| 元素不可见 | invisibility_of_element_located((By, 'value')) |
元素不存在于 DOM 中或存在但不可见。 |
你也可以自定义等待条件,只需要创建一个返回 True 或 False 的函数即可。
# 自定义等待条件
def element_has_class(driver, locator, class_name):
element = driver.find_element(*locator)
return class_name in element.get_attribute("class")
# 使用自定义条件
wait.until(lambda d: element_has_class(d, (By.ID, "my_button"), "active"))
优点
- 高度灵活:可以等待任意你想要的条件。
- 精确控制:只在你需要的条件满足时才继续,效率最高。
- 代码可读性强:
wait.until(EC.visibility_of_element_located(...))非常清晰地表达了你的意图。
缺点
- 代码稍显冗长:每次等待都需要写几行代码。
对于任何严肃的自动化测试项目,显式等待都是首选,它能极大地提高脚本的稳定性和可维护性。
总结与最佳实践
| 等待类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 强制等待 | 简单、直观 | 不灵活、效率低 | 调试、快速验证 |
| 隐式等待 | 代码简洁、全局生效 | 不够精确、对非 find 操作无效 |
简单的脚本、全局等待元素出现 |
| 显式等待 | 最灵活、最精确、最稳定 | 代码稍长 | 复杂场景、需要精确判断元素状态、生产环境脚本 |
最佳实践建议
- 优先使用显式等待:对于任何关键操作(点击、输入、断言),都应该使用显式等待来确保元素处于期望的状态(如可见、可点击)。
- 谨慎使用隐式等待:如果你决定使用隐式等待,最好只设置一次,并且不要和显式等待混用,混用可能会导致意想不到的等待时间叠加。
- 避免使用强制等待:除了在调试时,尽量不要在你的最终测试脚本中使用
time.sleep()。 - 组合使用:在某些复杂场景下,可以组合使用,先用隐式等待设置一个全局的“底线”等待时间(10 秒),然后对于关键操作,再使用显式等待进行精确控制,但请注意,这可能会导致总等待时间超过预期。
一个综合示例
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.maximize_window()
try:
driver.get("https://www.baidu.com")
# 使用显式等待等待搜索框出现并可见
search_box = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "kw"))
)
# 使用显式等待等待“百度一下”按钮可点击
search_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "su"))
)
# 执行操作
search_box.send_keys("Selenium 等待")
search_button.click()
# 使用显式等待等待新的页面标题变化
WebDriverWait(driver, 10).until(
EC.title_contains("Selenium 等待")
)
print("页面标题已更新,搜索成功!")
# 获取结果并打印
results = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".result h3"))
)
print(f"找到 {len(results)} 个搜索结果。")
for i, result in enumerate(results):
print(f"结果 {i+1}: {result.text}")
except TimeoutException:
print("等待超时!元素未在指定时间内出现或达到条件。")
except Exception as e:
print(f"发生未知错误: {e}")
finally:
# 无论成功与否,最后都关闭浏览器
driver.quit()
通过遵循这些原则,你的 Selenium 脚本将变得更加健壮和可靠。
