ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Puppeteer Page.pdf() 全解析:从 print 媒体类型到 PDFOptions 参数、页眉页脚与底层实现

Puppeteer Page.pdf() 全解析:从 print 媒体类型到 PDFOptions 参数、页眉页脚与底层实现 Puppeteer Page.pdf() 全解析从 print 媒体类型到 PDFOptions 参数、页眉页脚与底层实现【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteerPuppeteer 的Page.pdf()方法可以把当前页面按照打印样式printCSS 媒体类型渲染成 PDF是生成发票、报表、简历、网页存档等场景的核心能力。本指南以官方 API 文档 Page.pdf() 为主体逐一展开其返回类型、完整参数表PDFOptions、纸张格式PaperFormat与页边距PDFMargin的取值细节并结合仓库内 CDP / WebDriver BiDi 两条底层实现路径帮助你写出可直接运行且行为可控的 PDF 生成代码。一、方法签名与基本行为Page.pdf()是Page类上的抽象方法官方签名如下class Page { abstract pdf(options?: PDFOptions): PromiseUint8Array; }参数options可选类型为 PDFOptions用于配置 PDF 生成返回值PromiseUint8Array即 PDF 文件内容的二进制字节数组。其核心行为在 Page.pdf() 文档中描述得非常明确以printCSS 媒体类型生成页面 PDF。也就是说默认情况下页面中针对media print编写的样式如隐藏导航栏、去除交互元素、调整分页会被应用这与你在浏览器中执行打印 → 另存为 PDF的效果保持一致。抽象方法的类型定义位于 packages/puppeteer-core/src/api/Page.ts#L2867-L2870而具体实现按协议分流Chrome DevTools ProtocolCDP路径在 packages/puppeteer-core/src/cdp/Page.tsWebDriver 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。下表完整罗列每个属性的类型、作用与默认值请按需对照使用属性类型说明默认值displayHeaderFooterboolean是否显示页眉与页脚需配合headerTemplate/footerTemplatefalseheaderTemplatestring打印页眉的 HTML 模板无footerTemplatestring打印页脚的 HTML 模板约束与特殊类名支持同headerTemplate无formatPaperFormat纸张格式一旦设置即优先于width/heightletterwidthstring \| number纸宽可传数字或带单位的字符串无heightstring \| number纸高可传数字或带单位的字符串无landscapeboolean是否横向打印falsemarginPDFMargin设置 PDF 页边距undefined不设边距omitBackgroundboolean隐藏默认白底允许生成透明背景的 PDFfalseoutlineboolean实验性生成文档大纲书签目录falsepageRangesstring要打印的页码范围如1-5, 8, 11-13空字符串打印全部页pathstringPDF 保存路径相对路径相对于当前工作目录解析undefined不写盘preferCSSPageSizeboolean让页面声明的 CSSpage尺寸优先于width/height/formatfalse内容缩放到适配纸张printBackgroundboolean设为true以打印背景图形/背景色falsescalenumber页面渲染缩放比例取值必须介于0.1与2之间1taggedboolean实验性生成带标签无障碍可访问的 PDFtruetimeoutnumber超时时间毫秒传0表示禁用超时30_000waitForFontsboolean为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 的英寸尺寸。marginPDFMargin 边距结构margin使用 PDFMargin 接口四个方向均为可选的string | numberexport 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 | UppercaseLowerCasePaperFormat | CapitalizeLowerCasePaperFormat | LowerCasePaperFormat;即你既可以写a4也可以写A4、Letter等任意大小写组合基础枚举见 LowerCasePaperFormat。各格式的精确英寸与厘米尺寸如下格式英寸 (in)厘米 (cm)Letter8.5 x 1121.59 x 27.94Legal8.5 x 1421.59 x 35.56Tabloid11 x 1727.94 x 43.18Ledger17 x 1143.18 x 27.94A033.1102 x 46.81184.1 x 118.9A123.3858 x 33.110259.4 x 84.1A216.5354 x 23.385842 x 59.4A311.6929 x 16.535429.7 x 42A48.2677 x 11.692921 x 29.7A55.8268 x 8.267714.8 x 21A64.1339 x 5.826810.5 x 14.8以上尺寸在源码中以常量表形式维护于 packages/puppeteer-core/src/common/PDFOptions.ts#L226-L274paperFormats记录每种格式的cm与in两套尺寸A 系列英寸数值由厘米换算后四舍五入到四位小数。宽高的单位换算规则width/height与margin支持数字或带单位字符串。数字会被当作像素处理字符串需携带单位。底层换算表unitToPixels定义于 packages/puppeteer-core/src/common/util.ts#L378-L382export 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文档地址locationpageNumber当前页码totalPages文档总页数典型用法示例await page.pdf({ displayHeaderFooter: true, headerTemplate: div stylefont-size:8px; width:100%; text-align:center; color:#888; 我的报表 · span classtitle/span /div, footerTemplate: div stylefont-size:8px; width:100%; text-align:center; color:#888; span classpageNumber/span / span classtotalPages/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 与 waitForFontstimeout默认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 bugbugs.chromium.org issue 840455。preferCSSPageSize 与 CSS pagepreferCSSPageSize为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 给出了最简可用范式——启动浏览器、打开页面、生成 PDFimport 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 stylewidth:100%;text-align:center;font-size:8px;span classtitle/span/div, footerTemplate: div stylewidth:100%;text-align:center;font-size:8px;span classpageNumber/span / span classtotalPages/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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进