Puppeteer生成pdf

1,898 阅读3分钟

最近公司业务需要打印pdf的功能,先说需求:

  • 封面(就是第一页内容)在web端不用展示,打印的pdf需要展示封面。
  • 内容最多会有6页。

分析过程:

  • 打印简单的pdf(不超过一页内容)的使用原生的window.print()或者vue-print-nb,但是我们公司的pdf内容有十页左右,在实际测试中,不同电脑分页情况还不一样,效果不好。
  • 查了下原因,因为每台电脑的dpi不同,导致打印的宽高度不一样。
  • 尝试用html2canvas,但是问题挺多的,超过一屏幕的内容就截取不了。
  • 之前了解过Puppeteer,可以用来 生成页面pdf,还可以爬虫等等。
  • Puppeteer默认会安装一个特定版本的chrome,因为在node接口是放在服务器上,node接口调用Puppeteer启动是Puppeteer安装的的chrome,那我只要适配这款chrome就行了,这样就不存在电脑差异问题了。

说明:

  1. 公司服务器是centos7
  2. 前端vue

1. 安装Puppeteer

npm install puppeteer --unsafe-perm=true(要加--unsafe-perm=true否则会提示权限不足)

2. 安装Puppeteer依赖

先根据服务器安装的系统安装相关Puppeteer依赖,防止启动Puppeteer失败。

3. 生成pdf代码

const puppeteer = require('puppeteer');
const account = 'xxxx';
const password = 'xxxxxx';

module.exports = {
  printPdf: async function (id,callBack) {
    const browser = await puppeteer.launch({ headless: true, args:['--no-sandbox']});//打开浏览器
    const page = await browser.newPage();//打开一个空白页
    await page.setViewport({width: 1920, height: 720});  // 设置视窗
    await page.goto('http://example.com/login',{ waitUntil: 'networkidle0' });
    // 输入账号密码
    await page.type(".ant-form-item:nth-child(1) .ant-input", account);
    await page.type(".ant-form-item:nth-child(2) .ant-input", password);
    // 登录
    await page.click('.login-form-button'),
    await page.waitForNavigation({ waitUntil: 'networkidle0' }),
    // 跳到报告页面
    await page.goto(`http://example.com/report/${id}`,{ waitUntil: 'networkidle0' });
    await page.pdf({
      path: 'res.pdf',
      printBackground:true,
      format:'A4',
      margin:{
       left:'5px',
       right:'5px',
      }
    });
    //关掉浏览器
    console.log('完成');
    await browser.close()
  }
}

4. node接口

const http= require('http');
const url = require("url");
const querystring = require("querystring");
const fs= require('fs');

const printFn = require('./print-pdf.js')

const secret = require('./secret')  // 使用crypto-js解密id 这步骤可以省略
const server= http.createServer(function(request, response,next){
  // 设置跨域
  response.setHeader("Access-Control-Allow-Origin","*");
  //允许的header类型
  response.setHeader("Access-Control-Allow-Headers","*");
  //跨域允许的请求方式
  response.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
  if(request.method === "OPTIONS"){return response.end();}

  // 获取参数
  const requestUrl = request.url;
  const arg = url.parse(request.url).query;
  const params = querystring.parse(arg);

  if(requestUrl.indexOf( '/printPdf')>=0){
    let id = secret.Decrypt(params.id)  // 解密id
    printFn.printPdf(id).then(r=>{
      fs.readFile('./res.pdf', function(err, data){
        if(!err){
          // encode name 因为filename不能中文
          let realName = encodeURI(params.name,"GBK")
          realName = realName.toString('iso8859-1')
          // 返回pdf文件
          response.setHeader("Content-Type","application/pdf"); 
          response.setHeader("Content-Disposition","attachment; filename="+realName+'.pdf' )
          response.end(data);
        }else{
           response.end(JSON.stringify(err));
        }
      });
    })
  }
})
server.listen(8004)

当时遇到一个问题,前端接口发送到node了,但是没有返回,查看控制台接口状态是cancel,没有其他报错。 最后发现前端axiostimeout是5秒,但是我写的这个node接口大概7秒。修改timeout就可以了。

总结

这次接触Puppeteer,安装使用的时候各种报错,好在github都有这种issue,能够找到解决方案。这次也是第一次接触node,用node写接口,遇到跨域问题,返回请求头的问题等等。