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





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

免费的解决方案
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; }
|
代码说明
- 通过
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的截图
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; }
|
代码说明
- 我们这次通过
page.goto
跳转到指定的url地址,而不是 setContent
设置页面内容
- 我们通过
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})
|
代码说明
- 通过调用上面写好的 getScreenshot 的方法,我们可以拿到截图的二进制数据
- 然后调用
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
|
代码说明
- 我们通过
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
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()
|