如何从0到1搭建puppeteer服务端截图

4,438 阅读4分钟

简介

本文会手把手教你
  • 服务端配置puppeteer
  • 截图关键代码实现
  • 截图服务压测

依赖

  • puppeteer
  • nestjs
  • pm2

背景

🐟 近一年多都在开发优化公司的一个前端较重的项目,类似于一个网页版的ppt(如下图,这里拿keynote做一个展示,毕竟不能透露公司内部项目的啦~),功能很多,性能一直是项目里面难于突破的瓶颈😿,随着业务功能从0到1,功能已经满足于公司业务,组里前端从11名同学,就剩下4名同学(加上我)维护,产品也是走了一波又一波,领导换了一波又一波。言归正传,为了将左侧大纲作为图片展示,减少页面的dom数量,并且图片的用处很多,例如生成pdf,预览等等。所以一个截图服务就此诞生了~


前端与服务端截图优劣

前端利用 html2canvas 等框架也可以在前端页面进行HTML截图

优点:截图运行在客户端,不用担心服务器资源与并发等相关问题,对于纯前端小伙伴是一个很容易上手的项目。

缺点:第一个缺点是,html2canvas中(html2canvas原理是foreignObject与canvas)跨域以及一些不能序列化的元素无法生成正确的截图;第二个缺点是,js是单线程的,截图时会占用主线程,使得页面进入假死状态,无法进行UI操作。

服务端利用puppeteer调用chromium截图方法

优点:相对于前端实现,接口请求服务端为异步事件,不占用主线程资源

缺点:截图服务生成需要渲染时间,且时间因页面的复杂度而定,所以无法同步生成截图。异步生成截图带来的问题就是,客户端需要定时去轮询资源是否存在,(当然你可以用长连接生成截图)判断资源存在之后再去展示。并且要考虑服务器的并发与任务调度等问题

服务端准备工作

安装puppeteer-core

首先在puppeteer官网上查找想要使用的chromium版本github.com/puppeteer/p…

我是使用是"puppeteer": "3.3.0"对应chromium版本是83

如何下载puppeteer中对应的chromium

下载链接在puppeteer\src\BrowserFetcher.js中,如下图:


对应版本在package.json中,如下图:


完整链接

https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/756035/chrome-linux.zip

服务器配置chromium

下载chromium

wget https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/756035/chrome-linux.zip

检测缺失的依赖包

ldd chrome | grep not

如果缺失依赖包,如下图


则运行安装如下:

#依赖库
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
#字体
yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y


试运行chromium

const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({ headless: false,
        executablePath : '/home/homework/local-chrome/chrome-linux/chrome'
   });
  const page = await browser.newPage();
  await page.goto("https://google.com");
  await page.screenshot({ path: "example.png" });

  await browser.close();
})();


截图关键代码实现

流程介绍


预加载逻辑

为了节约browser openpage的时间,预先加载10个page实例;

let ready = false;
let domCaptureBrowser: CreateBrowserPage;
(async () => {
  const time = +new Date().getTime();
  const filePath = 'domcapture.html';
  domCaptureBrowser = new CreateBrowserPage(10);
  const init = await domCaptureBrowser.init();
  if (!init) {
    Log({data: '初始化domcapture preopen error', logType: 'error'});
    return;
  }
  const html = await getFile({serverWebroot: config.serverWebroot, filePath});
  const htmlFile = path.join(config.outputPath, `/domcapture.html`);
  try {
    fs.writeFileSync(htmlFile, html);
  } catch (error) {
    Log({data: `初始化domcapture preopen html write error`, logType: 'error'});
  }
  await (async () => {
    const len = domCaptureBrowser.pageList.length;
    for (let i = 0; i < len; i++) {
      try {
        await domCaptureBrowser.pageList[i].page.goto(`${config.serverProtocol}${htmlFile}`, {
          waitUntil: ['load', 'networkidle0'],
          timeout: 1000 * 3 * 60,
        });
      } catch (error) {
        Log({data: `初始化domcapture preopen page No.${i} error`, logType: 'error'});
      }
    }
  })();
  ready = true;
  Log({data: `初始化domcapture preopen success, newpage用时: ${(+ new Date().getTime() - time) / 1000}s`});
})();
export {
  ready,
  domCaptureBrowser,
};


截图服务压测

压测使用的是stress,公司测试大佬们搭建的一套服务。

建议压测一下服务,因为本地测试单点不会触发并发引起的问题,因为在压测的过程中还是发现了代码page调度的问题。


最后展示一下生成日志


总结

总体来讲截图服务开发及搭建还是很简单的一个过程,不过就是第一次搭建node服务走了很多弯路,比如:服务器没有连网.. 变量没有配,新申请的机器一些配置没有初始化,最后找了op解决,但是发现这些问题浪费了一些时间,以及pm2多进程造成taskId生成异常等...

关于pm2以及nestjs的使用会在其他文章中继续讲述,感谢阅读~