Canvas内容无法直接用requests获取,因其仅提供绘图上下文而无像素数据;需通过Playwright执行JS调用toDataURL()导出图像,或逆向分析并请求原始数据接口本地重绘。

Canvas内容无法直接用requests获取
Canvas元素本身不存储图像数据,只提供绘图上下文;requests拿到的HTML里只有<canvas>标签和JS初始化逻辑,没有像素信息。想“抓取Canvas内容”,本质是执行页面JS、触发绘制、再导出图像——这超出了纯HTTP请求的能力范围。
常见错误现象:response.text里能看到<canvas id="chart">,但用BeautifulSoup查不到图像URL,也找不到base64数据。
可行路径只有两个:
• 启动浏览器环境(如Playwright或Selenium),等JS渲染完成,调用toDataURL()提取图片
• 逆向分析JS逻辑,看Canvas是否由某个API返回的数据驱动(比如fetch('/api/data')),直接请求该接口拿原始数据再本地绘图
用Playwright截取Canvas图像最可靠
Playwright启动真实Chromium内核,支持等待Canvas绘制完成并执行JS导出。比Selenium轻量,API更稳定。
立即学习“Python免费学习笔记(深入)”;
实操要点:
- 必须显式等待Canvas元素存在且尺寸非零,避免截图为空白:
page.wait_for_function("() => document.querySelector('#myCanvas').width > 0") - 用
page.evaluate()调用Canvas的toDataURL("image/png"),不能直接用screenshot()——后者截的是整个视口,可能含遮罩层或未对齐 - 导出的base64字符串需解码写入文件:
base64.b64decode(data_url.split(",")[1]) - 若Canvas内容动态生成(如ECharts),要等图表动画结束,可加
page.wait_for_timeout(1000)或监听chartInstance.on('finished')事件
合并多个Canvas图像要用PIL而非简单拼接HTML
网页上多个<canvas>可能是独立绘制区域(如分栏图表、前后对比图),直接截图会丢失布局语义。正确做法是分别导出为PNG,再用PIL.Image按需合成。
关键细节:
- 注意Canvas的
width/height属性(CSS样式缩放不影响实际分辨率),导出时以属性值为准,否则图像模糊 - 合并前统一转为
RGBA模式,避免透明通道处理异常:img.convert("RGBA") - 用
Image.alpha_composite()叠加带透明度的图层,用paste()定位拼接,别依赖Image.new().paste()的默认mode参数 - 如果Canvas有阴影/抗锯齿,导出时浏览器可能启用GPU加速,导致不同机器结果微异——生产环境建议固定Playwright的
--disable-gpu启动参数
绕过Canvas:优先检查背后的数据接口
多数业务场景中,Canvas只是渲染层,真实数据来自XHR/Fetch请求。打开浏览器开发者工具的Network面板,筛选XHR或Fetch,刷新页面,找响应体为JSON且含xAxis/series等字段的请求。
一旦定位到数据接口:
- 用
requests直接请求,加headers={"User-Agent": "..."}绕过基础反爬 - 注意接口是否需要Referer或Cookie,可用
page.context.cookies()从Playwright中提取后复用 - 拿到数据后,用
matplotlib或plotly本地重绘,完全可控,还能批量导出PDF/SVG - 这种方法规避了浏览器自动化开销,速度快、稳定性高,且天然支持合并逻辑(比如把多个
series画在同一张图上)
真正难的不是“怎么截Canvas”,而是判断它到底有没有必要被截图——很多情况下,你根本不需要它。


















