Puppeteer Page.pdf() 全解析:从 print 媒体类型到 PDFOptions 参数、页眉页脚与底层实现
【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer
Puppeteer 的Page.pdf()方法可以把当前页面按照打印样式(printCSS 媒体类型)渲染成 PDF,是生成发票、报表、简历、网页存档等场景的核心能力。本指南以官方 API 文档 Page.pdf() 为主体,逐一展开其返回类型、完整参数表(PDFOptions)、纸张格式(PaperFormat)与页边距(PDFMargin)的取值细节,并结合仓库内 CDP / WebDriver BiDi 两条底层实现路径,帮助你写出可直接运行且行为可控的 PDF 生成代码。
一、方法签名与基本行为
Page.pdf()是Page类上的抽象方法,官方签名如下:
class Page { abstract pdf(options?: PDFOptions): Promise<Uint8Array>; }- 参数:
options(可选),类型为 PDFOptions,用于配置 PDF 生成; - 返回值:
Promise<Uint8Array>,即 PDF 文件内容的二进制字节数组。
其核心行为在 Page.pdf() 文档中描述得非常明确:以printCSS 媒体类型生成页面 PDF。也就是说,默认情况下页面中针对@media print编写的样式(如隐藏导航栏、去除交互元素、调整分页)会被应用;这与你在浏览器中执行"打印 → 另存为 PDF"的效果保持一致。
抽象方法的类型定义位于 packages/puppeteer-core/src/api/Page.ts#L2867-L2870,而具体实现按协议分流:Chrome DevTools Protocol(CDP)路径在 packages/puppeteer-core/src/cdp/Page.ts,WebDriver BiDi 路径在 packages/puppeteer-core/src/bidi/Page.ts。两条实现共用同一套PDFOptions参数模型,因此上层调用方式一致。
二、print 与 screen 媒体类型:如何让 PDF 用屏幕样式渲染
文档特别强调了一个极易踩坑的点:page.pdf()默认使用print媒体类型。如果页面没有专门编写打印样式,视觉上可能与你在浏览器里看到的不一致。
用 emulateMediaType('screen') 输出屏幕样式
若要生成**屏幕样式(screen媒体类型)**的 PDF,官方给出的做法是在调用page.pdf()之前先调用page.emulateMediaType('screen'):
// 在模拟 screen 媒体类型后,matchMedia 行为如下 await page.evaluate(() => matchMedia('screen').matches); // → true await page.evaluate(() => matchMedia('print').matches); // → false await page.emulateMediaType('print'); await page.evaluate(() => matchMedia('screen').matches); // → false await page.evaluate(() => matchMedia('print').matches); // → true await page.emulateMediaType(null); // null 关闭媒体模拟 await page.evaluate(() => matchMedia('screen').matches); // → true该示例完整收录在 emulateMediaType() 文档中。注意emulateMediaType的合法取值只有'screen'、'print'与null,传入null即关闭 CSS 媒体类型模拟、恢复浏览器默认行为。
颜色调整:-webkit-print-color-adjust
另一个文档强调的细节是:默认情况下page.pdf()生成的是"为打印做了颜色调整"的 PDF。打印模式往往会对背景色、对比度做优化,导致颜色与屏幕所见有差异。若要求严格还原颜色,应使用 CSS 的-webkit-print-color-adjust属性强制渲染精确颜色,例如:
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }与之相关的背景绘制开关则是PDFOptions.printBackground(见下节),两者配合才能得到"所见即所得"的彩色 PDF。
三、PDFOptions 完整参数表
page.pdf()的全部可配置项集中在 PDFOptions 接口中(源码定义见 packages/puppeteer-core/src/common/PDFOptions.ts#L73-L189)。下表完整罗列每个属性的类型、作用与默认值,请按需对照使用:
| 属性 | 类型 | 说明 | 默认值 |
|---|---|---|---|
displayHeaderFooter | boolean | 是否显示页眉与页脚(需配合headerTemplate/footerTemplate) | false |
headerTemplate | string | 打印页眉的 HTML 模板 | (无) |
footerTemplate | string | 打印页脚的 HTML 模板,约束与特殊类名支持同headerTemplate | (无) |
format | PaperFormat | 纸张格式;一旦设置即优先于width/height | 'letter' |
width | string \| number | 纸宽,可传数字或带单位的字符串 | (无) |
height | string \| number | 纸高,可传数字或带单位的字符串 | (无) |
landscape | boolean | 是否横向打印 | false |
margin | PDFMargin | 设置 PDF 页边距 | undefined(不设边距) |
omitBackground | boolean | 隐藏默认白底,允许生成透明背景的 PDF | false |
outline | boolean(实验性) | 生成文档大纲(书签目录) | false |
pageRanges | string | 要打印的页码范围,如1-5, 8, 11-13 | 空字符串(打印全部页) |
path | string | PDF 保存路径;相对路径相对于当前工作目录解析 | undefined(不写盘) |
preferCSSPageSize | boolean | 让页面声明的 CSS@page尺寸优先于width/height/format | false(内容缩放到适配纸张) |
printBackground | boolean | 设为true以打印背景图形/背景色 | false |
scale | number | 页面渲染缩放比例,取值必须介于0.1与2之间 | 1 |
tagged | boolean(实验性) | 生成带标签(无障碍可访问)的 PDF | true |
timeout | number | 超时时间(毫秒),传0表示禁用超时 | 30_000 |
waitForFonts | boolean | 为true时等待document.fonts.ready完成 | true |
这些默认值与底层参数归一化逻辑一一对应。在 packages/puppeteer-core/src/common/util.ts#L317-L334 的parsePDFOptions()中可以看到默认值集合{ scale: 1, displayHeaderFooter: false, headerTemplate: '', footerTemplate: '', printBackground: false, landscape: false, pageRanges: '', preferCSSPageSize: false, omitBackground: false, outline: false, tagged: true, waitForFonts: true },并在此基础上解析width、height与四向margin。若未指定format且未传宽高,则回落到width = 8.5、height = 11(即 letter 的英寸尺寸)。
margin:PDFMargin 边距结构
margin使用 PDFMargin 接口,四个方向均为可选的string | number:
export interface PDFMargin { top?: string | number; bottom?: string | number; left?: string | number; right?: string | number; }例:margin: { top: '1in', bottom: '1in', left: '0.5in', right: '0.5in' }。方向的数值/字符串同样支持下列单位换算规则。
四、纸张尺寸:PaperFormat、自定义宽高与单位换算
预置格式与尺寸表
format选项的类型为 PaperFormat,其类型定义相当宽松:
export type PaperFormat = | Uppercase<LowerCasePaperFormat> | Capitalize<LowerCasePaperFormat> | LowerCasePaperFormat;即你既可以写'a4',也可以写'A4'、'Letter'等任意大小写组合(基础枚举见 LowerCasePaperFormat)。各格式的精确英寸与厘米尺寸如下:
| 格式 | 英寸 (in) | 厘米 (cm) |
|---|---|---|
| Letter | 8.5 x 11 | 21.59 x 27.94 |
| Legal | 8.5 x 14 | 21.59 x 35.56 |
| Tabloid | 11 x 17 | 27.94 x 43.18 |
| Ledger | 17 x 11 | 43.18 x 27.94 |
| A0 | 33.1102 x 46.811 | 84.1 x 118.9 |
| A1 | 23.3858 x 33.1102 | 59.4 x 84.1 |
| A2 | 16.5354 x 23.3858 | 42 x 59.4 |
| A3 | 11.6929 x 16.5354 | 29.7 x 42 |
| A4 | 8.2677 x 11.6929 | 21 x 29.7 |
| A5 | 5.8268 x 8.2677 | 14.8 x 21 |
| A6 | 4.1339 x 5.8268 | 10.5 x 14.8 |
以上尺寸在源码中以常量表形式维护于 packages/puppeteer-core/src/common/PDFOptions.ts#L226-L274(paperFormats记录每种格式的cm与in两套尺寸,A 系列英寸数值由厘米换算后四舍五入到四位小数)。
宽高的单位换算规则
width/height与margin支持"数字或带单位字符串"。数字会被当作像素处理,字符串需携带单位。底层换算表unitToPixels定义于 packages/puppeteer-core/src/common/util.ts#L378-L382:
export const unitToPixels = { px: 1, in: 96, cm: 37.8, mm: 3.78, // ... };解析逻辑(convertPrintParameterToInches):读取字符串末尾两位作为单位,若命中上述单位则乘以对应像素系数;若单位无法识别,则把整个字符串当作像素数解析(这一行为与 PhantomJS 的paperSize保持一致),解析失败会抛出Failed to parse parameter value错误。因此以下写法都合法:
await page.pdf({ width: '210mm', // 等价于 A4 宽度 height: '297mm', margin: {top: '20mm', right: '10mm', bottom: '20mm', left: '10mm'}, });五、页眉与页脚模板:五个内置占位类
当displayHeaderFooter: true时,可通过headerTemplate与footerTemplate自定义页眉/页脚内容。模板必须是合法 HTML,并借助以下特殊 class注入动态值(官方文档与 源码注释 双重确认):
| class 名 | 注入内容 |
|---|---|
date | 格式化后的打印日期 |
title | 文档标题 |
url | 文档地址(location) |
pageNumber | 当前页码 |
totalPages | 文档总页数 |
典型用法示例:
await page.pdf({ displayHeaderFooter: true, headerTemplate: ` <div style="font-size:8px; width:100%; text-align:center; color:#888;"> 我的报表 · <span class="title"></span> </div>`, footerTemplate: ` <div style="font-size:8px; width:100%; text-align:center; color:#888;"> <span class="pageNumber"></span> / <span class="totalPages"></span> </div>`, margin: {top: '80px', bottom: '80px'}, format: 'a4', printBackground: true, });footerTemplate与headerTemplate拥有相同的约束条件与占位类支持。注意页眉/页脚只有在页面上/下留有足够边距时才可见,因此通常需要配合margin一起设置。
六、结果输出:内存字节数组与写盘
pdf()默认不写盘,直接把 PDF 内容以Uint8Array返回(path默认undefined)。这便于你自行决定后续处理:上传到对象存储、作为附件发送邮件、或在浏览器中触发下载。若传入path(相对路径按当前工作目录解析),则会同步把文件写入磁盘,同时仍返回字节数组。
在 CDP 实现中(packages/puppeteer-core/src/cdp/Page.ts#L1212-L1298),内部调用链是:
- 归一化参数(
parsePDFOptions); - 若
omitBackground为真,先通过Emulation.setDefaultBackgroundColorOverride把默认背景色改为透明(见setTransparentBackgroundColor相关逻辑); - 向主目标发送 CDP 命令
Page.printToPDF,且transferMode: 'ReturnAsStream'(大文件走流式传输而非一次性返回 base64); - 从协议流(
result.stream)读取完整数据; - 通过
getReadableAsTypedArray汇聚为Uint8Array,若配置了path则一并写盘。
其中landscape、displayHeaderFooter、headerTemplate、footerTemplate、printBackground、scale、paperWidth/Height、四个方向的margin、pageRanges、preferCSSPageSize、generateTaggedPDF、generateDocumentOutline等都会原样映射到 CDP 请求体(见 packages/puppeteer-core/src/cdp/Page.ts#L1250-L1271)。
七、进阶选项的原理与坑点
timeout 与 waitForFonts
timeout默认30_000毫秒,传0可关闭超时;该默认值可通过 Page.setDefaultTimeout() 全局修改。在 CDP 路径中,打印命令使用 RxJS 的raceWith(timeout(ms))进行超时竞速。waitForFonts默认true,即生成 PDF 前会等待document.fonts.ready完成,以避免字体未加载完导致缺字。文档提醒:若页面在后台,可能需要先用 Page.bringToFront() 激活页面才能推进字体加载。
tagged 与 outline:无障碍与书签
tagged(实验性,默认true):生成"带标签的(tagged/可访问)PDF",便于屏幕阅读器解析文档结构。outline(实验性,默认false):生成文档大纲。值得注意的是源码中存在一个针对 Chromium bug(bugs.chromium.org issue 840455)。
preferCSSPageSize 与 CSS @page
preferCSSPageSize为true时,页面内@page规则声明的尺寸会覆盖format/width/height;否则默认按传入纸张尺寸缩放内容以适配。
omitBackground 与 printBackground 的关系
两者容易混淆:omitBackground去掉的是"默认白底",允许输出透明背景;printBackground决定是否绘制页面上元素的背景图形与背景色。二者方向相反,可叠加使用达到"页面内容 + 透明画布"的效果。
八、WebDriver BiDi 侧的差异化实现
除 CDP 外,Puppeteer 也为 WebDriver BiDi 协议实现了pdf()(packages/puppeteer-core/src/bidi/Page.ts#L488-L535),可以从源码结构推断它与 CDP 版本存在三处明显差异:
- 单位基准不同:BiDi 实现调用
parsePDFOptions(options, 'cm'),即统一按厘米为单位解析宽高与边距,再传给底层browsingContext.print命令; - 协议命令不同:不再走
Page.printToPDF,而是调用 WebDriver BiDi 的browsingContext.print,并把landscape映射为orientation: 'landscape' | 'portrait'、preferCSSPageSize映射为shrinkToFit: !preferCSSPageSize; - 流程更显式:会先显式等待
document.fonts.ready,再用stringToTypedArray(data, true)将 base64 结果解码为Uint8Array后写盘返回。
正是因为有这两套实现并存,"一套page.pdf()代码在 Puppeteer 支持的不同浏览器/协议下运行"才成为可能——这既是仓库 packages/puppeteer-core 面向多协议架构设计的体现,也意味着你在排查跨浏览器 PDF 差异时,应当先确认目标走的是哪一条实现路径。
九、完整可运行示例
仓库 examples/pdf.js 给出了最简可用范式——启动浏览器、打开页面、生成 PDF:
import puppeteer from 'puppeteer'; const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://news.ycombinator.com', { waitUntil: 'networkidle2', }); await page.pdf({ path: 'hn.pdf', format: 'letter', }); await browser.close();在此基础上,结合本文所有参数可写出一个"屏幕样式 + A4 + 页眉页脚 + 自定义边距 + 保留背景 + 写入指定目录"的生产级示例:
import puppeteer from 'puppeteer'; const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com/report', {waitUntil: 'networkidle0'}); // 场景 A:用屏幕样式而不是打印样式渲染 await page.emulateMediaType('screen'); const pdfBytes = await page.pdf({ format: 'a4', landscape: false, printBackground: true, // 打印背景色 scale: 1, displayHeaderFooter: true, headerTemplate: '<div style="width:100%;text-align:center;font-size:8px;"><span class="title"></span></div>', footerTemplate: '<div style="width:100%;text-align:center;font-size:8px;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>', margin: {top: '80px', bottom: '80px', left: '15mm', right: '15mm'}, pageRanges: '1-5', // 只打印前 5 页 timeout: 60_000, }); // 将字节数组写入文件(或上传 / 发送) await Bun.write('report.pdf', pdfBytes); // Node 可用 fs.writeFile 等价实现 await browser.close();十、常见问题与排查建议
- 颜色不对 / 背景缺失:先检查
printBackground: true是否设置;仍不一致时,为目标元素补充-webkit-print-color-adjust: exact样式。 - 输出的是打印样式:确认你是否在
pdf()前调用了page.emulateMediaType('screen'),以及调用顺序是否在页面导航完成后。 - 页眉页脚不显示:确认
displayHeaderFooter: true,且margin.top/margin.bottom留出了足够空间(页眉页脚渲染在边距区域内)。 - 打印等待超时:若页面包含大量异步字体/图片,可调大
timeout或先手动等待资源加载;页面在后台时用page.bringToFront()激活。 - 生成了奇怪页数:检查
pageRanges写法,合法的组合格式如1-5, 8, 11-13,空字符串表示打印全部页面。
深入理解Page.pdf()的关键在于记住三点:它渲染的是print媒体类型(除非你先用emulateMediaType('screen')模拟屏幕样式)、默认输出字节数组而非直接写盘、以及format一旦指定便优先于width/height。掌握 PDFOptions 全表与上述实现细节后,你就能在不同浏览器与协议上稳定输出符合预期的 PDF。
【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考