在 Node.js 中把 HTML 页面导出为 PDF

在这个无纸化的时代,我们越来越多的日常操作是在 Web 页面上完成,比如编写文档、记笔记、签合同等等,但很多时候我们需要把这些数据转换为 PDF 文件下载下来,打印、归档或者是作为文件共享。

在 Chrome 浏览器中我们可以直接 「右键 -> 打印」来打印一个网页或者是存储为 PDF,但这样操作往往满足不了我们的需求,打印内容不容易定制并且操作也不够方便,我们希望点击一个「下载按钮」就能够把文件下载下来,这个时候就需要程序特殊处理了,那么在 Node.js 中该如何实现呢?

在 Node.js 中一般会通过 PhantomJS 或者 Puppeteer 来实现。这两个均为无头浏览器,提供了完善的 API 来实现对 Chrome 的一些操作,可以用来对页面进行性能分析、页面快照或者是做一些服务端渲染。下面我们来分别看一下该如何实现。

使用 PhantomJS

首先安装 phantom,这是一个对 PhantomJS 进行二次封装的包,以方便在 Node.js 中使用。

npm i phantom --save
# 或 yarn add phantom

下面就是具体实现了,我们用 phantom 访问下 Google,然后把页面保存为 PDF。

// render_to_pdf.js
// 代码需要在 Node v8.x 以上版本运行,直接 node render_to_pdf.js 即可
const phantom = require('phantom');

async function renderToPdf() {
const instance = await phantom.create();
const page = await instance.createPage();

// 设置视口大小,相当于浏览器窗口
page.property('viewportSize', { width: 1200, height: 700 });

// 设置页面尺寸来控制在 PDF 中的展现形式
page.property('paperSize', {
format: 'A4',
width: '1200px',
height: '700px',
orientation: 'portrait'
});

const status = await page.open('https://www.google.com');
console.log('status: ', status);
const pdf = await page.render('./google.pdf');
console.log('pdf: ', pdf);
await instance.exit();
}

renderToPdf();

运行代码,我们就可以直接得到 PDF 文件了。如果需要更精致的操作,还可以参考下 html-pdf 这个包,也是基于 PhantomJS 的,不过封装了更多可定制的功能,比如添加页眉、页脚等。

使用 Puppeteer

Puppeteer 是 Chrome 自身推出的一个无头浏览器,提供了丰富的 Node API,使用起来要比 PhantomJS 更加方便。

同样我们需要安装 Puppeteer 这个包

npm i puppeteer --save
# 或者 yarn add puppeteer

如果安装过程过慢或者报错,我们可以选择跳过 Chromium 的下载,因为它本身就将近 200M,稍后可以手动下载。

env PUPPETEER_SKIP_CHROMIUM_DOWNLOAD="true" npm i --save puppeteer

然后到 https://download-chromium.appspot.com/ 手动下载 Chromium,解压到项目根目录(可以在 .gitignore 里忽略它)。

然后来实现导出 HTML 为 PDF 的功能,不过需要配置下 Chromium 的路径 executablePath

const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch({
executablePath: './chrome-mac/Chromium.app/Contents/MacOS/Chromium'
});
const page = await browser.newPage();
await page.goto('https://www.google.com', {waitUntil: 'networkidle2'});
await page.pdf({
path: './google.pdf',
format: 'A4'
});

await browser.close();
})();

对于 PDF 的尺寸、留白等都可以在官方文档里查到,可以根据具体需求进行配置。

Ok,以上就是在 Node.js 中把 HTML 页面导出为 PDF 的方法,由于目前 PhantomJS 项目本身处于暂停更新的状态,推荐使用 Puppeteer 来进行处理。