Puppeteer 踩坑笔记

Puppeteer 是最常见的服务端 Node.js 网页转 PDF 工具库之一,原理是启动 Chromium 无头模式,打开网页,输出PDF,由于其原理是直接操控浏览器,导出的 PDF 几乎和网页效果一致。本篇记录一下踩坑经历。

我的 Puppeteer 版本:19.4.1

无法安装 puppeteer

  1. Node.js 要求 >=14.1.0
  2. Puppeteer 安装过程中默认访问 Google 服务器下载 Chromium 安装包,建议切换到阿里源进行安装。
1
npm config set PUPPETEER_DOWNLOAD_HOST=https://registry.npmmirror.com/-/binary

启动 Chrome 没反应

puppeteer.launch 指定了 Chrome 浏览器路径,启动后任务管理器进程中有 chrome.exe 但代码不往下走

必须使用 Chromium 或者 Chrome Canary(金丝雀)版本,不要使用 Chrome 正式版。

Linux 系统服务器无法启动 Chromium

安装 Chromium 所需的运行依赖,以下是 RedHat Enterprise 需要安装的依赖。

1
2
yum install -y alsa-lib.x86_64 atk.x86_64 cups-libs.x86_64 gtk3.x86_64 ipa-gothic-fonts libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXrandr.x86_64 libXScrnSaver.x86_64 libXtst.x86_64 pango.x86_64 xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-fonts-cyrillic xorg-x11-fonts-misc xorg-x11-fonts-Type1 xorg-x11-utils
yum install -y at-spi2-atk libxkbcommon-x11 libgbm

另外,有些 Linux 内核不支持 sandbox,还需要在 Chromium 启动参数里加 --no-sandbox --disable-setuid-sandbox

Linux 系统服务器导出的 PDF 缺字、口口、字体异常

从 Windows 复制宋体、黑体、微软雅黑等常用字体到 Linux /usr/share/fonts/ 下,然后执行

1
2
3
4
5
yum install -y fontconfig mkfontscale
cd /usr/share/fonts
mkfontscale
mkfontdir
fc-cache

导出的 PDF 是空白

通常是单页面应用还没有加载完就触发了导出造成的,可以通过等待页面指定元素出现再导出来解决。

1
2
3
4
5
6
7
await browserPage.goto('http://localhost:8080', { waitUntil: 'networkidle2' });
// 解决页面未加载完成时,直接导出的 PDF 空白的问题
await browserPage.mainFrame().waitForSelector('.xxx-container');
await browserPage.pdf({
format: 'A4',
// ...
});

性能优化

容器运行 Chromium 无头浏览器推荐添加的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const browser = await puppeteer.launch({
args: [
// 解决 brwoser.newPage() 卡死的问题
'–-disable-gpu',
'–-no-zygote',
// 容器的 /dev/shm 分区默认只有 64M 不够用,禁用这个分区
'–-disable-dev-shm-usage',
// 解决某些服务器 Linux 发行版不支持沙盒启动的问题
'–-no-sandbox',
'–-disable-setuid-sandbox',
// 加快启动速度
'–-no-first-run',
// 单进程启动,好维护
'–-single-proces',
]
});

最后放一个完整的代码示例

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
41
console.log('Starting Chromium...');
const browser = await puppeteer.launch({
// 如果是手动安装的 chromium,需要传路径
// 如果是自动安装的 Chromium,不需要传 executablePath
executablePath: 'D:\\Software\\Chromium\\chrome.exe',
// 在某些Linux发行版下需要加以下参数才能启动
args: [
// 解决 brwoser.newPage() 卡死的问题
'–-disable-gpu',
'–-no-zygote',
// 容器的 /dev/shm 分区默认只有 64M 不够用,禁用这个分区
'–-disable-dev-shm-usage',
// 解决某些服务器 Linux 发行版不支持沙盒启动的问题
'–-no-sandbox',
'–-disable-setuid-sandbox',
// 加快启动速度
'–-no-first-run',
// 单进程启动,好维护
'–-single-proces',
],
});
console.log('Started chromium');
try {
const browserPage = await browser.newPage();
browserPage.setDefaultNavigationTimeout(0);
// 传需要转 PDF 的页面地址
await browserPage.goto('http://localhost:8080', {
waitUntil: 'networkidle2',
});
// 解决页面未加载完成时,直接导出的 PDF 空白的问题
await browserPage.mainFrame().waitForSelector('.xxx-container');
// 传 PDF 的存放位置等选项
await browserPage.pdf({
path: 'filename.pdf',
format: 'A4',
});
console.log('Generated PDF');
} finally {
await browser.close();
console.log('Closed Chromium');
}

总之,遇到问题时多打日志,更容易分析具体是哪一步出的问题。

作者

iMaeGoo

发布于

2023-01-05

更新于

2023-03-31

许可协议

CC BY 4.0

评论