⚠️服务端使用puppeteer注意事项
当我们安装 puppeteer时,它会自动下载最新版的 Chrome和chrome-headless-shell,在本地它通常运行的非常良好。
但是当我们将它部署到服务器启动时,它会报各种错误





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

免费的解决方案
puppeteer 提供了connect连接远程Chrome的方法,我们可以通过docker使用别人制作好的 Chrome 镜像,通过 connect 远程连接获取浏览器实例来进行操作。
免费的Chrome镜像
通过docker compose去部署
| 12
 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 连接
| 12
 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内容截图
| 12
 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;
 }
 
 | 
代码说明
- 通过 puppeteer.connect连接到远程浏览器
- 通过 browser.newPage在浏览器打开一个新的页面
- 通过 page.setContent给这个页面设置内容,{waitUntil: 'domcontentloaded'}表示dom内容加载成功的时候,视为内容设置成功
- 通过 page.$(".container")获取到我们要进行截图的节点
- 通过 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格式
- 截图成功之后,我们通过 browser.close关闭浏览器
获取一个url的截图
| 12
 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;
 }
 
 | 
代码说明
- 我们这次通过 page.goto跳转到指定的url地址,而不是setContent设置页面内容
- 我们通过 page.screenshot来获取整个页面的截图,而不是获取某个dom节点的截图
免费的图片压缩上传方案
图片转码
我们在上面获取到的图片格式是png的格式,文件比较大,我们将要使用的免费图片存储方案会限制每次上传的图片大小,所以我们希望可以得到一个比较小的图片文件。
本身我们如果能得到webp格式的图片的话,那我们是可以不做这一步的,但问题是拿不到,所以我们就不得不做压缩这一步了。
图片转码方案:https://sharp.pixelplumbing.com/
安装 sharp
| 12
 3
 4
 5
 
 | import sharp from 'sharp'
 const screenshot = await getScreenshot(html);
 console.log('截图完成',screenshot.length)
 const img = sharp(screenshot).avif({quality: 50})
 
 | 
代码说明
- 通过调用上面写好的 getScreenshot 的方法,我们可以拿到截图的二进制数据
- 然后调用sharp传入二进制数据,然后调用avif将png格式的图片转为avif格式,图片质量设置为 50%,得到Sharp对象,我们可以再做后续的操作
免费的图片存储
我们可以使用免费的 https://www.picgo.net/ 图床服务,注册账号,然后生成API key,通过调用它的上传接口存储图像
由于它的要的参数是formdata格式编码,所以我们需要安装 form-data库
| 12
 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
 
 | 
代码说明
- 我们通过 img.toBuffer将转码后的图片转为buffer
- 根据 picgo 的接口文档,将二进制数据给source,指定文件名字和数据类型
- form.append('format', 'json')我们希望返回的数据是JSON格式
- 最后通过axios调用接口上传文件,最后拿到上传后的图片地址
puppeteer 扩展
SEO
在nginx层根据agent判断请求是否来自搜索引擎,如果来自搜索引擎,将请求转发到 puppeteer服务,通过 page.goto去渲染页面,然后通过 page.content拿到渲染后的页面的html,将html返回,使搜索引擎进行关键词抓取
爬虫
通过page.goto跳转到具体页面后,通过 page.$或 page.$$获取ElementHandle
| 12
 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)
 })
 
 | 
输入内容
| 12
 3
 
 | await page.goto(url);const input = await page.$('input');
 await input.press('hello world')
 
 | 
点击
| 12
 3
 
 | await page.goto(url);const btn = await page.$('button');
 await btn.click()
 
 |