最近公司需要在服务端生成pdf发票,现有方法感觉使用puppeteer是最优的解决方案
一、什么是Puppeteer?
Puppeteer 可以用于自动化网页操作,例如:
- 网页截图
- 填写表单
- 模拟用户交互(点击、输入等)
- 爬取网页数据
二、安装 Puppeteer
在使用 Puppeteer 之前,需要先安装 Node.js。然后,可以使用以下命令使用 npm
来安装 Puppeteer:
npm install puppeteer
三、基本用法
下面是一个简单的示例,它打开了一个 Chrome 浏览器窗口,并导航到了百度的首页。
const puppeteer = require('puppeteer'); // 启动 Chrome 浏览器 const browser = await puppeteer.launch(); // 打开新页面 const page = await browser.newPage(); // 导航到百度首页 await page.goto('https://www.baidu.com'); // 关闭浏览器 await browser.close();
在这个示例中,首先使用 puppeteer.launch()
方法启动了一个 Chrome 浏览器实例。然后,使用 browser.newPage()
方法创建了一个新的页面。最后,使用 page.goto()
方法导航到了百度的首页。
四、截图
Puppeteer 可以轻松地对网页进行截图。下面是一个示例,它将截取百度首页的截图并保存到文件中。
const puppeteer = require('puppeteer'); // 启动 Chrome 浏览器 const browser = await puppeteer.launch(); // 打开新页面 const page = await browser.newPage(); // 导航到百度首页 await page.goto('https://www.baidu.com'); // 截取网页截图并保存到文件 await page.screenshot({path: 'example.png'}); // 关闭浏览器 await browser.close();
在这个示例中,使用 page.screenshot()
方法截取了当前页面的截图,并将其保存到名为 example.png
的文件中。
五、填写表单
Puppeteer 可以模拟用户填写表单的操作。下面是一个示例,它将在百度搜索框中填入关键词并点击搜索按钮。
const puppeteer = require('puppeteer'); // 启动 Chrome 浏览器 const browser = await puppeteer.launch(); // 打开新页面 const page = await browser.newPage(); // 导航到百度首页 await page.goto('https://www.baidu.com'); // 找到搜索框元素 const searchBox = await page.$('input#kw'); // 填入关键词 await searchBox.type('Puppeteer'); // 找到搜索按钮元素 const searchButton = await page.$('button.search'); // 点击搜索按钮 await searchButton.click(); // 等待搜索结果加载完成 await page.waitForNavigation({waitUntil: 'networkidle2'}); // 关闭浏览器 await browser.close();
在这个示例中,使用了 page.$()
方法来找到页面上的元素。$()
方法接受一个 CSS 选择器作为参数,并返回找到的第一个元素。然后,使用 element.type()
方法来填入表单元素的值,使用 element.click()
方法来点击按钮。
六、爬取数据
Puppeteer 还可以用于爬取网页数据。下面是一个简单的示例,它将爬取百度搜索结果的标题和链接。
const puppeteer = require('puppeteer'); // 启动 Chrome 浏览器 const browser = await puppeteer.launch(); // 打开新页面 const page = await browser.newPage(); // 导航到百度首页 await page.goto('https://www.baidu.com'); // 填入关键词并点击搜索按钮 await page.type('Puppeteer'); await page.click('button.search'); // 等待搜索结果加载完成 await page.waitForNavigation({waitUntil: 'networkidle2'}); // 找到搜索结果元素 const results = await page.$('.result'); // 遍历搜索结果元素 for (const result of results) { // 找到标题和链接元素 const title = await result.$('.t'); const link = await result.$('a'); // 获取标题和链接的文本内容 const titleText = await title.text(); const linkText = await link.text(); } // 关闭浏览器 await browser.close();
在这个示例中,使用了 page.$('.result')
来找到所有的搜索结果元素。然后,使用 result.$('.t')
和 result.$('a')
来找到每个搜索结果的标题和链接元素。最后,使用 element.text()
方法来获取元素的文本内容。
七、如何在服务器上部署Puppeteer
- 找不到浏览器
Error: Could not find Chromium (rev. 1056772). This can occur if either1. you did not perform an installation before running the script (e.g. `npm install`) or2. your cache path is incorrectly configured (which is: /home/www/.cache/puppeteer).For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.at ChromeLauncher.resolveExecutablePath (/blog-server/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ProductLauncher.js:120:27)at ChromeLauncher.executablePath (/blog-server/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:166:25)at ChromeLauncher.launch (/blog-server/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:70:37)at async TasksService.setScreenShot (/blog-server/dist/schedule/tasks.service.js:37:29)
- 防止puppeteer 在某些构建步骤中打包并移动到新位置,导致了不能部署。必须在构建好的项目中新增名为 .puppeteerrc.cjs 的文件,并将其放在根目录, .puppeteerrc.cjs的配置如下
const { join } = require('path'); module.exports = { cacheDirectory: join(__dirname, '.cache', 'puppeteer'), };
- 服务器缺少依赖
Error: Failed to launch the browser process!/blog-server/.cache/puppeteer/chrome/linux-1056772/chrome-linux/chrome: error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directoryTROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.mdat onClose (/blog-server/node_modules/puppeteer-core/lib/cjs/puppeteer/node/BrowserRunner.js:299:20)at Interface.<anonymous> (/www/wwwroot/blog-server/node_modules/puppeteer-core/lib/cjs/puppeteer/node/BrowserRunner.js:287:24)at Interface.emit (node:events:406:35)at Interface.close (node:readline:586:8)at Socket.onend (node:readline:277:10)at Socket.emit (node:events:406:35)at endReadableNT (node:internal/streams/readable:1343:12)at processTicksAndRejections (node:internal/process/task_queues:83:21)
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 -y // 使用上面的命令安装完依赖包后 使用nss进行更新 yum update nss -y
Ubuntu:
第一步:
sudo apt-get install gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
第二步:
sudo apt-get install -y libgbm-dev
- 不在沙箱内
Error: Failed to launch the browser process!
需要在打开浏览器的时候完全信任浏览器内容关闭沙箱
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'], });
- 中文乱码
CentOS:
//安装fontconfig来管理字体库 yum -y install fontconfig // 创建字体目录: mkdir /usr/share/fonts/chinese // 修改字体文件的权限,使root用户以外的用户也可以使用: chmod -R 755 /usr/share/fonts/chinese //在 windows 的 C:WindowsFonts目录下找到需要的字体, copy 到 /usr/share/fonts/chinese目录下 //建立字体索引信息,更新字体缓存 cd /usr/share/fonts/chinesemkfontscale //如果提示 mkfontscale: command not found,需自行安装 # yum install mkfontscale mkfontdir fc-cache -fv //如果提示 fc-cache: command not found,则需要安装# yum install fontconfig
Ubuntu:
//使用以下命令更新系统软件包列表: sudo apt update //安装中文字体支持包: sudo apt install fonts-noto-cjk //这将安装Noto字体系列,其中包含了广泛的中文字体。 //安装中文字体配置包: sudo apt install fontconfig //配置中文字体: sudo dpkg-reconfigure fontconfig-config //在配置过程中,选择"自动"选项以使用系统默认设置。 //更新字体缓存: sudo fc-cache -f -v //这将更新系统中的字体缓存。 //2024年7月10日更新: //如果要安装自定义的字体需要以下步骤 //上传到指定目录 /usr/local/share/fonts //更新字体缓存,代码如上 //使用fc-list 查看字体列表 可以得到 "/usr/local/share/fonts/NOTOSANS-REGULAR.TTF: Noto Sans:style=Regular" 的数据 //在对应文件中配置 "Noto Sans"
八、如何拦截http请求
// 使用puppeteer库启动一个浏览器实例,传入参数禁用沙箱模式,并以非无头模式启动 const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'], headless: 'true' // 以无头模式运行 }); // 在打开的浏览器中新建一个页面 const page = await browser.newPage(); // 初始化一个变量waitForResponse用于存储期望获取到的响应内容 let waitForResponse = null; // 创建一个新的Promise来监听并处理页面的response事件 new Promise((resolve, reject) => { // 注册一个回调函数,当接收到页面的HTTP响应时触发 page.on('response', async function(response) { try { // 检查响应是否来自'/api-docs'接口且状态码为200 if (response.request().url().endsWith('/api-docs') && response.status() === 200) { // 如果条件满足,获取并存储响应的文本内容 waitForResponse = await response.text(); // 解析获取到的内容后,调用resolve方法解决Promise resolve(waitForResponse); } } catch (error) { // 若在处理过程中发生错误,则调用reject方法拒绝Promise并传入错误信息 reject(error); } }); }); // 关闭已打开的浏览器实例 await browser.close(); // 解析之前获取到的API响应内容,并将其转换为JSON对象 let { paths, components } = JSON.parse(waitForResponse); // 提取paths和components属性供后续代码使用
九、如何生成pdf
// 使用puppeteer库启动一个浏览器实例,传入参数禁用沙箱模式,并以非无头模式启动(注:headless: 'new' 应该是设置为布尔值true或false,而不是字符串'new') const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'], headless: true // 根据上下文推测这里可能应该设置为headless: true 或 headless: false }); // 在打开的浏览器中新建一个页面 const page = await browser.newPage(); // 让页面导航到指定URL,并等待网络完全空闲(即没有网络活动)后才继续执行后续操作 await page.goto("http://127.0.0.1:7021/template/pdf?token="+token, { waitUntil: 'networkidle0' }); // 将当前页面内容渲染为PDF格式,设定宽度为800像素 const pdf = await page.pdf({ width: 800 }); // 关闭已打开的浏览器实例 await browser.close(); // 返回生成的PDF数据 this.ctx.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdf.length + '','Content-Disposition':`inline; filename="xxx.pdf"; filename*=UTF-8''xxx.pdf` }); this.ctx.body = pdf;
参考文章:https://juejin.cn/post/7178734705703911480 与 https://www.cnblogs.com/yesyes/p/15382614.html