Puppeteer Page.pdf() 全解析:从 print 媒体类型到 PDFOptions 参数、页眉页脚与底层实现
2026/9/8 23:43:56 网站建设 项目流程

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)。下表完整罗列每个属性的类型、作用与默认值,请按需对照使用:

属性类型说明默认值
displayHeaderFooterboolean是否显示页眉与页脚(需配合headerTemplate/footerTemplatefalse
headerTemplatestring打印页眉的 HTML 模板(无)
footerTemplatestring打印页脚的 HTML 模板,约束与特殊类名支持同headerTemplate(无)
formatPaperFormat纸张格式;一旦设置即优先于width/height'letter'
widthstring \| number纸宽,可传数字或带单位的字符串(无)
heightstring \| number纸高,可传数字或带单位的字符串(无)
landscapeboolean是否横向打印false
marginPDFMargin设置 PDF 页边距undefined(不设边距)
omitBackgroundboolean隐藏默认白底,允许生成透明背景的 PDFfalse
outlineboolean(实验性)生成文档大纲(书签目录)false
pageRangesstring要打印的页码范围,如1-5, 8, 11-13空字符串(打印全部页)
pathstringPDF 保存路径;相对路径相对于当前工作目录解析undefined(不写盘)
preferCSSPageSizeboolean让页面声明的 CSS@page尺寸优先于width/height/formatfalse(内容缩放到适配纸张)
printBackgroundboolean设为true以打印背景图形/背景色false
scalenumber页面渲染缩放比例,取值必须介于0.12之间1
taggedboolean(实验性)生成带标签(无障碍可访问)的 PDFtrue
timeoutnumber超时时间(毫秒),传0表示禁用超时30_000
waitForFontsbooleantrue时等待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 },并在此基础上解析widthheight与四向margin。若未指定format且未传宽高,则回落到width = 8.5height = 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)
Letter8.5 x 1121.59 x 27.94
Legal8.5 x 1421.59 x 35.56
Tabloid11 x 1727.94 x 43.18
Ledger17 x 1143.18 x 27.94
A033.1102 x 46.81184.1 x 118.9
A123.3858 x 33.110259.4 x 84.1
A216.5354 x 23.385842 x 59.4
A311.6929 x 16.535429.7 x 42
A48.2677 x 11.692921 x 29.7
A55.8268 x 8.267714.8 x 21
A64.1339 x 5.826810.5 x 14.8

以上尺寸在源码中以常量表形式维护于 packages/puppeteer-core/src/common/PDFOptions.ts#L226-L274(paperFormats记录每种格式的cmin两套尺寸,A 系列英寸数值由厘米换算后四舍五入到四位小数)。

宽高的单位换算规则

width/heightmargin支持"数字或带单位字符串"。数字会被当作像素处理,字符串需携带单位。底层换算表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时,可通过headerTemplatefooterTemplate自定义页眉/页脚内容。模板必须是合法 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, });

footerTemplateheaderTemplate拥有相同的约束条件与占位类支持。注意页眉/页脚只有在页面上/下留有足够边距时才可见,因此通常需要配合margin一起设置。

六、结果输出:内存字节数组与写盘

pdf()默认不写盘,直接把 PDF 内容以Uint8Array返回(path默认undefined)。这便于你自行决定后续处理:上传到对象存储、作为附件发送邮件、或在浏览器中触发下载。若传入path(相对路径按当前工作目录解析),则会同步把文件写入磁盘,同时仍返回字节数组。

在 CDP 实现中(packages/puppeteer-core/src/cdp/Page.ts#L1212-L1298),内部调用链是:

  1. 归一化参数(parsePDFOptions);
  2. omitBackground为真,先通过Emulation.setDefaultBackgroundColorOverride把默认背景色改为透明(见setTransparentBackgroundColor相关逻辑);
  3. 向主目标发送 CDP 命令Page.printToPDF,且transferMode: 'ReturnAsStream'(大文件走流式传输而非一次性返回 base64);
  4. 从协议流(result.stream)读取完整数据;
  5. 通过getReadableAsTypedArray汇聚为Uint8Array,若配置了path则一并写盘。

其中landscapedisplayHeaderFooterheaderTemplatefooterTemplateprintBackgroundscalepaperWidth/Height、四个方向的marginpageRangespreferCSSPageSizegenerateTaggedPDFgenerateDocumentOutline等都会原样映射到 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

preferCSSPageSizetrue时,页面内@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 版本存在三处明显差异:

  1. 单位基准不同:BiDi 实现调用parsePDFOptions(options, 'cm'),即统一按厘米为单位解析宽高与边距,再传给底层browsingContext.print命令;
  2. 协议命令不同:不再走Page.printToPDF,而是调用 WebDriver BiDi 的browsingContext.print,并把landscape映射为orientation: 'landscape' | 'portrait'preferCSSPageSize映射为shrinkToFit: !preferCSSPageSize
  3. 流程更显式:会先显式等待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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询