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

使用Python+Selenium爬取动态表格遇问题:过时元素异常与性能下降

问题描述

我是编程新手,还需学习很多内容,恳请各位耐心解答。

我正在用Python和Selenium爬取某网站的动态表格数据,表格采用无刷新分页模式(切换页面时URL不变,仅表格内容更新)。为获取刷新后的表格数据并避免Stale Element Exception(过时元素异常),我用了WebDriverWait结合expected_conditions等待<tr>元素可见,但仍无法获取新数据,反而拿到上一页旧数据并触发异常。添加time.sleep()点击下一页按钮后暂时解决了问题,但爬取到约120页时,代码性能逐渐下降,再次出现过时元素异常,无法获取更新后的数据。

我尝试过设置隐式等待、逐步增加time.sleep()时长,但均无效果。该表格每页有100行数据,总计约3100页。

核心问题:

  1. 为何会出现Stale Element Exception,该如何避免?
  2. 如何提升代码的爬取效率?

我已自行搜索并尝试解决未果,特此求助,感谢您的时间与帮助。

以下是我的代码:

while True:

    # waits until the table elements are visible when the page is loaded
    # this is a must step for Selenium to scrap data from the dynamic table when we navigate through different pages
    tr = WebDriverWait(driver, 10).until(EC.visibility_of_all_elements_located((By.XPATH, "//*[@id='erdashboard']/tbody/tr")))
    
    for record in tr:
        count += 1
    
        posted_date = datetime.strptime(record.find_element(By.XPATH, './td[7]').text, "%m/%d/%Y").date()
    
        exclusion_request_dict["ID"].append(int(record.find_element(By.XPATH, './td[1]').text))
        exclusion_request_dict["Company"].append(record.find_element(By.XPATH, './td[2]').text)
        exclusion_request_dict["Product"].append(record.find_element(By.XPATH, './td[3]').text)
        exclusion_request_dict["HTSUSCode"].append(record.find_element(By.XPATH, './td[4]').text)
        exclusion_request_dict["Status"].append(record.find_element(By.XPATH, './td[5]').text)
        exclusion_request_dict["Posted Date"].append(posted_date)
    
    next_button = driver.find_element(By.ID, "erdashboard_next")
    next_button_clickable = driver.find_element(By.ID, "erdashboard_next").get_attribute("class").split(" ")
    print(next_button_clickable)
    print("Current Page:", page, "Total Counts:", count)
    
    if next_button_clickable[-1] == "disabled":
        break
    
    next_button.click() # goes to the next page
    time.sleep(wait + 0.01)

解决方案

1. Stale Element Exception的成因与规避方法

Stale Element Exception本质是你之前定位的元素已被页面DOM移除或重新渲染,导致旧的元素引用失效。比如切换分页后,原页面的<tr>元素会被新数据替换,此时再操作存到tr变量里的旧元素引用,就会触发异常。

你的代码存在两个关键问题:

  • 调用visibility_of_all_elements_located等待时,旧的<tr>可能还未完全消失,导致等待提前结束,拿到的还是旧元素
  • 用固定时长的time.sleep()等待页面更新,可靠性极差——网络或服务器波动会导致等待时长不足(拿到旧数据)或过长(拖慢效率)

规避方法:

  • 每次切换分页后重新定位元素,绝不复用之前的元素引用
  • 等待时结合staleness_of条件,确保旧表格元素已被移除,再去定位新表格
  • 优先从表格父元素内定位子元素,缩小定位范围,减少DOM变化带来的影响

2. 爬取效率提升方案

  • 彻底移除time.sleep(),用显式等待替代,仅在必要时等待,避免无意义的延迟
  • 减少元素定位次数:遍历<tr>时一次性获取所有<td>元素,而非多次调用find_element
  • 尝试用execute_script直接通过JavaScript提取表格数据,比Selenium逐个定位元素快数倍
  • 若能通过浏览器开发者工具找到网站的API接口,直接调用API获取数据是最高效的方式(完全绕开Selenium的渲染开销)

优化后的代码示例

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import StaleElementReferenceException
import datetime

while True:
    try:
        # 先定位表格主体,确保其存在
        table_body = WebDriverWait(driver, 15).until(
            EC.presence_of_element_located((By.XPATH, "//*[@id='erdashboard']/tbody"))
        )
        # 从表格主体内重新定位所有<tr>,避免引用旧元素
        tr_list = WebDriverWait(table_body, 10).until(
            EC.visibility_of_all_elements_located((By.TAG_NAME, "tr"))
        )
        
        for record in tr_list:
            count += 1
            # 一次性获取当前行的所有<td>,减少定位次数
            td_list = record.find_elements(By.TAG_NAME, "td")
            posted_date = datetime.strptime(td_list[6].text, "%m/%d/%Y").date()
            
            exclusion_request_dict["ID"].append(int(td_list[0].text))
            exclusion_request_dict["Company"].append(td_list[1].text)
            exclusion_request_dict["Product"].append(td_list[2].text)
            exclusion_request_dict["HTSUSCode"].append(td_list[3].text)
            exclusion_request_dict["Status"].append(td_list[4].text)
            exclusion_request_dict["Posted Date"].append(posted_date)
        
        # 重新定位下一页按钮,检查是否可点击
        next_button = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.ID, "erdashboard_next"))
        )
        next_button_classes = next_button.get_attribute("class").split(" ")
        print(next_button_classes)
        print("Current Page:", page, "Total Counts:", count)
        
        if "disabled" in next_button_classes:
            break
        
        # 点击下一页后,等待旧表格主体失效,确保DOM已更新
        WebDriverWait(driver, 10).until(
            EC.staleness_of(table_body)
        )
        next_button.click()
        
    except StaleElementReferenceException:
        # 遇到过时元素异常时,跳过当前循环重新执行
        continue

额外建议

  • 开启浏览器无头模式(options.add_argument('--headless=new')),减少渲染开销
  • 定期将数据写入文件,避免字典内存占用过高影响性能
  • 给WebDriver设置页面加载超时时间,防止页面无限卡住

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 16:46:14