Node.js基于Puppeteer生成PDF与部署问题解决_Puppeteer捕获HTTP请求
2024-11-21 22:49:38
详解Node.js使用Puppeteer生成PDF的全过程,完整的Puppeteer部署问题解决方案,利用Puppeteer捕获HTTP请求,如何在Ubuntu上解决Puppeteer字体缺失的问题
599

最近公司需要在服务端生成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