需求
有一个网页版的报告需要打印成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, }];
坑点: 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
安装的也很快, 这次终于打印了一个完美的网页.
散花了.