1. 1. ⚠️服务端使用puppeteer注意事项
  2. 2. 免费的解决方案
  3. 3. 实现截图
    1. 3.1. 设置html内容截图
    2. 3.2. 获取一个url的截图
  4. 4. 免费的图片压缩上传方案
    1. 4.1. 图片转码
    2. 4.2. 免费的图片存储
  5. 5. puppeteer 扩展
    1. 5.1. SEO
    2. 5.2. 爬虫
    3. 5.3. 输入内容
    4. 5.4. 点击

⚠️服务端使用puppeteer注意事项

当我们安装 puppeteer时,它会自动下载最新版的 Chrome和chrome-headless-shell,在本地它通常运行的非常良好。

但是当我们将它部署到服务器启动时,它会报各种错误

img

img

img

img

img

查看故障文档,它在linux上运行还需要再安装一系列的系统依赖项,想要运行起来并没有那么容易

参考链接

img

免费的解决方案

puppeteer 提供了connect连接远程Chrome的方法,我们可以通过docker使用别人制作好的 Chrome 镜像,通过 connect 远程连接获取浏览器实例来进行操作。

免费的Chrome镜像

通过docker compose去部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
services:
browserless:
restart: unless-stopped
image: ghcr.nju.edu.cn/browserless/chromium
container_name: browserless
ports:
- "3123:3000"
environment:
TZ: Asia/Shanghai
CONCURRENT: 5
TOKEN: 4B0Y656Z873497
TIMEOUT: 60000
HOST: 0.0.0.0
DOWNLOAD_DIR: ./down
ALLOW_FILE_PROTOCOL: 'true'

nginx 配置允许web socket 连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 443 ssl;
server_name broserless.kaibinluo.com;

ssl_certificate /etc/nginx/ssl/kaibinluo.fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/kaibinluo.com.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

location / {
proxy_pass http://10.0.0.10:3123;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

实现截图

⚠️仅安装 puppeteer-core,不安装 puppeteer

⚠️仅安装 puppeteer-core,不安装 puppeteer

⚠️仅安装 puppeteer-core,不安装 puppeteer

设置html内容截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import puppeteer from "puppeteer-core";

const getScreenshot = async (html) => {
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://broserless.kaibinluo.com?token=4B0Y656Z873497'
});
const page = await browser.newPage();
await page.setContent(html, {waitUntil: 'domcontentloaded'});
const ele = await page.$(".container");
const screenshot = await ele.screenshot({
encoding: "binary",
// quality: 50,
captureBeyondViewport: true,
type: "png"
});
await browser.close();

return screenshot;
}

代码说明

  1. 通过 puppeteer.connect连接到远程浏览器
  2. 通过 browser.newPage在浏览器打开一个新的页面
  3. 通过 page.setContent给这个页面设置内容,{waitUntil: 'domcontentloaded'}表示dom内容加载成功的时候,视为内容设置成功
  4. 通过 page.$(".container")获取到我们要进行截图的节点
  5. 通过 ele.screenshot来对当前节点进行截图,encoding: "binary"表示截图以二进制数据返回,captureBeyondViewport: true表示我们希望在窗口之外的视图也被截到,type: "png"表示我们希望截图是png格式;⚠️ puppeteer支持png|jpeg|webp三种格式的截图,但是当我们在Debian上使用 browserless/chromium镜像时,使用webp格式进行截图时,只会得到空白,通过搜索 puppeteer的 issues 看到一个相似的bug https://github.com/puppeteer/puppeteer/issues/7923 ,猜测原因是相同的,所以我们先不使用webp格式
  6. 截图成功之后,我们通过 browser.close关闭浏览器

获取一个url的截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import puppeteer from "puppeteer-core";

const getScreenshot = async (url) => {
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://broserless.kaibinluo.com?token=4B0Y656Z873497'
});
const page = await browser.newPage();
await page.goto(url);
const screenshot = await page.screenshot({
encoding: "binary",
// quality: 50,
captureBeyondViewport: true,
type: "png"
});
await browser.close();

return screenshot;
}

代码说明

  1. 我们这次通过 page.goto跳转到指定的url地址,而不是 setContent设置页面内容
  2. 我们通过 page.screenshot来获取整个页面的截图,而不是获取某个dom节点的截图

免费的图片压缩上传方案

图片转码

我们在上面获取到的图片格式是png的格式,文件比较大,我们将要使用的免费图片存储方案会限制每次上传的图片大小,所以我们希望可以得到一个比较小的图片文件。

本身我们如果能得到webp格式的图片的话,那我们是可以不做这一步的,但问题是拿不到,所以我们就不得不做压缩这一步了。

图片转码方案:https://sharp.pixelplumbing.com/

安装 sharp

1
2
3
4
5
import sharp from 'sharp'

const screenshot = await getScreenshot(html);
console.log('截图完成',screenshot.length)
const img = sharp(screenshot).avif({quality: 50})

代码说明

  1. 通过调用上面写好的 getScreenshot 的方法,我们可以拿到截图的二进制数据
  2. 然后调用sharp传入二进制数据,然后调用avif将png格式的图片转为avif格式,图片质量设置为 50%,得到 Sharp对象,我们可以再做后续的操作

免费的图片存储

我们可以使用免费的 https://www.picgo.net/ 图床服务,注册账号,然后生成API key,通过调用它的上传接口存储图像

由于它的要的参数是formdata格式编码,所以我们需要安装 form-data

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
import sharp from 'sharp'
import axios from 'axios';
import FormData from 'form-data';

const screenshot = await getScreenshot(html);
console.log('截图完成',screenshot.length)
const img = sharp(screenshot).avif({quality: 50})
/*********************************************************/
const buff = await img.toBuffer()
const form = new FormData();
form.append('source', buff, {
filename: `${dayjs().format('YYYY-MM-DD')}.avif`, // 指定文件名
contentType: 'application/octet-stream' // 指定 MIME 类型
})
form.append('format', 'json')

const config = {
method: 'post',
maxBodyLength: Infinity,
url: 'https://www.picgo.net/api/1/upload',
headers: {
'X-API-Key': '生成的API key',
...form.getHeaders()
},
data : form
};
const result = await axios.request(config)
const imgUrl = result?.data?.image?.url

代码说明

  1. 我们通过 img.toBuffer将转码后的图片转为buffer
  2. 根据 picgo 的接口文档,将二进制数据给source,指定文件名字和数据类型
  3. form.append('format', 'json')我们希望返回的数据是JSON格式
  4. 最后通过axios调用接口上传文件,最后拿到上传后的图片地址

puppeteer 扩展

SEO

在nginx层根据agent判断请求是否来自搜索引擎,如果来自搜索引擎,将请求转发到 puppeteer服务,通过 page.goto去渲染页面,然后通过 page.content拿到渲染后的页面的html,将html返回,使搜索引擎进行关键词抓取

爬虫

通过page.goto跳转到具体页面后,通过 page.$page.$$获取ElementHandle

1
2
3
4
5
6
await page.goto(url);
const trList = await page.$$('table tr');
trList.forEach(async tr => {
const tdListContent = await tr.$$eval('td', (td) => td.textContent);
console.log(`这一行的数据为`, tdListContent)
})

输入内容

1
2
3
await page.goto(url);
const input = await page.$('input');
await input.press('hello world')

点击

1
2
3
await page.goto(url);
const btn = await page.$('button');
await btn.click()