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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 14:15:04