Python Selenium如何等待元素消失或变为stale状态 解决自动化脚本报错
Selenium 动态闪烁元素等待方案(记忆答题游戏实现)
场景说明
我正尝试通过代码实现地址 大脑记忆游戏 的答题逻辑,该游戏会连续展示多个闪烁数字,要求用户按倒序输入看到的数字。
游戏规则
- 进入网站后有一个启动按钮,蓝色进度条会从左到右填满按钮,进度条填满前点击按钮不会触发逻辑,启动按钮ID为
start,实测time.sleep(3)足够等待进度条加载完成 - 点击启动按钮后页面刷新,首先展示提示:
The numbers are coming soon. Please pay attention...
- 之后会出现多个闪烁数字,数字通过调整透明度实现闪烁效果,从完全可见过渡到完全不可见,一个数字消失后下一个数字才会出现,即使数值相同也是不同的DOM元素
- 所有数字展示完成后会出现提示,同时展示0-9的数字键盘:
Please click on the number you just saw IN REVERSE ORDER.
- 按倒序点击对应数字后,会出现展示答题结果的按钮,点击后进入下一题,总共有10道题。
元素结构
<div class="questionWrapper"> <div class="question"> <div class="gnumber_title"> <div class="gend-tip">Please click on the number you just saw IN REVERSE ORDER.</div> </div> <div class="gnumber_btns" id="gbtn0" style=""><button data-n="9">9</button><button data-n="8">8</button><button data-n="7">7</button><button data-n="6">6</button><button data-n="5">5</button><button data-n="4">4</button><button data-n="3">3</button><button data-n="2">2</button><button data-n="1">1</button><button data-n="0">0</button></div> </div> <div class="answer" value="10">1</div> </div>
元素交互逻辑
- 所有题目都被
questionWrapper类的div包裹,每个题目块内仅有一个gnumber_title类的元素,用于承载提示文本和闪烁数字 - 该元素内任何时候都仅存在一个子元素,子元素的内容随时间变化,但相对于
gnumber_title的XPath统一为./div - 答题阶段开始时,第一个提示文本对应的子元素类名为
gstart-tip blink_me - 该元素消失后会替换为类名
gflash-num的元素,其内容可通过.text属性获取 - 该元素通过修改样式实现闪烁:样式从
opacity: 1.000000;逐步变为opacity: 0.000000;,之后变为display: none;,最后被删除并替换为下一个gflash-num实例 - 所有
gflash-num元素展示完成后,会出现第二个提示文本,类名为gend-tip,和之前的提示、数字位于同一个XPath位置,同时类名为gnumber_btns的数字键盘变为可见 - 点击和展示数字数量相同的按钮后,类名为
answer的结果按钮会出现,点击进入下一题
原有实现&报错
原有代码
import time from selenium import webdriver Firefox = webdriver.Firefox() Firefox.get('https://www.arealme.com/brain-memory-game/en/') time.sleep(3) Firefox.find_element_by_id('start').click() questions = Firefox.find_elements_by_class_name("questionWrapper") for q in questions: numbers = [] title = q.find_element_by_class_name('gnumber_title') while True: if title.find_element_by_xpath('./div').get_attribute('class') != 'gstart-tip blink_me': break while True: if title.find_element_by_xpath('./div').get_attribute('class') == "gend-tip": break numbers.append(title.find_element_by_class_name('gflash-num').text) while True: if not title.find_element_by_class_name('gflash-num').get_attribute('style').startswith('opacity'): break while True: if not title.find_element_by_class_name('gflash-num').get_attribute('style').startswith('display'): break buttons = q.find_element_by_class_name("gnumber_btns") for n in reversed(numbers): buttons.find_element_by_xpath(f'.//*[text() = "{n}"]').click() time.sleep(0.5) q.find_element_by_class_name("answer").click()
报错信息
--------------------------------------------------------------------------- StaleElementReferenceException Traceback (most recent call last) <ipython-input-1-9d34e88c4046> in <module> 20 numbers.append(title.find_element_by_class_name('gflash-num').text) 21 while True: ---> 22 if not title.find_element_by_class_name('gflash-num').get_attribute('style').startswith('opacity'): 23 break 24 while True: c:\program files\python39\lib\site-packages\selenium\webdriver\remote\webelement.py in get_attribute(self, name) 137 attributeValue = '' 138 if self._w3c: --> 139 attributeValue = self.parent.execute_script( 140 "return (%s).apply(null, arguments);" % getAttribute_js, 141 self, name) c:\program files\python39\lib\site-packages\selenium\webdriver\remote\webdriver.py in execute_script(self, script, *args) 632 command = Command.EXECUTE_SCRIPT 633 --> 634 return self.execute(command, { 635 'script': script, 636 'args': converted_args})['value'] c:\program files\python39\lib\site-packages\selenium\webdriver\remote\webdriver.py in execute(self, driver_command, params) 319 response = self.command_executor.execute(driver_command, params) 320 if response: --> 321 self.error_handler.check_response(response) 322 response['value'] = self._unwrap_value( 323 response.get('value', None)) c:\program files\python39\lib\site-packages\selenium\webdriver\remote\errorhandler.py in check_response(self, response) 240 alert_text = value['alert'].get('text') 241 raise exception_class(message, screen, stacktrace, alert_text) ---> 242 raise exception_class(message, screen, stacktrace) 243 244 def _value_or_default(self, obj, key, default): StaleElementReferenceException: Message: The element reference of <div class="gflash-num"> is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed
问题原因&解决方案
问题原因
原有代码采用死循环反复查询动态元素的属性,当元素刚好在查询瞬间被DOM移除时,就会触发StaleElementReferenceException。
解决方案
使用Selenium内置的显式等待工具,配合staleness_of预期条件,直接等待指定元素失效(从DOM中移除),该方法内部已经做了异常捕获,不会触发上述报错。
首先导入依赖:
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By
修改后的核心逻辑:
import time from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By Firefox = webdriver.Firefox() Firefox.get('https://www.arealme.com/brain-memory-game/en/') # 全局显式等待实例,超时时间设为10秒 wait = WebDriverWait(Firefox, 10) time.sleep(3) wait.until(EC.element_to_be_clickable((By.ID, 'start'))).click() questions = Firefox.find_elements(By.CLASS_NAME, "questionWrapper") for q in questions: numbers = [] title = q.find_element(By.CLASS_NAME, 'gnumber_title') # 1. 等待初始提示元素消失 start_tip = title.find_element(By.XPATH, './div') wait.until(EC.staleness_of(start_tip)) # 2. 收集闪烁数字 while True: current_div = title.find_element(By.XPATH, './div') # 判断是否进入答题阶段 if current_div.get_attribute('class') == 'gend-tip': break # 记录当前数字 numbers.append(current_div.text) # 等待当前数字元素被移除 wait.until(EC.staleness_of(current_div)) # 3. 倒序点击数字 buttons = q.find_element(By.CLASS_NAME, "gnumber_btns") for n in reversed(numbers): # 用data-n属性匹配比文本匹配更可靠 buttons.find_element(By.XPATH, f'.//button[@data-n="{n}"]').click() # 4. 等待结果按钮可点击后进入下一题 wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'answer'))).click()
如果追求更简单的实现,结合实测的固定时长,也可以直接用固定等待替换显式等待:初始提示后sleep(3),每收集一个数字后sleep(1),也能避开元素删除的时间点,不会触发报错。
内容的提问来源于stack exchange,提问作者Ξένη Γήινος
相关产品推荐
相关产品推荐

