Node-Puppeteer 打印网页成PDF--坑点去死吧.

需求

有一个网页版的报告需要打印成pdf, 供用户下载. 要求打印原版的网页, 美观漂亮. 

开始用手吧

安装不用多说, 老套路, 没啥子坑.

npm i puppeteer

网上抄一段打印pdf的代码. 

...
let browser = await puppeteer.launch();
let page = await browser.newPage();
let options = { waitUntil: ‘networkidle0‘ };
page.setCookie(...cookies);
let url = `http://0.0.0.0/page/report/down?rid=3a1325b6`;
await page.goto(url, options);
let pdf = await page.pdf({format: ‘A4‘, fullPage: true);
await page.close();
await browser.close();
...
res.set({ ‘Content-Disposition‘: `attachment`, ‘filename‘: ‘report.pdf‘, ‘Content-Type‘: ‘application/octet-stream‘, ‘Content-Length‘: pdf.length });
res.send(pdf);

这段代码大部分执行都没啥子问题, 但没有输出report.pdf文件.  好吧, 逐一填坑.

坑点: Res的header不正确

修改res.set的内容, 把文件名写到Content-Disposition对象上去. 

旧: 

res.set({ ‘Content-Disposition‘: ‘attachment‘, ‘filename‘: ‘report.pdf‘, ‘Content-Type‘: ‘application/octet-stream‘, ‘Content-Length‘: pdf.length })

修改后: 

res.set({ ‘Content-Disposition‘: `attachment;filename=report.pdf`, ‘Content-Type‘: ‘application/octet-stream‘, ‘Content-Length‘: pdf.length });

 经过修改, 这次输出了pdf文件. 

但是, 新坑来了, pdf只有一页, 并且显示不合, 仅显示网页上边中间的部分内容, 没有背景, 整个内容奇丑无比且, 看来是应该是pdf的设置不正确, 丢失了背景色, A4大小也不能适用于 1200宽的网页. 

坑点: 没有网页背景

这个好解决, 加入一个参数即可 :

let pdf = await page.pdf({
   format: ‘A4‘, printBackground:
true });

 

坑点: 修改PDF页面大小以显示全部内容

这是花了我时间最多的地方, 并一度放弃改为图片输出. 但后来将就着找到解决办法. 

不通的办法: 

  • 删除format参数, 无效
  • 设置fullPage参数, 无效
  • 使用网上的自动滚动代码, 把网页滚到底部, 无效
  • 抄一段仅打印body元素内容,无效. 
  • 读取body的宽度, 并将page的viewport设置为该值, 无效. 
  • ...

是不是因为网页没有渲染完成呢? 因为是vue项目, 先下回代码来, 然后再由浏览器计算得到网页, 极有可能呀, 改吧. 

let options = { waitUntil: ‘networkidle0‘ };
let options = { waitUntil: ‘networkidle2‘ };
page.waitForTimeout(8000);
page.waitForFunction(‘window.renderdone‘, ...

上面全部失败, 根本不是解决问题的核心点. 

看来, 解决的途径还应该是读取正确的网页宽高值, 可上面的试过的方法怎么就不起作用了. 经详细检查, 发现, 读取的body的宽度值不正确, 高度总是0, 所以, 打印的pdf肯定不正确. 

打印了一下另外一个网站的网页, 我K, 竟然全页成功了. 可为什么我的不行呢?  我的网页是VUE写的, 与其他有什么不同呢? 最终都是生成html呀. ..

chrome 开发工具检查一下宽高值, 发现 body 元素真的是 0高度, 无语. 改用 id 为app的元素, 成功读取到了网页的宽高值

具体代码如下: 

const dimensions = await page.evaluate(() => {
return {
        width: document.getElementById("app").scrollWidth + 100,
        height: document.getElementById("app").scrollHeight + 100,
        deviceScaleFactor: window.devicePixelRatio
    };
});
let pdf = await page.pdf({
        margin: {
        top: ‘0.5cm‘,
        bottom: ‘0.5cm‘,
        left: ‘0.5cm‘,
        right: ‘0.5cm‘
        }, 
  printBackground:
true,
width: dimensions.width,
height: dimensions.height });

顺便, 打印一下pdf的边距, 设成0.5cm, 并把网页的宽度高度各加了100, 以抵销边距的开销, 保证内容不丢失.

上面打印的网页只是一个示例页, 还不是最终的页面, 因为最终页面需要登录, 得需要解决登录的问题.

这个网站使用的cookie中存着用户信息 userInfo. 和一个原始的token, 在具体的网页中, 需要读取这两个cookie值完成用户的检查. 

于是加上cookie的内容: 

let cookies = [{
        name: ‘token‘,
        value: token
    }, {
        name: ‘userInfo‘,
        value: userInfo,
}];
  page.setCookie(...cookies);

坑点: Cookie的内容要写正确

我去, 竟然不起作用. 为什么?  查找资源, 原来cookie的对象内容不正确,要把域名, 过期时间, 路径等等都写上. 

最终的结果: 

let cookies = [{
    name: ‘token‘,
    value: token,
    path: ‘/‘,
    domain: conf.app.domain,
    expires: -1,
    httpOnly: false,
    secure: false,
    session: true
}, {
    name: ‘userInfo‘,
    value: userInfo,
    path: ‘/‘,
    domain: conf.domain,
    expires: -1,
    httpOnly: false,
    secure: false,
    session: true
}];
page.setCookie(...cookies);

这次读取到了cookie, 开心. 

最终的结果:

可以打印完整网页的pdf, 但是仅一页, 不像其他pdf一样, 一个页面一个页面的, 这个就不再改了, 一来是分页比较麻烦, 二来是即便是分了, 数据内容被拆到多页,反而不利于阅读.  三就是我不会拆页(汗).

 

完成pdf的打印, 又顺手试了一下png的打印, 没有问题, 只要把图片的大小设置好, 成功打印了一个png图片. 

好了, 线上部署吧. 

坑点: 服务器部署

使用的centos 7的服务器, 

正确上传代码,然后 "npm i"

正在所预料的不正确, 改为"cnpm i", 非常快, 100多M在服务器上是小case. 

启动项目, .. 尝试打印一个网页,  报错, 说什么browser没有启动. 

找解决方法, 明明是本机测试正常的代码, 到了服务器就不行呢, ... 可能是macos和centos的差异吧. 

网搜解决办法, 需要安装一些依赖的包: 

yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc

好了. 很快安装完毕, 终于可以启动了. 什么? 又报错了, 看了一下错误原因, brower启动不能在不使用sand-box时情况以root用户吂动. 

坑点: 使用chrome, 不能以root账号直接使用

搜索一下吧, 很快找到了答案: 

let browserOptions = { args: [--no-sandbox, --disable-setuid-sandbox] };
let browser = await puppeteer.launch(browserOptions);

加上一个参数即可, 我懒得换服务器上的账号了, 就这样将就着吧. 

再打印一个pdf吧.... 怎么, 里面的汉字全成了框框与叉叉了! 应该是服务器上没有字体. 这个在其他项目上解决过, 这次就好办了.

坑点: 打印中文内容, 服务器上需要安装中文字体

轻车熟路: 

yum groupinstall Fonts

安装的也很快, 这次终于打印了一个完美的网页. 

散花了. 

 

Node-Puppeteer 打印网页成PDF--坑点去死吧.

上一篇:html css 旋转 圆


下一篇:kubernetes网络介绍