博客

gPdf vs Puppeteer:什么时候 800 MB 的 Chromium 是错误答案

Puppeteer 能把任何网页渲染成 PDF,但你大部分时候是在为一个用不上的无头浏览器买单。一份给 2026 年技术选型用的实用对比。

如果你今天是 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 秒。每次渲染都要:

  1. 启动浏览器进程(或复用进程池)
  2. 开新标签页
  3. 导航到你的 HTML / URL
  4. domcontentloaded(通常还要等字体、图片、Web Components)
  5. page.pdf(),把已绘制的页面通过 Chromium 的 PDF 引擎序列化
  6. 销毁标签页

这是「整个 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 秒
单次渲染 RAM600 – 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 让浏览器去画,而是把文档描述成结构化 JSONDocumentRequest),由 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 ms3.4 ms
p99 延迟420 ms8 ms
冷启动惩罚首次 +1800 ms首次 +12 ms
内存峰值720 MB18 MB
镜像 / 模块大小280 MB4.5 MB
CJK 字形❌ 除非显式安装✅ 内嵌 NotoSans CJK
100K 次渲染成本~$240(Lambda 算力)~$5(gPdf Basic 套餐)

最后一行经常让人吃惊。差距是真的,不是钓鱼价——是结构性的。我们不需要摊销 Chromium 启动、浏览器内存、容器冷启动,所以单次渲染的单位成本本来就极小。

「但 $5/100K 听着太便宜,是不是有坑?」

坑就是我们不把浏览器放进生成链路。在热 V8 isolate 上跑二进制渲染器的成本是毫秒级 CPU千字节级内存。按 Puppeteer 的价格收费,等于在为我们没在跑的基础设施收费。

什么情况下你还是该选 Puppeteer

如果我们的回答永远是「用 gPdf」,我们会是最差的咨询对象。诚实的几种情况:

  1. 你已经在生产跑 Puppeteer 而且它在工作。 别为了换而换。重新评估 gPdf 的合适时机是 Puppeteer 开始痛起来——通常是月算力账单越过 $400,或者冷启动 SLA 把下游什么东西搞坏了。

  2. 你的文档就是网页,仅此而已。 一份 60 页用户生成、套你设计系统、含嵌套图表和动态内容的报告,不是 JSON 迁移——是重新设计。

  3. 你需要和网页预览做像素一致。 某些工作流(比如「编辑器里所见即所打印」)真的需要两边都用 Chromium 当渲染器。

如果以上都不适用,数学很直白:更小部署、更低延迟、更低账单、字节一致输出,也不再被字体安装问题拖住。

怎么迁移真实工作负载

如果你已经被说服愿意试,迁移通常是每文档类型 1–2 天的小型 spike,不是重新架构:

  1. 选一份文档——从最大量的开始,不是最复杂的。
  2. 把 HTML 模板的逻辑分块映射到 gPdf JSON 元素(textboxtablebarcodeimage)。
  3. 在线体验迭代一份真实的 DocumentRequest,直到输出符合预期。
  4. 写个小映射函数把现有数据接到 JSON。
  5. 把新接口和你的 Puppeteer 接口 A/B 跑一周。Diff PDF。决定。

大多团队一天内就接受了 JSON 模型。难的不是新工具——是拆解旧模板上长年累积的 HTML/CSS 体操。

一句话

Puppeteer 是网页的正确答案。对文档,你为了避免「把文档描述成数据」这个一次性小成本,每次渲染要交 100–200× 的税。如果你的系统在批量生成发票、运单、收据、对账单、票据,或任何「形状一样、值不同」的文档,像 gPdf 这样的边缘原生 PDF 生成器会更快、更小、更便宜、更确定——而且这是可测量的。

在线体验试试——它是真实运行在边缘的 Worker,无需注册,浏览器里 5 ms 内出响应。