首页 > 解决方案 > 使用 Express 作为 HTML 服务器的 NodeJS --> PDF 生成。能有效率吗?

问题描述

我知道 NodeJS 的事件循环和单线程特性。鉴于此,您认为继续开发 NodeJS/Express 服务,我们可以使用它来将 HTML 部分转换为 PDF 页面是一个好主意吗?

我们正在考虑 Puppeteer。我已经使用过它并且效果很好,但我不确定组织中的每个用户是否都必须等待事件循环,因为每个请求都会让进程一直忙到最后?

标签: node.jsmultithreadingpuppeteer

解决方案


事件循环

事件循环负责处理 JavaScript 的“单线程事件驱动”性质,这意味着需要执行的异步 (JavaScript) 代码将被放入队列中并一个接一个地执行(通过循环)使用更经典的多线程方法。有关此主题的更多信息,我推荐这个很棒的视频解释

事件循环与您的问题并不真正相关,因为大部分工作在浏览器内异步发生(而不是在 Node.js 运行时内)。这意味着您的 puppeteer 脚本将大部分时间等待浏览器返回结果。

考虑这样一个简单的行:

await browser.newPage();

这实际上是做什么的?它将命令发送到浏览器(在另一个进程中运行)以打开页面。实际工作发生在浏览器内部,而不是在您的 Node.js 环境中。基本上所有的 puppeteer 功能也是如此。因此,“主要工作”不会发生在您的 Node.js 环境中,因此事件循环与您的问题无关。

实施

您所描述的对于 puppeteer 和 Node.js 是绝对可行的。让我们考虑一下这个示例代码,它应该可以帮助您入门:

const puppeteer = require('puppeteer');
const express = require('express');

const app = express();

app.get('/pdf', async (req, res) => { // Call /pdf?url=... to create a PDF of the provided URL
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto(req.query.url); // URL is given by the user
    const pdfBuffer = await page.pdf();

    // Respond with the PDF
    res.writeHead(200, {
        'Content-Type': 'application/pdf',
        'Content-Length': pdfBuffer.length
    });
    res.end(pdfBuffer);

    await browser.close();
});

app.listen(4000);

这将提供一个 API 来生成 URL 的 PDF。每个请求都会打开一个浏览器,打开一个新页面,导航到给定的 URL 并将 PDF 返回给用户。由于 JavaScript 的异步环境,这将完全并行发生。只要您的机器可以处理并行打开的浏览器的数量,就可以了。

进一步改进

虽然给定的脚本有效,但您应该记住,由于打开的浏览器过多,过多的请求可能会很快消耗过多的内存/CPU,从而导致资源问题。为了改进实施,您希望使用 puppeteer 资源池来处理流量。为此,您可能需要查看puppeteer-cluster(免责声明:我是作者),它为您提供浏览器实例池,并允许限制正在运行的浏览器的数量。该库可以轻松处理此用例。对于这个确切的用例,实际上有一个在线示例(但是,它会生成屏幕截图而不是 PDF)。


推荐阅读