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

Python中Selenium StaleElementReferenceException问题排查求助

问题:Selenium操作Cookie Clicker时随机出现StaleElementReferenceException错误

我在完成Angela的《100 Days of Python》课程项目,用Selenium持续点击http://orteil.dashnet.org/experiments/cookie/页面的Cookie,每5秒检查右侧升级项并购买当前能负担的最贵升级。但运行代码时随机抛出以下错误:

selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: element is not attached to the page document

我的代码如下:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import time

service = Service("~/Downloads/chromedriver_mac64/chromedriver")
driver = webdriver.Chrome(service=service)
driver.get("http://orteil.dashnet.org/experiments/cookie/")

def format_items():
    """Format the data in [{name, cost, element (to click)}]"""
    store_items = driver.find_elements("css selector", "#store div b")
    items_ = []
    for store_item in store_items:
        if store_item.text != "":
            description = store_item.text.split(" - ")
            name = description[0]
            cost = int(description[1].replace(",", ""))
            element = store_item

            items_.append({"name": name, "cost": cost, "element": element})
    return items_

cookie = driver.find_element("id", "cookie")


def find_most_expensive():
    affordable_upgrades = []
    for item in items:
        cost = item["cost"]
        if cost <= cookie_count:
            affordable_upgrades.append(item)

    expensive_affordable_cost = 0
    expensive_affordable_dict = {}
    for upgrade in affordable_upgrades:
        if upgrade["cost"] > expensive_affordable_cost:
            expensive_affordable_cost = upgrade["cost"]
            expensive_affordable_dict = upgrade

    return expensive_affordable_dict


base = time.time() + 5
while True:
    cookie.click()
    items = format_items()
    if time.time() >= base:
        cookie_count = int(driver.find_element("id", "money").text.replace(",", ""))
        most_expensive_item = find_most_expensive()
        if len(most_expensive_item) != 0:
            most_expensive_item["element"].click()
        base += 5

driver.quit()

我已在循环中每次调用format_items()重新获取元素,也查阅过相关文章但未解决,恳请帮忙排查错误原因。


错误原因及修复方案

核心原因

你遇到的StaleElementReferenceException是因为:当点击升级项后,页面的商店区域(#store)会重新渲染(比如新增升级项、更新已有项的状态),导致之前获取的WebElement对象与当前DOM文档脱节,引用失效。哪怕每次循环调用format_items(),在两次检查升级的间隔内,页面可能已经发生更新,导致后续要点击的元素引用过期。

具体问题点

  1. 直接保存WebElement对象到字典中,该对象的生命周期与DOM绑定,页面更新后引用立即失效。
  2. 每次点击Cookie后都调用format_items(),频繁获取元素但未及时使用,增加了元素引用过期的概率。
  3. 原代码中点击的是<b>标签元素,而页面实际可点击的升级区域是其父<div>标签,点击<b>可能触发不稳定的DOM更新。

修复方案

  1. 不保存WebElement,改用元素定位标识:比如保存每个升级项的父div的索引或CSS选择器,需要点击时再重新查找元素,确保引用最新。
  2. 调整format_items()逻辑:获取每个升级项的父div,提取文本信息并保存定位方式,而非元素本身。
  3. 优化获取时机:只在需要检查升级的5秒间隔到来时,才获取最新的升级项数据,避免无效的元素引用。

修改后的代码

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import time

service = Service("~/Downloads/chromedriver_mac64/chromedriver")
driver = webdriver.Chrome(service=service)
driver.get("http://orteil.dashnet.org/experiments/cookie/")

def format_items():
    """Format the data in [{name, cost, index}]"""
    store_divs = driver.find_elements("css selector", "#store div")
    items_ = []
    for index, div in enumerate(store_divs):
        b_element = div.find_element("tag name", "b")
        text = b_element.text.strip()
        if text != "":
            description = text.split(" - ")
            name = description[0]
            cost = int(description[1].replace(",", ""))
            items_.append({"name": name, "cost": cost, "index": index})
    return items_

cookie = driver.find_element("id", "cookie")

def find_most_expensive(available_items, current_cookies):
    affordable_upgrades = [item for item in available_items if item["cost"] <= current_cookies]
    if not affordable_upgrades:
        return None
    # 按成本降序排序,取第一个
    affordable_upgrades.sort(key=lambda x: x["cost"], reverse=True)
    return affordable_upgrades[0]

base = time.time() + 5
while True:
    cookie.click()
    if time.time() >= base:
        cookie_count = int(driver.find_element("id", "money").text.replace(",", ""))
        items = format_items()
        most_expensive_item = find_most_expensive(items, cookie_count)
        if most_expensive_item:
            # 根据索引重新获取对应div并点击
            target_div = driver.find_elements("css selector", "#store div")[most_expensive_item["index"]]
            target_div.click()
        base += 5

driver.quit()

代码说明

  • format_items()现在保存每个升级项的父div索引,而非WebElement,避免引用过期。
  • find_most_expensive()改用列表推导和排序简化逻辑,更清晰。
  • 仅在5秒检查间隔到来时才获取items和cookie_count,减少不必要的DOM操作,降低元素失效概率。
  • 点击升级项时,通过索引重新查找父div元素,确保每次点击的都是当前DOM中有效的元素。

内容的提问来源于stack exchange,提问作者Daniel Crompton

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 18:55:44