You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Selenium(Python):sleep与显式等待的差异及失效问题排查

问题:Selenium显式等待失效,改用time.sleep却正常运行

爬取机场航班信息时,用Selenium显式等待处理元素,运行几条航班后抛出StaleElementReferenceException异常,即便把该异常加入忽略列表、延长超时时间都没用。换成time.sleep(1)替代循环内的显式等待后,代码就能正常运行,疑惑为什么显式等待没法避免崩溃,而手动等待1秒却可行,原本以为两者效果一致。

原代码

import time
from selenium import webdriver
from selenium.common import NoSuchElementException, StaleElementReferenceException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_experimental_option("detach", True);
driver = webdriver.Chrome(options)
driver.get("https://www.flightradar24.com/airport/tun/arrivals") # 爬取到达航班
ignored_exceptions = (NoSuchElementException, StaleElementReferenceException)
wait = WebDriverWait(driver, 5,ignored_exceptions=ignored_exceptions)
try:
    consent = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'button[id="onetrust-accept-btn-handler"]'))) 
    # 处理弹窗,点击同意
    consent.click()
except:
    print('弹窗处理出错')
info = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'li[class="airport__flight-list-item border-b-2 delay-300 border-white w-full"]'))) 
# 获取所有航班条目
for i in info: # 遍历航班,只处理TU开头的航班
    wait_i = WebDriverWait(i, 5, ignored_exceptions=ignored_exceptions)
    Flightnumber = wait_i.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'span[class="truncate text-sm text-gray-900"]'))).text
    if Flightnumber[:2] == 'TU': 
        drop_down_button = wait_i.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'button[class="px-1"]'))) 
        drop_down_button.click()    # 点击右侧箭头展开航班详情
        details = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'div[class="mx-2 grid grid-cols-24"]'))).text  
        # 等待详情加载后打印
        print(details)

报错信息

StaleElementReferenceException: Message: stale element reference: stale element not found at    info = driver.find_element(By.CSS_SELECTOR,'div[class="mx-2 grid grid-cols-24"]').text

修改后正常运行的代码片段

for i in info:
    Flightnumber = i.find_element(By.CSS_SELECTOR, 'span[class="truncate text-sm text-gray-900"]').text
    if Flightnumber[:2] == 'TU':
        drop_down_button = i.find_element(By.CSS_SELECTOR, 'button[class="px-1"]')
        drop_down_button.click()
        time.sleep(1)
        details = driver.find_element(By.CSS_SELECTOR,'div[class="mx-2 grid grid-cols-24"]').text
        print(details)

原因分析

  1. DOM动态刷新导致元素引用过期:
    原代码提前一次性获取了所有航班的info列表,之后循环遍历这些元素。但点击下拉按钮后,页面可能会重新渲染部分DOM(比如其他航班条目或详情区域),导致之前获取的info列表中的元素引用失效,触发StaleElementReferenceException。显式等待虽然配置了忽略该异常,但当循环到已过期的元素时,等待逻辑无法重新获取新的元素引用,最终超时抛出异常。

  2. 显式等待的作用范围不匹配:
    原代码对单个航班元素i使用局部等待WebDriverWait(i, ...),但点击下拉按钮后引发的是全局DOM变化,局部等待无法应对这种全局的元素失效问题。

  3. 详情元素定位缺乏唯一性:
    定位详情的CSS选择器div[class="mx-2 grid grid-cols-24"]可能不是唯一的,显式等待可能找到旧的过期元素;而time.sleep(1)给了足够时间让新的详情元素完全加载并替换旧元素,此时再查找就能拿到有效的元素引用。


修复方案

核心思路是避免持有过期的元素引用,不要提前一次性获取所有航班元素,而是每次循环都重新查找当前页面的航班列表,同时优化显式等待的定位条件,确保操作的是最新的DOM元素。

优化后的完整代码

import time
from selenium import webdriver
from selenium.common import NoSuchElementException, StaleElementReferenceException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_experimental_option("detach", True)
driver = webdriver.Chrome(options)
driver.get("https://www.flightradar24.com/airport/tun/arrivals")
ignored_exceptions = (NoSuchElementException, StaleElementReferenceException)
wait = WebDriverWait(driver, 5, ignored_exceptions=ignored_exceptions)

try:
    consent = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'button[id="onetrust-accept-btn-handler"]')))
    consent.click()
except:
    print('弹窗处理出错')

# 循环处理,每次重新获取航班列表避免元素过期
processed_count = 0
while True:
    try:
        # 每次循环都重新获取当前页面的航班条目
        info = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'li[class="airport__flight-list-item border-b-2 delay-300 border-white w-full"]')))
        found = False
        for i in info:
            try:
                flight_num = i.find_element(By.CSS_SELECTOR, 'span[class="truncate text-sm text-gray-900"]').text
                if flight_num[:2] == 'TU':
                    # 确保下拉按钮可点击,且关联当前航班
                    drop_btn = wait.until(EC.element_to_be_clickable(
                        (By.CSS_SELECTOR, f'li:has(span:text("{flight_num}")) button[class="px-1"]')
                    ))
                    drop_btn.click()
                    # 等待详情元素加载完成并获取文本
                    details = wait.until(EC.presence_of_element_located(
                        (By.CSS_SELECTOR,'div[class="mx-2 grid grid-cols-24"]')
                    )).text
                    print(f"航班 {flight_num} 详情:\n{details}\n")
                    processed_count += 1
                    found = True
                    # 处理完一个后退出内层循环,重新获取航班列表
                    break
            except StaleElementReferenceException:
                # 遇到过期元素直接跳过,进入下一次循环
                continue
        if not found:
            # 没有找到未处理的TU开头航班,退出循环
            print(f"共处理 {processed_count} 个TU开头航班")
            break
    except Exception as e:
        print(f"循环出错: {str(e)}")
        break

driver.quit()

关键优化点

  • 每次循环重新获取航班列表:确保每次操作的都是当前DOM中的有效元素,避免持有过期引用。
  • 精准关联元素:通过航班号定位对应的下拉按钮,避免误操作其他航班的按钮。
  • 异常捕获后重试:遇到StaleElementReferenceException时直接跳过,进入下一次循环重新获取元素。
  • 显式等待条件优化:使用element_to_be_clickable替代单纯的presence_of_element_located,确保元素不仅存在还可交互。

内容的提问来源于stack exchange,提问作者Dimbo123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 01:54:58