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