Python Selenium 调用 JS 获取图片 Base64 的正确实现

作者:百宝袋时间:2026-10-31 10:03:29浏览:392

本文详细记录了在 Python Selenium 自动化测试中,通过 JavaScript 获取网页图片 Base64 编码的完整过程。文章从最初的 pyautogui 模拟操作出发,逐步排查并解决 Canvas 绘图尺寸、图片异步加载、同步与异步执行差异等核心问题,最终给出基于 Promise 机制的稳定代码实现方案。

前言

近期有一个具体需求:需要在由 Python Selenium 控制的浏览器环境中执行 JavaScript 代码,从而获取图片的 Base64 编码,并将结果返回给 Python 变量。

这一看似简单的需求,实际上涉及了多个 JavaScript 相关的技术细节以及开发过程中的关键问题。

本文旨在梳理整个过程中具有参考价值的技术要点。

原始方法

起初,并未考虑使用 JavaScript 来获取图片,而是尝试通过 pyautogui 模拟键盘操作来下载图片,具体代码如下:

import pyautogui
from selenium.webdriver import ActionChains

driver.get(url)
ActionChains(driver).move_to_element(driver.find_element_by_tag_name('img')).context_click().perform()
pyautogui.typewrite(['v'])
time.sleep(1)
pyautogui.typewrite(['enter'])

该方案主要依赖电脑快捷键操作:选中图片后右键,按下 v 键调出保存窗口,再按 Enter 键即可按默认信息保存。

这种模拟手动操作的方式原理简单,但在多次重复执行时会暴露出一个 Bug:按下 v 键可能与保存操作冲突,导致系统误将图片命名为 v,进而使整个流程无法继续。

为解决上述问题,才转而采用 JavaScript 方案,并由此展开了后续的技术探索。

第一版 JS 代码

var source = document.getElementsByTagName('img')[0];
// 在网页元素中找到图片资源
var img=new Image();
// 创建一个image元素
img.src=source.src;
// 设置img的图片链接为网页图片链接
img.crossOrigin='anonymous';
// 设置跨域的配置,这个是比较常见的,如果不了解,读者可以自行百度
var xhr=new XMLHttpRequest();
// 初始化一个http请求
xhr.open('GET',source.src,false);
// 构造请求
xhr.send(null);
// 发送请求
xhr.open('GET',source.src,false); // 重复上面步骤,为了确定加载到资源
xhr.send(null);
var canvas = document.createElement('canvas');  
// 创建canvas元素
var ctx=canvas.getContext('2d');
// 获取canvas二维属性
ctx.drawImage(img,0,0);
// 将img画到canvas上
var dataURL = canvas.toDataURL('image/png');
// 利用canvas转换图片为base64形式
return dataURL;

运行上述代码后发现,获取到的 Base64 还原出的图片显示为黑色,且多次尝试均如此。随后在浏览器控制台逐行执行代码,发现能生成正常的 Base64,但在浏览器直接解析后,虽然图片能正常显示,却被裁剪了。

经过仔细思考,起初怀疑是获取的图片本身有问题,但查看 Network 加载记录后发现图片加载完整。因此推测问题可能出在 Canvas 绘图环节,于是开始研究 Canvas 的 drawImage 函数。

经过研究得知,该函数的第一个参数是图片信息,第二和第三个参数指定画布坐标(即绘制起始位置),此外还可以有第四和第五个参数,用于指定绘制图像的宽度和高度。基于此,修改了相关代码:

ctx.drawImage(img,0,0,source.naturalWidth,source.naturalHeight);
// 这里面的source是指网页的图片,然后naturalWidth是原宽,naturalHeight是原高

此处尝试将整张图画上去,但实验发现绘制出的图像依然是裁剪后的结果,且每次图像大小一致。这提示画布大小可能存在问题。

查阅资料后发现,Canvas 具有初始大小,若不手动修改,其大小不会自动变化。因此,在绘图前增加了设置 Canvas 画布大小的代码。

canvas.width=source.naturalWidth;
canvas.height=source.naturalHeight;

经过此次修改,在浏览器端逐行执行时,代码运行无误,最终结果正确。本以为问题已解决,次日简单处理即可,但更复杂的问题随之而来。

注意:在控制台逐行执行代码与在 Python 中执行 JS 代码的结果存在差异。因为逐行执行时存在时间消耗,异步任务有足够时间完成;而在 Python 中执行时则不然。

第二版 JS 代码

var source = document.getElementsByTagName('img')[0];
// 在网页元素中找到图片资源
var img=new Image();
// 创建一个image元素
img.src=source.src;
// 设置img的图片链接为网页图片链接
img.crossOrigin='anonymous';
// 设置跨域的配置,这个是比较常见的,如果不了解,读者可以自行百度
var xhr=new XMLHttpRequest();
// 初始化一个http请求
xhr.open('GET',source.src,false);
// 构造请求
xhr.send(null);
// 发送请求
xhr.open('GET',source.src,false); // 重复上面步骤,为了确定加载到资源
xhr.send(null);
var canvas = document.createElement('canvas');  
// 创建canvas元素
canvas.width=source.naturalWidth;
// 设置画布宽
canvas.height=source.naturalHeight;
// 设置画布高
var ctx=canvas.getContext('2d');
// 获取canvas二维属性
ctx.drawImage(img,0,0,source.naturalWidth,source.naturalHeight);
// 将img画到canvas上
var dataURL = canvas.toDataURL('image/png');
// 利用canvas转换图片为base64形式
return dataURL;

上述代码是修改后自认为正确的版本,但次日在 Python 中运行时,结果依然错误,图片仍显示为黑色。

怀疑是否保存的代码有误,于是重复之前的步骤,在浏览器中逐行运行。结果依然正确,这让人困惑:相同的代码为何在不同环境下结果不同?

虽不理解原因,但尝试将代码封装为函数形式在浏览器端运行以观察结果。发现返回的内容并非正常的图片 Base64,只能转换为黑色图片。

为验证结果,多次运行后结论一致。对比浏览器正常结果、错误结果以及 Python 端返回结果,发现正确结果字符串很长,而两个错误结果长度相同且较短。

至此,问题陷入僵局,不知如何继续。

正值晚饭时间,便先去吃饭。用餐期间查阅 Canvas 文档,了解具体使用方法,希望能有所收获。

在查阅过程中,注意到 Canvas 绘图的一项注意事项:若调用 drawImage 时图片未装载完成,则不会发生任何操作(旧版浏览器可能会抛出异常)。因此,应使用 load 事件来确保图片加载完毕后再使用。(来源:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Using_images)

官方文档提供了示例:

var img = new Image();   // 创建 img 元素
img.src = 'myImage.png'; // 设置图片源地址
img.onload = function(){
  // 执行 drawImage 语句
}

未直接将此作为解决方案,而是查阅了更多论坛,发现大部分实现方式与此类似。

随后将此代码加入原代码中,发现若 return 语句在外层,仍会返回错误值;若 return 语句在内层,虽能正常执行,但返回值无法传递到外层。

灵机一动,尝试直接 return img.onload...。

在浏览器下确实成功,但在 Python 端仍不正确,返回的是一个函数对象。

至此明白,需等待图片装载完成后,再执行 drawImage 语句并返回结果。但 JS 代码本身不会等待,只要执行就会继续往下执行下一条语句,无论异步任务是否完成。

第三版 JS 代码

为解决加载完成的问题,思考是否有可以休眠等待的函数。查阅后发现可以自行构造一个 sleep 函数。

const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
sleep(1000); // 休眠1s

但在上述代码后添加该函数,返回结果仍无变化,感觉无法停止代码执行。

查阅资料后发现还有 setTimeout 函数,可设置一段时间后再执行函数,但输入值只能是函数,这就要求在函数内返回最终的 Base64。实际实验发现此方式行不通,不返回内容。

继续查阅资料,发现可以通过 throw error 抛出内容,尝试后发现均为 uncaught。当时感到纳闷,随后将 setTimeout 函数也换成了 img.onload,但结果均不正确。

中间经过大量实验,省略了多次尝试过程...。

最终明白,只有在 try 块内直接执行 throw error 代码,才能被 catch 捕获。利用 try 包裹 setTimeout 和 img.load 等耗时异步语句,均无法正常 catch,因为执行到 throw error 时,外层的 try{}catch{} 语句已执行完毕,故无法捕获。

上述大量过程让人明白,根本问题在于异步代码执行完成前,如何不继续执行后续代码。

最终的 JS 代码

var source = document.getElementsByTagName('img')[0];
var img=new Image();
img.src=source.src;
img.crossOrigin='anonymous';
var canvas = document.createElement('canvas');
canvas.width=source.naturalWidth;
canvas.height=source.naturalHeight;
var ctx=canvas.getContext('2d');
function httpPromise(){
   return new Promise((resolve,reject) => {
       if(img.complete || img.naturalHeight!==0)
       {
         ctx.drawImage(img,0,0,source.naturalWidth,source.naturalHeight);
         var dataURL = canvas.toDataURL('image/png');
         resolve(dataURL);
        }
        else{
         img.onload=function(){ 
                ctx.drawImage(img,0,0,source.naturalWidth,source.naturalHeight);
                var dataURL = canvas.toDataURL('image/png');
                resolve(dataURL);
               }
         }
    })
}
return httpPromise().then((dataURL)=>{return dataURL;}

最后一版代码做了若干改动:首先删除了 xhr 加载代码,因为该加载过程与最终结果无关,不影响结果;其次添加了 Promise 异步处理机制,利用该机制等待异步执行结束后收集所有结果并返回(记得 Java 也有类似库可解决异步等待问题);最后添加了判断机制,防止 img.onload 因缓存原因不响应,因此分为两种情况处理,但均将结果收集并在 return 语句处返回。

插一嘴:这里需要 return 语句,是因为 Python 中的 driver.execute_script 函数将 JS 代码包装成函数,需要返回内容进行处理,否则返回值直接为 None。

以上是通过自行摸索实现的方案,虽历经坎坷,但结果良好。其实,有一篇博客的代码优于我的代码。

大佬的代码

// https://blog.csdn.net/yaxuan88521/article/details/122794055
let c = document.createElement('canvas');
let ctx = c.getContext('2d');
let img = document.getElementsByTagName('img')[0];
c.height=img.naturalHeight;
c.width=img.naturalWidth;
ctx.drawImage(img, 0, 0,img.naturalWidth, img.naturalHeight);
let base64String = c.toDataURL();
return base64String;

上述大佬的代码直接获取加载后的图片元素,效果优于我的代码,毕竟减少了一次加载的时间消耗。

总结

本文通过笔者在 JS 获取图片 Base64 过程中遇到的问题,总括如下几点:

  • Canvas 绘图需要修改画布大小,否则无法绘制完整图像。
  • Canvas 的 drawImage 函数需要等待图片加载完成,否则直接返回画布。
  • JS 代码本身是同步执行的,但如果实现了如 setTimeout 和 img.onload 等异步函数,可能会出现返回结果不正确的情况。因此,如需返回值,最好利用 Future 构造,最后接收返回值。
  • 对于同一需求,最好选择更为简便的方法实现,否则会浪费时间和资源。(笔者在此处花费时间较多,但对 JS 有了更深的认识)

最后,本文主要介绍了 JS 获取图片 Base64 的问题,中间过程有些曲折,内容可能略显凌乱,但技术点已给出,希望读者能从中有所收获。

附:JS 实现 Base64 格式编码图片(通俗,易懂)

vip会员内容
作者最新文章