Selenium 爬取 Canvas 渲染的数据图表

在数据可视化场景中,很多平台会采用 Canvas 技术渲染动态数据图表(如 ECharts、Highcharts、Chart.js 等),这类图表的核心数据并不会直接出现在 HTML DOM 节点中,传统的静态网页爬取方式无法获取有效数据。Selenium 作为主流的自动化测试工具,能模拟浏览器完整的渲染过程,精准捕获 Canvas 图表背后的原始数据,成为爬取这类动态渲染图表的最优解之一。本文将从核心原理出发,详细讲解 Selenium 爬取 Canvas 图表的实现步骤、关键技巧及实战案例,帮助开发者高效解决 Canvas 数据爬取难题。

一、Canvas 图表爬取的核心难点

传统网页的文本、列表等数据会直接嵌入 HTML 的标签属性或文本节点中,通过解析 DOM 树即可轻松提取;而Canvas 是基于像素的画布渲染技术,其工作原理是浏览器执行前端 JavaScript 代码,将原始数据计算后绘制为像素图形展示在 Canvas 画布上。

这一特性导致两个核心爬取难点:

  1. Canvas 标签本身仅作为绘图容器,<canvas>节点内无任何数据相关的 DOM 内容,无法通过 XPath、CSS 选择器直接提取数据;
  2. 图表数据仅存在于浏览器的 JavaScript 执行环境(内存)中,静态爬虫(如 Requests+BeautifulSoup)无法执行 JS 代码,自然无法获取渲染所需的原始数据。

二、Selenium 的核心优势:模拟浏览器完整渲染

Selenium 是一款用于 Web 应用自动化测试的工具,其核心能力是驱动真实浏览器(Chrome、Firefox 等)完成页面的加载、JS 执行、DOM 渲染和事件触发,完全模拟人类操作浏览器的行为。

针对 Canvas 图表爬取,Selenium 的核心优势体现在:

  1. 等待页面完全渲染:可配置显式等待,确保图表对应的 JS 代码执行完毕、Canvas 绘图完成,避免因数据未加载导致的爬取失败;
  2. 访问浏览器 JS 执行环境:支持直接在当前页面执行自定义 JavaScript 代码,突破 DOM 解析的限制,直接从 JS 环境中提取 Canvas 图表的原始数据;
  3. 适配所有 Canvas 图表库:无论使用 ECharts、Highcharts 还是原生 Canvas 开发的图表,只要浏览器能渲染,Selenium 就能捕获其背后的数据。

三、关键技术:execute_script 方法执行 JS 代码

Selenium 爬取 Canvas 图表的核心技术核心execute_script()方法,该方法允许开发者在 Selenium 驱动的浏览器上下文中,执行任意合法的 JavaScript 代码,并能将 JS 执行结果返回给 Python 程序。

方法核心作用

  1. 桥接 Python 与浏览器 JS 环境:Python 代码运行在本地进程,而 Canvas 数据存在于浏览器的 JS 进程,execute_script()是两者之间的唯一数据通道;
  2. 直接操作浏览器内存数据:通过 JS 代码访问页面中定义的图表实例、数据变量,无需解析 DOM,直接提取原始结构化数据(数组、对象等);
  3. 支持复杂 JS 逻辑执行:可在方法中编写多行 JS 代码,完成数据筛选、格式转换等操作,再将处理后的数据返回。

基础语法

python

运行

复制代码
# 执行简单JS代码,无返回值
driver.execute_script("console.log('执行JS代码')")

# 执行JS代码并获取返回值,data为Python变量,接收JS返回的结果
data = driver.execute_script("""
    // 这里编写提取Canvas数据的JS代码
    return 图表原始数据;
""")

四、完整实战步骤(以 ECharts Canvas 图表为例)

ECharts 是国内最主流的 Canvas 可视化库,本文以 ECharts 渲染的折线图 / 柱状图为例,讲解从环境准备到数据提取的完整流程,其他 Canvas 图表库(Highcharts、Chart.js)实现逻辑一致,仅 JS 数据提取代码略有差异。

步骤 1:环境准备与依赖安装

1.1 安装核心 Python 库

Selenium 核心库用于驱动浏览器,webdriver-manager 用于自动管理浏览器驱动(无需手动下载、配置驱动路径):

bash

运行

复制代码
pip install selenium webdriver-manager
1.2 确认浏览器版本

确保本地安装了 Chrome/Firefox 浏览器,Selenium 会通过 webdriver-manager 自动匹配对应版本的驱动,无需额外操作。

步骤 2:编写 Python 核心代码

核心逻辑:驱动浏览器打开目标页面→等待 Canvas 图表渲染完成→通过execute_script()执行 JS 代码提取原始数据→解析并保存数据。

完整可运行代码(Chrome 浏览器)

python

运行

复制代码
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
import json

# 1. 配置浏览器驱动,初始化Chrome浏览器
driver = webdriver.Chrome(
    service=Service(ChromeDriverManager().install())
)
# 最大化浏览器窗口,避免渲染异常
driver.maximize_window()

try:
    # 2. 打开目标页面(替换为实际的Canvas图表页面URL)
    target_url = "https://echarts.apache.org/examples/zh/editor.html?c=line-basic"
    driver.get(target_url)

    # 3. 显式等待:确保Canvas元素渲染完成(关键,避免数据未加载)
    # 等待条件:页面中出现Canvas标签,超时时间10秒
    wait = WebDriverWait(driver, 10)
    canvas_elem = wait.until(
        EC.presence_of_element_located((By.TAG_NAME, "canvas"))
    )
    print("Canvas图表渲染完成,开始提取数据...")

    # 4. 核心:执行JS代码提取ECharts原始数据,通过return返回给Python
    # ECharts图表实例通常挂载在window对象,或通过echarts.getInstanceByDom获取
    extract_js = """
        // 方式1:通过ECharts内置方法获取图表实例(推荐,适配绝大多数ECharts场景)
        const canvasDom = document.querySelector('canvas');
        const chartInstance = echarts.getInstanceByDom(canvasDom);
        if (!chartInstance) {
            return null; // 未找到图表实例,返回空
        }
        // 获取图表的完整配置项,其中包含原始数据(series是核心数据区)
        const chartOption = chartInstance.getOption();
        // 提取核心数据:x轴标签 + 系列数据(可根据需求自定义提取字段)
        const xAxisData = chartOption.xAxis[0].data; // x轴坐标数据
        const seriesData = chartOption.series; // 图表系列数据(折线图/柱状图的核心数值)
        // 构造结构化数据,方便Python解析
        const result = {
            x_axis: xAxisData,
            series: seriesData
        };
        return result;
    """
    # 执行JS代码,获取返回的原始数据(自动转换为Python字典/列表)
    chart_data = driver.execute_script(extract_js)

    # 5. 解析并处理数据
    if chart_data:
        print("✅ 成功提取Canvas图表原始数据:")
        # 格式化输出数据,方便查看
        print(json.dumps(chart_data, ensure_ascii=False, indent=2))
        
        # 保存数据到本地JSON文件(持久化存储)
        with open("canvas_chart_data.json", "w", encoding="utf-8") as f:
            json.dump(chart_data, f, ensure_ascii=False, indent=2)
        print("📁 数据已保存到canvas_chart_data.json文件")
    else:
        print("❌ 未提取到Canvas图表数据,可能是图表实例未找到或页面结构不同")

except Exception as e:
    print(f"爬取过程出现异常:{str(e)}")

finally:
    # 6. 关闭浏览器,释放资源
    driver.quit()
    print("浏览器已关闭,爬取流程结束")

步骤 3:代码关键说明

  1. 显式等待的必要性 :必须等待 Canvas 元素或图表相关 JS 执行完毕,否则execute_script()执行时可能找不到图表实例,导致提取失败;
  2. JS 提取逻辑适配 :不同 Canvas 图表库的实例获取方式不同(如 Highcharts 通过Highcharts.charts[0]获取实例),需根据目标页面的图表库调整extract_js中的代码;
  3. 数据结构化 :JS 中构造result对象时,按需提取核心字段(如 x 轴、y 轴、系列名称、数值等),避免返回冗余数据。

步骤 4:扩展适配其他 Canvas 图表库

1. Highcharts 图表(JS 提取代码)

javascript

运行

复制代码
// Highcharts图表实例通常存储在window.Highcharts.charts数组中
const chartInstance = window.Highcharts.charts[0];
if (!chartInstance) return null;
// 提取核心数据
const xAxisData = chartInstance.xAxis[0].categories;
const seriesData = chartInstance.series.map(s => ({
    name: s.name,
    data: s.data
}));
return {x_axis: xAxisData, series: seriesData};
2. 原生 Canvas 图表(JS 提取代码)

原生 Canvas 图表的数据源通常是页面中自定义的 JS 变量(如window.chartDatavar data = [...]),直接访问对应变量即可:

javascript

运行

复制代码
// 假设原生Canvas的数据源挂载在window.chartData变量中
return window.chartData || null;

五、数据提取后的处理与分析

通过 Selenium 提取的 Canvas 数据为原始结构化数据(Python 字典 / 列表),无需解析像素或进行 OCR 识别,可直接用于后续处理:

  1. 数据清洗:过滤空值、异常值,统一数据格式;
  2. 数据分析:结合 Pandas、NumPy 进行统计分析、可视化复现;
  3. 数据存储:保存为 JSON、Excel、CSV 或存入数据库(MySQL、MongoDB);
  4. 可视化复现:使用 Matplotlib、Seaborn 将提取的数据重新绘制成图表,验证数据的完整性。

示例:Pandas 快速处理提取的数据

python

运行

复制代码
import pandas as pd
import json

# 读取保存的JSON数据
with open("canvas_chart_data.json", "r", encoding="utf-8") as f:
    data = json.load(f)

# 转换为DataFrame
df = pd.DataFrame({
    "x轴": data["x_axis"],
    "数值": data["series"][0]["data"]
})
# 查看数据前5行
print(df.head())
# 保存为Excel文件
df.to_excel("canvas_chart_data.xlsx", index=False)

六、常见问题与解决方案

问题 1:执行 JS 代码返回 null,未找到图表实例

  • 原因:图表实例的获取方式错误,或页面存在多个 Canvas 标签,匹配到了非目标画布;
  • 解决方案:
    1. 打开目标页面的浏览器开发者工具(F12),在 Console 面板中调试 JS 代码,确认图表实例的正确获取方式;
    2. 通过 Canvas 的父元素定位(如By.IDBy.CLASS_NAME),精准匹配目标 Canvas,避免误选。

问题 2:页面加载缓慢,图表渲染超时

  • 原因:目标页面网络延迟高,或 JS 代码执行耗时较长,超过显式等待的超时时间;
  • 解决方案:
    1. 延长显式等待的超时时间(如从 10 秒调整为 20 秒:WebDriverWait(driver, 20));
    2. 添加页面加载完成等待:driver.execute_script("return document.readyState") == "complete"

问题 3:浏览器启动后立即关闭,无任何输出

  • 原因:Python 代码执行速度过快,未进入try块就执行了driver.quit(),或驱动与浏览器版本不兼容;
  • 解决方案:
    1. 确保所有核心逻辑都在try块中执行,driver.quit()仅在finally块中调用;
    2. 升级 Selenium 和 webdriver-manager 版本:pip install --upgrade selenium webdriver-manager

问题 4:跨域限制导致无法访问数据

  • 原因:部分网站设置了跨域策略,限制 JS 访问某些变量;
  • 解决方案:启动浏览器时添加跨域相关配置,以 Chrome 为例:

python

运行

复制代码
from selenium.webdriver.chrome.options import Options

# 添加Chrome配置项
chrome_options = Options()
chrome_options.add_argument("--disable-web-security")  # 关闭跨域安全限制
chrome_options.add_argument("--allow-file-access-from-files")

# 初始化浏览器时传入配置
driver = webdriver.Chrome(
    service=Service(ChromeDriverManager().install()),
    options=chrome_options
)

七、进阶优化技巧

1. 无头模式运行浏览器

无需显示浏览器窗口,后台静默运行,节省资源,适合服务器端部署:

python

运行

复制代码
chrome_options = Options()
chrome_options.add_argument("--headless=new")  # 新版Chrome无头模式(推荐)
chrome_options.add_argument("--disable-gpu")  # 禁用GPU加速,避免无头模式渲染异常

2. 增加请求头,模拟真实浏览器

部分网站会检测请求头,识别自动化工具,添加请求头可提高爬取成功率:

python

运行

复制代码
chrome_options = Options()
# 添加User-Agent,模拟Chrome浏览器
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")

3. 批量爬取多个 Canvas 图表

若页面存在多个 Canvas 图表,通过循环定位每个 Canvas 元素,分别执行 JS 提取数据:

python

运行

复制代码
# 获取页面中所有Canvas元素
canvas_elems = driver.find_elements(By.TAG_NAME, "canvas")
for index, elem in enumerate(canvas_elems):
    # 为每个Canvas单独提取数据,通过JS传入elem对象
    data = driver.execute_script("""
        const chartInstance = echarts.getInstanceByDom(arguments[0]);
        return chartInstance ? chartInstance.getOption().series : null;
    """, elem)  # arguments[0]对应Python传入的elem参数
    print(f"第{index+1}个Canvas图表数据:", data)

八、总结

  1. Canvas 图表的核心爬取难点是数据仅存在于浏览器 JS 执行环境,无 DOM 节点映射,传统静态爬虫无法解决;
  2. Selenium 的核心优势是模拟浏览器完整渲染流程 ,通过execute_script()方法桥接 Python 与浏览器 JS 环境,直接提取原始结构化数据,无需像素解析;
  3. 爬取核心步骤为:环境准备→页面加载→显式等待渲染→执行 JS 提取数据→解析保存,其中显式等待JS 代码适配是关键;
  4. 该方法适配所有 Canvas 可视化库(ECharts、Highcharts、原生 Canvas),仅需调整 JS 代码中图表实例的获取方式和数据提取逻辑;
  5. 实际应用中需注意反爬策略(如请求头模拟、无头模式)、页面加载速度(显式等待)和资源释放(及时关闭浏览器)。

通过本文的方法,可高效解决 Canvas 渲染数据图表的爬取问题,获取的原始数据保留了完整的结构和精度,远优于 OCR 像素识别的方式,是处理 Canvas 动态数据爬取的工业级解决方案。

相关推荐
子午2 小时前
【2026计算机毕设】蔬菜识别系统~Python+深度学习+人工智能+算法模型+TensorFlow
人工智能·python·深度学习
kong79069282 小时前
Python 调用大模型(LLM)
人工智能·python·大模型llm
Just right2 小时前
python安装包问题
开发语言·python
hhy_smile2 小时前
Function in Python
python
dxz_tust2 小时前
flow match简单直观理解
开发语言·python·深度学习·扩散模型·流匹配·flow match
写代码的【黑咖啡】2 小时前
Python 中的时间序列特征自动提取工具:tsfresh
开发语言·python
癫狂的兔子2 小时前
【BUG】【Python】【爬虫】爬取加载中的数据
爬虫·python·bug
wqwqweee2 小时前
Flutter for OpenHarmony 看书管理记录App实战:个人中心实现
开发语言·javascript·python·flutter·harmonyos
费弗里2 小时前
我的Python环境管理方式,兼顾常用AI工具依赖环境
python·ai