本文主要针对 puppeteer 处理前端页面使用了懒加载图片等内容的截取总结。同时也对一些第三方防止页面被截图手段的学习和总结。

懒加载-图片

针对内容是通过图片懒加载的页面,比较好处理,只需要在 puppeteer 截取的时候设置 fullPage:true 全屏截取即可。

1
2
3
4
await page.screenshot({
fullPage: true, //设置全屏
encoding: "binary",
});

懒加载-接口

如果页面上的内容是通过接口返回,即 img 标签 src 属性设置的是一个返回图片格式的接口,此时就需要让页面进行滚动,触发接口请求的执行,然后再进行全屏截图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
await page.evaluate(async () => {
await new Promise((resolve, reject) => {
let totalHeight = 0;
const distance = 500;
const timer = setInterval(() => {
const scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= scrollHeight) {
clearInterval(timer);
resolve();
}
}, 100);
});
});

在执行这部分时,需要注意 document.body.scrollHeight,这个高度默认是 page.setViewport 设置的 height 参数,为了防止滚动不够,最好在调用截图之前,通过接口参数把要截取的区域类名选择器或 id 选择器给传递过来,在打开要访问的截图页面后,通过 page.evaluate 返回这部分区域的高度,把返回的高度通过 page.setViewport 的 height 参数设置默认的高度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
await page.goto(url, { timeout: 0, waitUntil: "networkidle2" });

let contentHeight = await page.evaluate(() => {
const element = document.querySelector(".classname"); //通过接口参数拿到要截取内容区域的元素
return element.clientHeight;
});

await page.setViewport({
width: 980,
height: contentHeight, //设置高度
});

await page.evaluate(async () => {
await new Promise((resolve, reject) => {
let totalHeight = 0;
const distance = 500;
const timer = setInterval(() => {
let scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= scrollHeight) {
clearInterval(timer);
resolve(totalHeight);
}
}, 100);
});
});

如果上述步骤仍然不能保证截取完整,就需要寻找要截取区域的父元素是否有设置固定高度导致页面没有滚动加载完全部的内容,如果有,要修改父元素的高度为 100%后,再进行截取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
await page.goto(url, { timeout: 0, waitUntil: "networkidle2" });
let contentHeight = await page.evaluate(() => {
const element = document.querySelector(".classname");
return element.clientHeight;
});
await page.setViewport({
width: 980,
height: contentHeight,
});

await page.evaluate(() => {
const element = document.querySelector(".classname");
if (element) {
element.style.height = "100%";
}
});
//等待页面加载完
await page.waitForTimeout(3000);
//执行页面滚动
await page.evaluate(async () => {
await new Promise((resolve, reject) => {
let totalHeight = 0;
const distance = 500;
const timer = setInterval(() => {
let scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= scrollHeight) {
clearInterval(timer);
resolve(totalHeight);
}
}, 100);
});
});
//截取全屏
let file = await page.screenshot({
encoding: "binary",
fullPage: true,
});
return file;

第三方防止截取页面的方式

像上面提到的,懒加载的图片 src 属性设置的是通过接口返回一个Content-Type: image/jpeg;charset=UTF-8 或其他图片格式的接口,而不是直接替换成一个图片文件地址,如果资源没有请求完成,最后截取的只有首屏第一张图的内容。

设置父元素高度,这样即使整个页面的主要内容区域都加载完,例如总的图片区域加载完高度是 5000px,但父元素因为设置了高度为 800px,同时设置 overfiow-x:hidden,最后截取下来的图片仍然是只有首屏大小。

像上面使用 puppteer 通过滚动页面到底部来使页面上所有资源都加载完,但当某个元素(例如上面提到的父元素)的高度不为设置的初始值,当滑动到底部,就给父元素再恢复初始值,如果不处理,最后截取的虽然整个页面高度是加载完的,但截取的内容还是首屏。

总结

总的来说,即使做了针对防止被人截图保存页面内容一些处理方式,但经过仔细分析页面总会发现一些端倪,再使用 puppteer 来进行针对性的破解这些操作,是一个斗智斗勇的过程,puppteer 提供的 api 基本都可以解决。至于防止被截取页面的方式,上述是我目前所遇到的,当然还有其他更多的方式,欢迎大家补充讨论。

错误汇总

  1. Error: Node has 0 height.