如何使用Puppeteer和Node.js生成PDF页面的屏幕截图。

6
我正在使用Puppeteer和Node.js创建屏幕截图生成器。它对于普通网页运行良好,但对于PDF页面,每次运行都会出现相同的错误。
这是代码(来自https://github.com/GoogleChrome/puppeteer的第一个示例):
const puppeteer = require('puppeteer');

(async () => {
    try {
        const browser = await puppeteer.launch();
        const page = await browser.newPage();
        await page.goto('https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf');
        await page.screenshot({ path: 'example.png' });
        await browser.close();
    } catch (err) {
        console.log(err);
    }
})();

我得到的错误
Error: net::ERR_ABORTED at https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf
    at navigate (C:\MEAN\puppeteer-demo\node_modules\puppeteer\lib\FrameManager.js:121:37)
    at process._tickCallback (internal/process/next_tick.js:68:7)
  -- ASYNC --
    at Frame.<anonymous> (C:\MEAN\puppeteer-demo\node_modules\puppeteer\lib\helper.js:110:27)
    at Page.goto (C:\MEAN\puppeteer-demo\node_modules\puppeteer\lib\Page.js:629:49)
    at Page.<anonymous> (C:\MEAN\puppeteer-demo\node_modules\puppeteer\lib\helper.js:111:23)
    at C:\MEAN\puppeteer-demo\index.js:7:20
    at process._tickCallback (internal/process/next_tick.js:68:7)

非常感谢您的帮助。我也愿意接受任何其他可能的解决方案。


你无法从PDF中截取屏幕,因为Chromium没有创建任何目标。当Chromium加载PDF时,它加载的是一个PDF查看器,而开发者工具无法调试该查看器。 - hardkoded
4个回答

4

无头浏览器不能访问PDF页面,并会抛出错误Error: net::ERR_ABORTED,您也会遇到这个问题。虽然您可以使用headless: false访问PDF文档,但是截图也将失败,因为PDF不是一个真正的网站,实际上在单独的视图中呈现。

替代方法

相反,您可以下载页面并使用PDF.js创建页面的图像。您可能想查看有关“pdf转图像”或“pdf预览”的其他信息。关于该主题有多个stackoverflow问题(12,..),PDF.js页面本身也提供了示例


谢谢,我一直在寻找下载PDF的方法,但这可能会为我节省很多时间。 - M4hd1
您可能也可以仅使用PDF.js完成所有工作,以便在无头模式下仍然可以执行puppeteer操作。您可以在同一脚本中使用puppeteer和PDF.js。您可以在选择要使用的文件之前使用/ \ .pdf $ / .test(url)。我还没有完全了解PDF.js的所有下载和图像方面的能力,所以无法对此进行评论,但我已经能够将它们组合使用来完成我的工作。 - knod

2

对于现在遇到这个问题的任何人,我使用Puppeteer、EJS和PDF.js的组合来实现,因为Puppeteer本身无法查看PDF文件。

我的方法基本上是使用EJS动态添加一个URL,该URL将通过PDF.js查看,然后Puppeteer将对其进行截图。

以下是JS部分:

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

(async () => {
    const browser = await puppeteer.launch({ 
        args: [
            '--disable-web-security',
            '--disable-features=IsolateOrigins',
            '--disable-site-isolation-trials'
        ]
    });
    const page = await browser.newPage();

    const url = "https://example.com/test.pdf";

    const html = await ejs.renderFile('./template.ejs', { data: { url } });

    await page.setContent(html);
    await page.waitForNetworkIdle();
    const image = await page.screenshot({ encoding: 'base64' });

    await browser.close();

    console.log('Image: ', image);
})();

我在 Puppeteer 的启动参数中添加了 Chromium args,以允许无需 CORS 加载 PDF 文件,参考这个答案
以下是 EJS 模板。
<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">

    <style>
        body {
            width: 100vw;
            height: 100vh;
            margin: 0;
        }
        #page {
            display: flex;
            width: 100%;
            height: 100%;
        }
    </style>

    <title>Document</title>
</head>

<body>
    <canvas id="page"></canvas>
    <script src="https://unpkg.com/pdfjs-dist@2.0.489/build/pdf.min.js"></script>
    <script>
        (async () => {
            const pdf = await pdfjsLib.getDocument('<%= data.url %>');
            const page = await pdf.getPage(1);

            const viewport = page.getViewport(1);
        
            const canvas = document.getElementById('page');
            const context = canvas.getContext('2d');

            canvas.height = viewport.height;
            canvas.width = viewport.width;

            const renderContext = {
                canvasContext: context,
                viewport: viewport
            };

            page.render(renderContext);
        })();
    </script>
</body>

</html>

请注意,此代码仅会截取第一页的屏幕截图。

1
太棒了!这解决了我的问题。谢谢。 - Web Star
谢谢,解决方案完美运行。不过我的截图质量很低,你的怎么样? - Kalana Perera
@KalanaPerera 低质量可以通过增加视口的比例来解决 - undefined

2
如@kalana-perera所提到的,@aaditya-chakravarty的解决方案是低分辨率且拉伸的。对其进行了一些修改,以输出PDF的第一页完整、无畸变的图像。
使用最新版本的PDF.js和TypeScript。
async function generatePdfPreview(pdfUrl: string) {
  const browser = await puppeteer.launch({
    headless: "new",
    defaultViewport: null,
    args: [
      "--no-sandbox",
      "--disable-setuid-sandbox",
      "--disable-web-security",
      "--disable-features=IsolateOrigins",
      "--disable-site-isolation-trials",
    ],
  });
  const page = await browser.newPage();
  await page.setContent(
    previewCreatorPage(pdfUrl)
  );
  await page.waitForSelector("#renderingComplete");
  await page.waitForNetworkIdle();
  const pdfPage = await page.$("#page");
  const screenshot = pdfPage!.screenshot({
    type: "png",
    omitBackground: true,
  });

  return screenshot;
}

function previewCreatorPage(url: string) {
  return `<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">
  
      <style>
          body {
              width: 100vw;
              height: 100vh;
              margin: 0px;
          }
          #page {
              display: flex;
              width: 100%;
          }
      </style>
  
      <title>Document</title>
  </head>
  
  <body>
      <canvas id="page"></canvas>
      <script src="https://mozilla.github.io/pdf.js/build/pdf.js"></script>
      <script>
          var pdfjsLib = window['pdfjs-dist/build/pdf'];
          (async () => {
              const pdf = await pdfjsLib.getDocument('${url}').promise;
              const page = await pdf.getPage(1);
  
              const viewport = page.getViewport({ scale: 1 });
          
              const canvas = document.getElementById('page');
              const context = canvas.getContext('2d');
  
              canvas.height = viewport.height;
              canvas.width = viewport.width;
  
              const renderContext = {
                  canvasContext: context,
                  viewport: viewport
              };
  
            await page.render(renderContext).promise;

            const completeElement = document.createElement("span");
            completeElement.id = 'renderingComplete';
            document.body.append(completeElement);
          })();
      </script>
  </body>
  `;
}
  • defaultViewport: null将允许更大尺寸的图片,而不仅限于800x600。
  • 保留width: 100%并移除height: 100%
  • 使用最新的pdf.js(版本3?)
  • 仅截取页面中的画布(#page),而不是整个页面。

编辑:

  • 更新为@terraloader的解决方案以改善时序

1
我做了一个小改进:不再使用waitForNetworkIdle(),而是使用waitForSelector('#renderingComplete'),同时还扩展了预览页面,如下所示:await page.render(renderContext).promise; const completeElement = document.createElement("span"); completeElement.id = 'renderingComplete'; document.body.append(completeElement);这样可以触发一个通知给puppeteer,告知真正的pdf-js-rendering已完成。 - undefined
经过测试和更新,感谢 @terraloader。 - undefined
谢谢@SamSussman,我会尝试这个解决方案。 - undefined

0

Chromium不允许在无头真实模式下打开PDF文件,改用无头假模式。 await puppeteer.launch({args: ['--no-sandbox'], headless: false })


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接