重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。
一、起因
使用过 Puppeteer
的小伙伴们一定多多少少接触过 Puppeteer
里面的截图功能,尤其是在一些自动化场景里,需要涉及验证码的自动识别时,必然少不了要将验证码图片截取下来,然后通过识别接口进行识别。
当我以为一切都是那么美好的时候,总是会出一些幺蛾子。当运行次数达到一定量之后,就会发现很多时候其实截下来的图片并不是验证码图片这块区域的图片内容,而是一张存在着一定的偏差的图片。这就很伤脑筋。
二、解决过程
为了保证每次截图都能够完美截取,选取了各种不同的截取范围,又尝试了不同的截图格式,但是最后截取下来的图片依然存在着偏移。但是慢慢的我注意到一个现象,每次截图的时候,界面上所有的东西都会闪一下,然后迅速恢复。如果闪烁时,截取目标在浏览器左上角就是存在偏移的,那么截取下来的最终结果就一定也是存在偏移的。
既然这样,于是我就手动创建一个canvas,然后把目标img绘制到这个canvas上面,这个canvas是一个fixed的并且zindex非长大,一直处于浏览器左上角的。然后我截图截取这个绘制的好的canvas。没想到误打误撞就可以了,通过这样一顿操作之后,每次截图都能够完美截图。
三、代码
最后整理出这个方法,方便直接进行截图:
/**
* 对指定图片进行截图。
* @param page {Page} 页面
* @param imgSelector {string} img选择器
* @param type {"png"|"jpeg"}
* @return {Promise<string>} 返回截取成功的base64
*/
async function shot(page, imgSelector, type = "png") {
// 先创建并绘制canvas。
let canvasId = await page.evaluate(function (select) {
let target = document.querySelector(select);
if (!target) {
throw new Error("未找到选择器:" + select);
}
if (target.tagName.toLowerCase() !== "img") {
throw new Error("本截图只支持命中img节点的选择器,请重新设定选择器。");
}
let id = "ca_"+String(Math.random()).split(".")[1];
let width = target.clientWidth;
let height = target.clientHeight;
/**
* @type {HTMLCanvasElement}
*/
let canvasElement = document.createElement("canvas");
canvasElement.style.position = "fixed";
canvasElement.style.zIndex = "999999";
canvasElement.style.top = "0";
canvasElement.style.left = "0";
canvasElement.style.width = width + "px";
canvasElement.style.height = height + "px";
canvasElement.id = id;
document.body.append(canvasElement);
canvasElement.style.display = "block";
canvasElement.width = width;
canvasElement.height = height;
/**
* @type {CanvasRenderingContext2D}
*/
let context = canvasElement.getContext("2d");
// 绘制白色背景,有些验证码可能是透明背景。
context.fillStyle = "#FFFFFF";
context.fillRect(0,0, width, height);
context.drawImage(target,0, 0, target['naturalWidth'], target['naturalHeight'],0,0, width, height);
return id;
}, imgSelector);
/**
* 添加并绘制好了之后,再截图绘制好了的canvas。
* @type {Base64ScreenShotOptions}
*/
let shotOption = {type: type, encoding: "base64"};
let element = await page.$(`#${canvasId}`);
shotOption.clip = await element.boundingBox();
let base64 = await page.screenshot(shotOption);
let result = "data:image/" + type + ";base64," + base64.toString();
// 图截好了,把这个canvas移除。
await page.$eval(`#${canvasId}`, function (element) {
element.remove();
});
return result;
}
上面的代码将生成一个Canvas并且这个canvas层级很高,保证不会被遮挡。
四、总结及注意事项
Puppeteer
截图是基于浏览器左上角的,确切的说,是基于网页左上角的,当你处理的网页如果很长,出现了滚动条后,这时截图一定要先把网页滚动到最顶部,然后再截图,要不让很可能截取一片白。