如果你今天是 Google 搜「Puppeteer PDF alternative」找过来的,你真正想问的问题大概是:
“为什么我的 serverless 函数每次冷启动要 2 秒、占 900 MB 内存,只是为了打印一张发票?”
Puppeteer 是个好工具。但对大多数团队真正在用它做的事——把结构化数据转成可预测的 PDF——它在工程上是过度设计了。这篇文章是写给即将把 Puppeteer 推上生产、又默默在想「有没有更合理选择」的团队。
我们会讲清楚 Puppeteer 在哪里物有所值,在哪里不值,以及 2026 年真实的取舍矩阵长什么样。
Puppeteer 实际让你部署什么
npm install puppeteer 之后,你下载了大约 170 MB 的 Chromium 构建,还没算依赖。运行时,无头 Chromium 渲染单页需要 600–900 MB 常驻内存,冷启动 1–2 秒。每次渲染都要:
- 启动浏览器进程(或复用进程池)
- 开新标签页
- 导航到你的 HTML / URL
- 等
domcontentloaded(通常还要等字体、图片、Web Components) - 跑
page.pdf(),把已绘制的页面通过 Chromium 的 PDF 引擎序列化 - 销毁标签页
这是「整个 Web 平台」的税。无论你的文档是 90 页带嵌入 SVG 图表的法律合同,还是一张 5 行文字的运单——你都要交。
对于真正需要 CSS 布局、JS 驱动内容、Web 字体的 HTML 转 PDF 场景,这税合理。但对于其他场景——发票、运单、收据、票据、对账单、证书——这是在烧钱。
Puppeteer 的强项
先把这个写清楚,否则团队后面会反复怀疑决策:
- 完美还原 HTML/CSS。 如果你的设计系统输出 HTML 而你想要像素一致的 PDF,Puppeteer 无敌。它就是 Chrome 在打印。
- Web 平台特性。 SVG 滤镜、CSS Grid 边界情况、Web Components、JS 计算的内容、第三方 iframe——全部直接能用。
- 可视化调试。 渲染中途可以截图,可以对无头模式开 DevTools,能看到渲染器看到的一切。
- 无需中间转换。 如果你的内容已经是网页,就不需要 schema 映射。
page.goto(url); await page.pdf()就是全部流水线。
如果上面任意两条描述了你的真实工作负载,别迁。 Puppeteer 就是对的答案。
Puppeteer 的弱项,毁灭性的弱
对其他场景,成本叠加得很快。
Serverless 里的内存与冷启动
典型的 Node 20 Lambda 或 Cloudflare Container 跑 Puppeteer:
| 指标 | 典型值 |
|---|---|
| 容器镜像大小 | 250–400 MB(Chromium + Node + 你的代码) |
| 冷启动时间 | 1.8 – 2.5 秒 |
| 单次渲染 RAM | 600 – 900 MB |
| 1 GB 实例并发渲染数 | 1(页面很小时偶尔 2) |
如果你的发票服务每月渲 100K 张,你在为每个冷容器的浏览器启动能耗买单——而其中零张渲染需要 JS 执行。
“容器里装字体” 这个坑
Chromium 自带字体集——通常缺 CJK、西里尔、天城文、阿拉伯文,以及一长串特定脚本字形。生产里发现这事的样子是:
2025 年 Q3 东京办公室的发票打印出
▢▢▢▢ 2025年第3四半期。客户升级。团队花一个 sprint 调试 Dockerfile 里的字体安装和 CSS fallback。
光嵌入 NotoSans CJK 就给镜像加 ~50 MB。嵌入 Noto 全球 fallback 集要加 ~250 MB。你为了打印一张日文发票,在为 Chromium 外加一整套庞大的字体包买单。
确定性
Puppeteer 跨 Chromium 版本的渲染不是字节一致的。一个补丁升级可能微妙地改变字距、字体基线或分页位置。如果你有 PDF diff 测试套件(你应该有),每次 Chromium 升级都是一次小型考古:哪个渲染变了,是不是有意的?
渲染时的 JavaScript
即使是「静态」HTML 页面也得解析、布局计算、绘制、序列化。实测单页 80–400 ms——大部分是布局,不是绘制。
对比一下:服务端把 JSON 直接送给二进制渲染器,同样一张单页发票要 3–8 ms(数字稍后给)。
gPdf 在哪里合适
gPdf 翻转了模型:不再把文档描述成 HTML 让浏览器去画,而是把文档描述成结构化 JSON(DocumentRequest),由 Rust 写、编译为 WebAssembly 的渲染器直接出 PDF。没有浏览器。没有 DOM。没有 JS 布局过程。
听起来很受限,对HTML 形状的问题确实如此。但对发票/运单/收据/对账单/证书这类文档,JSON-first 模型其实是更合身的:
- 你的数据本来就是结构化的。 你的发票本来就以
{ customer, lines, totals, taxes, notes }对象形式存在某处。你不会想先把它渲染成 HTML,然后让浏览器把 HTML 读回成布局。你想直接从数据到 PDF。 - 布局变成契约。 当
font_size: 11永远是 11 点、gap: 8永远是 8 点时,两个工程师 review PR 看到的输出完全一样。没有display: flex的解读差距。 - 输出字节一致。 同输入 → 同字节。可以
git diff两份 PDF 只看到改动的部分。 - 冷启动是运行时启动,不是浏览器启动。 Cloudflare Workers 上的 V8 isolate 初始化要 5–20 ms。WASM 模块在同一 isolate 上跨调用都是热的。
典型的 gPdf 单页发票渲染在边缘端 p50 wall-clock 3–5 ms,从用户最近的 Cloudflare colo 提供。比 Puppeteer 的热路径快约两个数量级,比冷路径快三个数量级。
决策矩阵
工程评审里你会真用的那张表。
| 工作负载 | 用 Puppeteer | 用 gPdf |
|---|---|---|
| 现有 HTML 报告 → PDF | ✅ 首选 | ⚠️ 需要重写 |
| 发票、对账单、收据 | ⚠️ 大锤打苍蝇 | ✅ 首选 |
| 带条码的运单 | ❌ 避免(字体问题) | ✅ 首选 |
| 电子发票(Factur-X / ZUGFeRD / EN 16931) | ❌ 无内置支持 | ✅ 内置 |
| PDF/A 长期归档 | ⚠️ 需 Ghostscript 后处理 | ✅ 内置档位 |
| 像素一致的设计系统 mockup | ✅ 首选 | ❌ 错的工具 |
| 需要真 D3 / Recharts 的图表 | ✅ 首选 | ❌ 错的工具 |
| 票据、证书、铭牌 | ⚠️ 杀鸡用牛刀 | ✅ 首选 |
| 需要渲染时 JS 的任何东西 | ✅ 唯一选 | ❌ 错的工具 |
如果你在右列里勾中三行以上,节省不是小数目。
真实对比:单页发票渲染
同内容、同纸张、同字体(NotoSans)、同 PDF/A-3b 输出档。
| Puppeteer(热 Lambda,1 GB) | gPdf(热 Cloudflare Worker) | |
|---|---|---|
| p50 延迟 | 180 ms | 3.4 ms |
| p99 延迟 | 420 ms | 8 ms |
| 冷启动惩罚 | 首次 +1800 ms | 首次 +12 ms |
| 内存峰值 | 720 MB | 18 MB |
| 镜像 / 模块大小 | 280 MB | 4.5 MB |
| CJK 字形 | ❌ 除非显式安装 | ✅ 内嵌 NotoSans CJK |
| 100K 次渲染成本 | ~$240(Lambda 算力) | ~$5(gPdf Basic 套餐) |
最后一行经常让人吃惊。差距是真的,不是钓鱼价——是结构性的。我们不需要摊销 Chromium 启动、浏览器内存、容器冷启动,所以单次渲染的单位成本本来就极小。
「但 $5/100K 听着太便宜,是不是有坑?」
坑就是我们不把浏览器放进生成链路。在热 V8 isolate 上跑二进制渲染器的成本是毫秒级 CPU 加千字节级内存。按 Puppeteer 的价格收费,等于在为我们没在跑的基础设施收费。
什么情况下你还是该选 Puppeteer
如果我们的回答永远是「用 gPdf」,我们会是最差的咨询对象。诚实的几种情况:
-
你已经在生产跑 Puppeteer 而且它在工作。 别为了换而换。重新评估 gPdf 的合适时机是 Puppeteer 开始痛起来——通常是月算力账单越过 $400,或者冷启动 SLA 把下游什么东西搞坏了。
-
你的文档就是网页,仅此而已。 一份 60 页用户生成、套你设计系统、含嵌套图表和动态内容的报告,不是 JSON 迁移——是重新设计。
-
你需要和网页预览做像素一致。 某些工作流(比如「编辑器里所见即所打印」)真的需要两边都用 Chromium 当渲染器。
如果以上都不适用,数学很直白:更小部署、更低延迟、更低账单、字节一致输出,也不再被字体安装问题拖住。
怎么迁移真实工作负载
如果你已经被说服愿意试,迁移通常是每文档类型 1–2 天的小型 spike,不是重新架构:
- 选一份文档——从最大量的开始,不是最复杂的。
- 把 HTML 模板的逻辑分块映射到 gPdf JSON 元素(
text、box、table、barcode、image)。 - 用在线体验迭代一份真实的 DocumentRequest,直到输出符合预期。
- 写个小映射函数把现有数据接到 JSON。
- 把新接口和你的 Puppeteer 接口 A/B 跑一周。Diff PDF。决定。
大多团队一天内就接受了 JSON 模型。难的不是新工具——是拆解旧模板上长年累积的 HTML/CSS 体操。
一句话
Puppeteer 是网页的正确答案。对文档,你为了避免「把文档描述成数据」这个一次性小成本,每次渲染要交 100–200× 的税。如果你的系统在批量生成发票、运单、收据、对账单、票据,或任何「形状一样、值不同」的文档,像 gPdf 这样的边缘原生 PDF 生成器会更快、更小、更便宜、更确定——而且这是可测量的。
来在线体验试试——它是真实运行在边缘的 Worker,无需注册,浏览器里 5 ms 内出响应。