Selenium点击单选按钮触发StaleElementReferenceException错误求助
错误原因
你遇到的StaleElementReferenceException是Selenium常见异常,核心原因是:你预先将页面元素查询后存储到了列表变量中,只要点击任意单选按钮触发页面DOM更新/重构,之前缓存的元素引用就会和当前页面DOM解绑,再次操作就会抛出该异常。
此外代码中大量使用硬sleep等待,也会增加页面未加载完成就操作元素的概率,加剧该问题。
修复方案
核心原则是:不要缓存需要循环点击的元素列表,每次操作前重新查询最新的DOM元素,配合显式等待替代硬sleep,确保操作时元素有效。
主要修改点如下:
- 遍历颜色、内存、资费单选按钮时,每次循环都重新查询对应元素列表,不使用预先查询的缓存列表
- 点击操作后添加对应状态的显式等待,确认页面更新完成再进行后续操作
- 操作元素前先校验元素是否可点击、是否挂载在DOM上
核心修改后代码片段
import xlwt from selenium import webdriver import re import time from datetime import date 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.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.keys import Keys class telekommobiles: def __init__(self): self.url="https://www.telekom.de/mobilfunk/geraete/smartphone?page=1&pageFilter=promotion" self.country='DE' self.currency='EUR' # 修正货币单位,德国使用欧元 self.VAT='Included' self.shipping = 'free shipping within 3-4 weeks' self.Pre_PromotionPrice ='N/A' self.color ='N/A' self.wait = None # 统一显式等待对象 def safe_click(self, by, locator, timeout=10): """封装安全点击方法,避免stale异常""" ele = WebDriverWait(self.driver, timeout).until(EC.element_to_be_clickable((by, locator))) # 确保元素没有过时 WebDriverWait(self.driver, timeout).until(EC.staleness_of(ele) == False) ele.click() return ele def telekom(self): self.driver=webdriver.Chrome() self.wait = WebDriverWait(self.driver, 30) self.driver.maximize_window() self.driver.get(self.url) today = date.today() # 接受cookie self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'button.cl-btn.cl-btn--accept-all'))).click() print("cookies accepted") product_links = [] containers = self.wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'div[class="styles_item__12Aw4"]'))) for container in containers: p_links =container.find_element(By.TAG_NAME, 'a').get_attribute('href') product_links.append(p_links) for links in product_links: self.driver.get(links) # 遍历颜色:每次循环重新查颜色列表 color_count = len(self.wait.until(EC.presence_of_all_elements_located((By.XPATH,"//li[@data-qa='list_ColorVariant']")))) for color_idx in range(color_count): # 每次都重新获取最新的颜色列表 color_options = self.wait.until(EC.presence_of_all_elements_located((By.XPATH,"//li[@data-qa='list_ColorVariant']"))) color_options[color_idx].click() # 等待颜色选中生效 self.wait.until(lambda d: 'is-selected' in color_options[color_idx].get_attribute('class')) time.sleep(1) # 短等待保证DOM更新 # 遍历内存:每次循环重新查内存列表 memory_count = len(self.wait.until(EC.presence_of_all_elements_located((By.XPATH,"//span[@class='phx-radio__element']")))) for mem_idx in range(memory_count): memory_options = self.wait.until(EC.presence_of_all_elements_located((By.XPATH,"//span[@class='phx-radio__element']"))) try: memory_options[mem_idx].click() self.wait.until(lambda d: memory_options[mem_idx].is_selected()) except: continue time.sleep(1) # 打开资费修改页 self.safe_click(By.CSS_SELECTOR, 'button[class="phx-link phx-list-of-links__link js-mod tracking-added"]') # 等待资费弹窗加载完成 self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'section[class="tariff-catalog--layer"]'))) section_loops = self.driver.find_elements(By.CSS_SELECTOR, 'section[class="tariff-catalog--layer"]') for section_loop in section_loops: heading_1 = section_loop.find_element(By.CSS_SELECTOR, 'h2[class="page-title page-title--lowercase"]').text print(heading_1) subcontainers = section_loop.find_elements(By.CSS_SELECTOR, 'div[class="phx-tariff-box__section"]') for sub_idx in range(len(subcontainers)): # 每次循环重新获取子容器,避免DOM更新后失效 subcontainers = section_loop.find_elements(By.CSS_SELECTOR, 'div[class="phx-tariff-box__section"]') subcontainer = subcontainers[sub_idx] ActionChains(self.driver).move_to_element(subcontainer).perform() radio_buttons_list=subcontainer.find_elements(By.CSS_SELECTOR, 'div[class="phx-form__row phx-form__row--small phx-form__row--full-width phx-form__row--radio"]') for radio_idx in range(len(radio_buttons_list)): # 每次重新获取单选按钮列表 radio_buttons_list=subcontainer.find_elements(By.CSS_SELECTOR, 'div[class="phx-form__row phx-form__row--small phx-form__row--full-width phx-form__row--radio"]') radio = radio_buttons_list[radio_idx] input_ele = radio.find_element(By.CSS_SELECTOR, 'span[class="phx-radio__element"]') if not input_ele.is_enabled(): continue try: ActionChains(self.driver).move_to_element(input_ele).click().perform() # 等待单选按钮选中 self.wait.until(lambda d: input_ele.is_selected()) time.sleep(1) except Exception as e: print(f'点击失败: {str(e)}') continue label = radio.find_element(By.CSS_SELECTOR, 'span[class="phx-radio__label"]').text if radio.find_elements(By.CSS_SELECTOR, 'span[class="phx-radio__label"]') else "" heading_2 = subcontainer.find_element(By.CSS_SELECTOR, 'p[class="phx-t6 phx-t--medium"]').text data_price_list= subcontainer.find_element(By.CSS_SELECTOR, 'div[class="phx-tariff-box__data-price"]') volumn = data_price_list.find_element(By.CSS_SELECTOR, 'div[data-qa="label_Tariff_VolumeSize"]').text if data_price_list.find_elements(By.CSS_SELECTOR, 'div[data-qa="label_Tariff_VolumeSize"]') else "" price = subcontainer.find_element(By.CSS_SELECTOR, 'p[class="phx-price phx-price--size_large phx-price--strong phx-price--color_brand"]').text if subcontainer.find_elements(By.CSS_SELECTOR, 'p[class="phx-price phx-price--size_large phx-price--strong phx-price--color_brand"]') else "" volumn = volumn.replace(u'\xa0', '').replace('\r\n','') print(f"{heading_2} {label} {volumn} {price}") # 关闭资费弹窗回到产品页,避免影响下一次内存/颜色遍历 self.driver.execute_script("document.querySelector('.tariff-catalog--layer').remove()") time.sleep(1) if __name__ == '__main__': telekom_de=telekommobiles() telekom_de.telekom()
额外优化建议
- 封装统一的元素获取、点击方法,避免重复写显式等待逻辑
- 可以适当调整等待时长,平衡抓取速度和稳定性
- 不要用空的try-except吞掉所有异常,方便排查问题
内容的提问来源于stack exchange,提问作者vinitha
相关产品推荐
相关产品推荐

