Selenium爬取XE汇率图表数据:光标起始偏移、边界报错及Shadow Root内模态框无法关闭问题
Selenium爬取XE汇率图表数据:光标起始偏移、边界报错及Shadow Root内模态框无法关闭问题
我帮你拆解并解决这两个核心问题,先看每个问题的根因和修复方案:
一、光标起始偏移与边界报错问题
你遇到的光标从中间开始移动、后期报MoveTargetOutOfBoundsException,核心原因是**move_to_element_with_offset的偏移量是相对于元素的中心点,而非左上角**!而且直接用element.size['width']作为最大偏移量也不准确,因为元素可能存在内边距、滚动区域或者实际可交互的宽度和元素尺寸有差异。
修复方案:
- 先获取元素的绝对左上角坐标,用
move_to_element先定位到元素左上角; - 计算元素的实际可交互宽度(用
clientWidth比size['width']更准确); - 用
move_by_offset从左上角开始逐步横向移动,避免超出元素实际边界。
二、Shadow Root内模态框无法关闭问题
你的脚本已经检测到模态框宿主元素,但找不到关闭按钮,可能是因为:
- 没有等待Shadow Root内的元素加载完成;
- 选择器不够精准,或者Shadow Root的获取方式需要调整;
- 你注入的
attachShadow覆盖脚本只对新创建的Shadow Root生效,页面加载时已存在的Shadow Root还是关闭状态。
修复方案:
- 用显式等待
WebDriverWait等待模态框宿主元素出现,再获取Shadow Root; - 在Shadow Root内用更精准的选择器定位关闭按钮(比如通过按钮的
aria-label特征); - 直接用
execute_script获取已存在的Shadow Root,避免依赖覆盖脚本的局限性。
整合后的完整代码
from selenium.webdriver.chrome.options import Options from shutil import which from selenium import webdriver import time import os from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC BASE_DIR = os.getcwd() chrome_path = which("chromedriver") options = Options() options.add_argument("--log-level=3") options.add_argument("--start-maximized") options.add_argument("--remote-debugging-port=9222") service = Service(executable_path=chrome_path) driver = webdriver.Chrome(service=service, options=options) # 注入脚本强制所有新Shadow Root为open模式 driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {'source': """ Element.prototype._attachShadow = Element.prototype.attachShadow; Element.prototype.attachShadow = function () { return this._attachShadow( { mode: "open" } ); }; """} ) # 加载目标页面 driver.get('https://www.xe.com/currencycharts/?from=SGD&to=USD&view=5Y') driver.implicitly_wait(10) # 处理Cookie弹窗 try: accept_cookies_button = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//button[contains(@class, 'kpuPwJ')]")) ) accept_cookies_button.click() except Exception as e: print("Cookie弹窗未出现或无法点击:", e) # 等待图表元素加载完成 graph_element = WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.XPATH, "//div[@class='recharts-wrapper']")) ) # 获取元素的实际可交互宽度(用clientWidth更准确) element_width = driver.execute_script("return arguments[0].clientWidth", graph_element) actions = ActionChains(driver) # 先移动到图表元素的左上角(偏移量相对于元素左上角,x和y都设为0) actions.move_to_element_with_offset(graph_element, 0, 0).perform() time.sleep(0.5) ad_closed = False # 从左上角开始逐步移动,步长10px,最大不超过元素宽度-10(避免边界) for x_offset in range(0, element_width - 10, 10): # 处理模态框(只处理一次) if not ad_closed: try: # 等待模态框宿主元素出现 ad_host = WebDriverWait(driver, 3).until( EC.presence_of_element_located((By.CSS_SELECTOR, "yld-tag-host-campaign")) ) # 获取Shadow Root shadow_root = driver.execute_script("return arguments[0].shadowRoot", ad_host) if shadow_root: # 定位关闭按钮(通过aria-label特征,兼容性更好) close_btn = WebDriverWait(shadow_root, 5).until( EC.element_to_be_clickable((By.CSS_SELECTOR, "button[aria-label*='close']")) ) close_btn.click() ad_closed = True print("模态框已关闭") except Exception as e: # 没出现模态框就继续执行 pass # 从当前位置横向移动指定偏移量 actions.move_by_offset(10, 0).perform() time.sleep(0.1) # 获取 tooltip 数据 try: tooltip_paragraphs = WebDriverWait(driver, 2).until( EC.presence_of_all_elements_located((By.XPATH, "//div[contains(@class, 'recharts-tooltip-wrapper')]/div/p")) ) for p in tooltip_paragraphs: print(p.text) except Exception as e: # 某些位置可能没有tooltip,跳过即可 pass # 关闭浏览器 driver.quit()
关键修改点说明
- 光标移动逻辑:先定位到图表左上角,再用
move_by_offset逐步移动,彻底解决起始偏移和边界报错; - Shadow Root处理:用显式等待确保元素加载完成,通过
aria-label特征定位关闭按钮,兼容性更好; - 稳定性优化:全程用
WebDriverWait替代time.sleep和盲目try-except,提升脚本稳定性; - 宽度计算:用
clientWidth获取元素实际可交互宽度,避免超出边界。
备注:内容来源于stack exchange,提问作者FaisalShakeel
相关产品推荐
相关产品推荐

