使用Python+Selenium爬取动态表格遇问题:过时元素异常与性能下降
问题描述
我是编程新手,还需学习很多内容,恳请各位耐心解答。
我正在用Python和Selenium爬取某网站的动态表格数据,表格采用无刷新分页模式(切换页面时URL不变,仅表格内容更新)。为获取刷新后的表格数据并避免Stale Element Exception(过时元素异常),我用了WebDriverWait结合expected_conditions等待<tr>元素可见,但仍无法获取新数据,反而拿到上一页旧数据并触发异常。添加time.sleep()点击下一页按钮后暂时解决了问题,但爬取到约120页时,代码性能逐渐下降,再次出现过时元素异常,无法获取更新后的数据。
我尝试过设置隐式等待、逐步增加time.sleep()时长,但均无效果。该表格每页有100行数据,总计约3100页。
核心问题:
- 为何会出现Stale Element Exception,该如何避免?
- 如何提升代码的爬取效率?
我已自行搜索并尝试解决未果,特此求助,感谢您的时间与帮助。
以下是我的代码:
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
相关产品推荐
相关产品推荐

