使用Puppeteer将博客导出为PDF

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

如有帮助,还请点赞、收藏

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值