这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战
前言
上一篇介绍了 Puppeteer 的基础用法和常用API,其中有提到 Puppeteer 的一个使用场景是“生成PDF”。接下来,我们实战一下 PDF 生成,感受一下 Puppeteer 的魅力。
给单个 HTML 生成 PDF
先来个简单的,这里拿 Vue 文档为例,把文档的介绍页转存为 pdf 。注意!生成PDF只支持无界面的操作,headless 必须为 true。 否则会报错哦。
const puppeteer = require('puppeteer');
//启动 chromium
puppeteer.launch({
headless:true, //生成PDF只支持无界面的操作!(我刚开始调试设了 false ,一直报错哈哈哈)
//指定视口大小
defaultViewport:{
width:1920,
height:1080
}
}).then(async (browser)=>{
//打开一个标签页
const page = await browser.newPage();
//进入指定页面,默认会等待页面load事件触发
await page.goto('https://cn.vuejs.org/v2/guide/index.html');
//指定生成的pdf文件存放路径
await page.pdf({path: `./vueDoc-pdf/guide.pdf`});
//关闭页面
page.close()
//关闭 chromium
browser.close();
})
page.goto 支持配置 waitUntil,满足什么条件认为页面跳转完成,默认是 load 事件触发时。也可以配置 networkidle0, 表示不再有网络连接时触发(至少500毫秒后)。更多配置项详见 page.goto
给多个HTML生成PDF
上面的示例只生成一个页面,如果想把整个 Vue 教程拿下来呢?这里就用个简单粗暴的方法:依次打开各个页面,逐个生成。还是以 Vue 文档为例,实现如下:
const puppeteer = require('puppeteer');
const fs = require('fs');
(async ()=>{
//指定存放pdf的文件夹
const folder = 'vueDoc'
fs.mkdir(folder,()=>{ console.log('文件夹创建成功') })
//启动无头浏览器
const browser = await puppeteer.launch({headless:true }) //PDF 生成仅在无界面模式支持, 调试完记得设为 true
const page = await browser.newPage();
await page.goto('https://cn.vuejs.org/v2/guide/index.html'); //默认会等待页面load事件触发
// 1) 已知Vue文档左侧菜单结构为:.menu-root>li>a
// 获取所有一级链接
const urls = await page.evaluate(()=>{
return new Promise( resolve => {
const aNodes = $('.menu-root>li>a')
const urls = aNodes.map(n=>{
return aNodes[n].href
})
resolve(urls);
})
})
// 2)遍历 urls, 逐个访问并生成 pdf
for( let i = 0; i<urls.length; i++ ){
const url = urls[i],
tmp = url.split('/'),
fileName = tmp[tmp.length-1].split('.')[0]
await page.goto(url); //默认会等待页面load事件触发
await page.pdf({path: `./${folder}/${i}_${fileName}.pdf`}); //指定生成的pdf文件存放路径
console.log(`${i}_${fileName}.pdf 已生成`)
}
page.close()
browser.close();
})()
给多个HTML生成PDF_plus
如果你有去跑一下上面的代码,可能也会碰到跟我一样的问题:
没错我是故意看着你摔跤的,自己摔过印象才深刻嘛~
puppeteer 默认的请求超时时间为 30s,有时候可能会因为网络环境或者别的因素导致某个页面生成失败,进而阻塞其他页面的生成。所以我们还需要做一些容错处理,最好顺便统计一下错误数据,以便后续重试或者别的啥:
const puppeteer = require('puppeteer');
const fs = require('fs');
(async ()=>{
//指定存放pdf的文件夹
const folder = 'vueDoc'
fs.mkdir(folder,()=>{ console.log('文件夹创建成功') })
//启动无头浏览器
const browser = await puppeteer.launch({headless:true }) //PDF 生成仅在无界面模式支持, 调试完记得设为 true
const page = await browser.newPage();
await page.goto('https://cn.vuejs.org/v2/guide/index.html'); //默认会等待页面load事件触发
// 1) 已知Vue文档左侧菜单结构为:.menu-root>li>a
// 获取所有一级链接
const urls = await page.evaluate(()=>{
return new Promise( resolve => {
const aNodes = $('.menu-root>li>a')
const urls = aNodes.map(n=>{
return aNodes[n].href
})
resolve(urls);
})
})
// 2)遍历 urls, 逐个访问并生成 pdf
let successUrls = [], failUrls = [] // 用于统计成功、失败情况
for(let i = 17; i<urls.length; i++){
const url = urls[i],
tmp = url.split('/'),
fileName = tmp[tmp.length-1].split('.')[0]
try{
await page.goto(url); //默认会等待页面load事件触发
await page.pdf({path: `./${folder}/${i}_${fileName}.pdf`}); //指定生成的pdf文件存放路径
console.log(`${fileName}.pdf 已生成!`)
successUrls.push(url)
}catch{
//如果页面打开超时,会抛出错误。为了保证后面的页面生成不被影响,这里做一下容错处理。
failUrls.push(url)
console.log(`${fileName}.pdf 生成失败!`)
continue
}
}
console.log(`PDF生成完毕!成功${successUrls.length}个,失败${failUrls.length}个`)
console.log(`失败详情:${failUrls}`)
//TODO: 失败重试
page.close()
browser.close();
})()
最后
写得有点眼花了,后面有空再继续完善,做一下重试、生成的 PDF 合并啥的。 上个图看看效果吧。