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

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()

关键修改点说明

  1. 光标移动逻辑:先定位到图表左上角,再用move_by_offset逐步移动,彻底解决起始偏移和边界报错;
  2. Shadow Root处理:用显式等待确保元素加载完成,通过aria-label特征定位关闭按钮,兼容性更好;
  3. 稳定性优化:全程用WebDriverWait替代time.sleep和盲目try-except,提升脚本稳定性;
  4. 宽度计算:用clientWidth获取元素实际可交互宽度,避免超出边界。

备注:内容来源于stack exchange,提问作者FaisalShakeel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 15:54:27