SPA网站Selenium点击触发StaleElementReferenceException问题求助
解决方案:规避SPA页面的StaleElementReferenceException问题
你的核心问题在于一次性获取所有用户名元素后,点击第一个用户触发SPA重新渲染DOM,导致之前获取的元素全部失效。以下是针对性的修复方案:
关键改进思路
- 不要预先批量获取元素列表,而是按索引逐个重新定位元素,避免旧引用失效
- 点击前先提取用户名文本,防止DOM刷新后无法获取
- 对单个元素操作做异常捕获,失败后重试当前元素
- 处理分页逻辑,确保能抓取全部1000个用户
修复后的代码
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.common.exceptions import StaleElementReferenceException, TimeoutException import json import random import time def scrape_user_rosters(driver): parent_dict = {"data": []} # 核心元素定位(需根据页面实际结构微调) USERNAME_XPATH = "//span[@class='hidden lg:flex font-semibold']" ROSTER_XPATH = "//span[@class='text-white hidden lg:flex font-semibold']" TITLE_USER_XPATH = "//div[@class='flex flex-col space-y-2']/span" NEXT_PAGE_XPATH = "//button[contains(@class, 'pagination-next')]" # 替换为实际翻页按钮的定位 page_num = 1 while True: try: # 等待当前页用户列表完全可见 WebDriverWait(driver, 20).until( EC.visibility_of_all_elements_located((By.XPATH, USERNAME_XPATH)) ) current_page_user_count = len(driver.find_elements(By.XPATH, USERNAME_XPATH)) # 逐个处理当前页的用户 for index in range(current_page_user_count): retry_count = 0 max_retries = 3 while retry_count < max_retries: try: # 每次循环重新定位当前索引的用户元素,避免stale引用 target_user = WebDriverWait(driver, 15).until( EC.element_to_be_clickable((By.XPATH, f"({USERNAME_XPATH})[{index+1}]")) ) # 提前获取用户名文本,防止点击后DOM刷新丢失引用 username = target_user.text.strip() if not username: break target_user.click() # 等待阵容数据加载完成 WebDriverWait(driver, 15).until( EC.visibility_of_all_elements_located((By.XPATH, ROSTER_XPATH)) ) # 提取阵容数据 roster = [elem.text.strip() for elem in driver.find_elements(By.XPATH, ROSTER_XPATH)] # 保存数据 parent_dict["data"].append({ "username": username, "roster": roster }) print(f"已抓取: 第{page_num}页 - {username}") # 返回用户列表页面(根据实际页面调整:如果是弹窗则关闭,SPA路由切换则等待列表重新加载) # 示例:如果是弹窗,添加关闭按钮定位并点击 # close_btn = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.XPATH, "//button[@aria-label='Close']"))) # close_btn.click() # 等待用户列表重新渲染完成 WebDriverWait(driver, 15).until( EC.visibility_of_all_elements_located((By.XPATH, USERNAME_XPATH)) ) break except StaleElementReferenceException: retry_count += 1 print(f"用户{index+1}引用失效,重试第{retry_count}次...") time.sleep(random.uniform(0.5, 1.5)) continue except TimeoutException: print(f"用户{index+1}加载超时,跳过") break # 处理翻页 try: next_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.XPATH, NEXT_PAGE_XPATH)) ) # 检查是否为最后一页(如果按钮禁用则停止) if "disabled" in next_btn.get_attribute("class"): print("已到最后一页,结束抓取") break next_btn.click() page_num += 1 print(f"切换到第{page_num}页") # 等待旧页面元素失效,新页面加载完成 WebDriverWait(driver, 15).until( EC.staleness_of(driver.find_element(By.XPATH, USERNAME_XPATH)) ) time.sleep(random.uniform(1, 2)) except TimeoutException: print("没有更多页面,结束抓取") break except Exception as e: print(f"第{page_num}页处理出错: {str(e)}") break # 保存数据(每100条自动保存一次,防止崩溃丢失) with open('user_rosters.json', 'w', encoding='utf-8') as f: json.dump(parent_dict, f, indent=2, ensure_ascii=False) print(f"抓取完成,共获取{len(parent_dict['data'])}条数据") return parent_dict
额外注意事项
- 需根据页面实际结构调整翻页按钮和弹窗关闭按钮的定位表达式
- 添加随机延迟
random.uniform(),避免触发网站反爬机制 - 可添加数据增量保存逻辑,比如每抓取100条就写入一次文件
- 确保登录状态持久有效,SPA可能会在会话过期后自动登出,需添加登录重试逻辑
内容的提问来源于stack exchange,提问作者neo5_50
相关产品推荐
相关产品推荐

