Puppeteer是什么?
Puppeteer 是一个控制 headless Chrome 的 Node.js API 。Puppeteer通过 DevTools 协议提供了一个高级的 API 来控制 headless Chrome,可以用来模拟 Chrome 浏览器的运行。它还可以配置为使用完整的(非 headless)Chrome。
headless Chrome: 在无界面的环境中运行 Chrome, 通过命令行或者程序语言操作 Chrome
....此次省略Puppeteer概念点(写完一个command+z直接全没了)建议查看官方文档 Puppeteer | Puppeteer 。网上的中文文档看看意思就好,大部分API都是旧的、废弃的。出现问题一定要优先查看官方英文文档
需求:输入博客地址,将博客正文导出为PDF
用到的技术栈: koa+puppeteer
1. 创建项目package.json
{
"name": "html-to-pdf",
"version": "1.0.0",
"scripts": {
"dev": "node main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"koa": "^2.13.4",
"koa-router": "^12.0.0",
"koa-static": "^5.0.0",
"puppeteer": "^17.1.3"
}
}
2.创建node入口文件
const Koa = require('koa')
const app = new Koa()
const path = require('path')
const Router = require('koa-router')
const router = new Router()
const cors = require('./utils/cors')
const static = require('koa-static')
const { handlePdf } = require('./utils/pdf')
app.use(cors)
// 配置静态资源
const staticPath = './public'
app.use(static(path.join( __dirname, staticPath)))
router.get('/', async (ctx) => {
ctx.body = '首页'
})
router.get('/pdf', async (ctx) => {
const { url, format = 'A4', top = '20px', right = '20px', bottom = '20px', left = '20px', scale = 1 } = ctx.query
const options = { format: format, scale: scale, margin: { top: top, right: right, bottom: bottom, left: left } }
const pdf = await handlePdf(url, options)
ctx.set('Content-Type', 'application/pdf')
ctx.body = pdf
})
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000, function() {
console.log('App listening at port 3000;');
});
3.使用Puppeteer导出PDF
async handlePDF function (url, options) {
console.log("启动puppeteer")
const brower = await puppeteer.launch({
args: ['--no-sandbox'],
headless: true
});
const page = await brower.newPage()
// 监听page.evaluate里的console.log
page.on('console', (log) => {
console.log('处理中...')
})
await page.goto(url, { waitUntil: 'networkidle0' })
await page.emulateMediaType('screen');
console.log("开始输出PDF")
const pdf = await page.pdf(options)
console.log("结束输出PDF")
await brower.close()
return pdf
}
在api请求时,将会携带博客url。api收到url后使用puppeteer进行处理
4. 样式优化
到这一步时,API已经具备了返回PDF stream的能力。接下来可以做进一步优化,此时导出的PDF中包含了blog两侧的内容。我们可以通过page.addStyleTag来对page添加CSS,将不想导出的内容屏蔽,
const getStyle = require('./style')
...
await page.addStyleTag({ content: getStyle() })
const pdf = await page.pdf(options)
...
5.前端下载
在API支持导出PDF后,前端如何取得文件流并完成下载呢?这里以axios举例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<input type="text" id="url" value="https://blog.csdn.net/ZK645968/article/details/126288400"
style="width: 320px;height: 30px;">
<button id="btn">
导出页面
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
document.getElementById('btn').addEventListener('click', function(){
const blogUrl = document.getElementById('url').value
const url = `http://localhost:3000/pdf?url=${blogUrl}`
axios.get(url, { responseType: 'arraybuffer' }).then(res => {
// 以时返回的是二进制流 通过创建一个标签实现下载文件
const newBlob = new Blob([res.data], { type: 'application/pdf' })
var a = document.createElement('a')
a.id = 'myload'
a.href = window.URL.createObjectURL(newBlob)
const [fileName] = blogUrl.split('/').reverse()
a.download = `${fileName}.pdf`
a.style.display = `none`
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
)
})
</script>
</body>
</html>
最终就可以导出CSDN博客的PDF了。使用puppeteer导出可以实现几乎等同于浏览器查看效果的PDF,从效果上看还是很满意的。缺点是导出的时间比较久,以下例子大概花了7s左右。算是尚可接受。
附上另一篇博客的导出效果: demo.pdf · master · ZK645945 / blog-pdf · GitCode
案例开源地址: https://gitcode.net/ZK645968/blog-pdf
如有帮助,还请点赞、收藏