在 SCF 中运行 Puppeteer

Puppeteer 是一个 Node.js 库, 提供了一组封装良好的接口, 使你可以通过 DevTools 协议控制 Chrome. 本文介绍如何在 SCF 中使用 Puppeteer.

一个截图的例子

我们使用官方仓库里的截图例子

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({path: 'example.png'});

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

将其改造一下, 使其可以在 SCF 上运行

// index.js
'use strict';

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

exports.main_handler = async (event, context, callback) => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('https://example.com');
    await page.screenshot({path: '/tmp/example.png'});
    await browser.close();

    let img = fs.readFileSync('/tmp/example.png');
    let data = {
        isBase64Encoded: true,
        statusCode: 200,
        headers: {'content-type': 'image/png'},
        body: img.toString('base64'),
    };
    return data;
};

为了可以看到截图的效果, 我们可以添加一个 API 网关触发器, 并将图片以 Base64 编码的格式返回.

至此, 我们期望这个函数可以在 SCF 上正确运行.

运行函数

在本地创建一个新项目, 把依赖装完后, 将代码打包上传至 COS, 创建一个新的 SCF 函数, 引用这个 COS 文件(由于打包生成的代码超过 50 MB, 你需要使用这种方式上传代码)

$ npm init
$ npm install puppeteer
$ tree -L 1 .
.
|-- index.js
|-- node_modules
|-- package.json
`-- package-lock.json

第一次运行

在控制台上点击测试, 你可能会看到如下错误:

Failed to launch chrome!
[0405/090101.405444:FATAL:zygote_host_impl_linux.cc(116)] No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux_suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live dangerously and need an immediate workaround, you can try using --no-sandbox.

我们按照提示来修复这个错误, 添加启动参数:

const browser = await puppeteer.launch({args: ['--no-sandbox']});

第二次运行

这一次, 你会遇到不一样的错误:

Error: Failed to launch chrome!
/var/user/node_modules/puppeteer/.local-chromium/linux-641577/chrome-linux/chrome: error while loading shared libraries: libXss.so.1: cannot open shared object file: No such file or directory

作为一个有经验的程序员, 你知道这是运行环境里缺少了必要的动态链接库, 你也发现机器上没有这个动态链接库, 搜索发现, 可以这样解决

$ yum install libXScrnSaver

安装完后, 你把 libXss.so.1/lib64 目录拷贝到项目到目录里, 并在代码中将项目的目录追加到 LD_LIBRARY_PATH 环境变量中.

// index.js
'use strict';

process.env['LD_LIBRARY_PATH'] += ';' + __dirname;

操作完后, 你想看看 Chrome 还依赖哪些动态链接库, 于是你执行了以下命令:

$ ldd node_modules/puppeteer/.local-chromium/linux-641577/chrome-linux/chrome

你会发现, Chrome 依赖多达 107 个动态链接库, 你可以选择把这些库都拷贝到当前目录, 这样就可以一劳永逸地解决依赖的问题.

你没有选择这样做, 因为这会使代码包变大许多, 你打包了代码, 再次运行.

第三次运行

问题不大, 你已经知道如何解决了

Error: Failed to launch chrome!
/var/user/node_modules/puppeteer/.local-chromium/linux-641577/chrome-linux/chrome: error while loading shared libraries: libatk-bridge-2.0.so.0: cannot open shared object file: No such file or directory

拷贝缺失的库到当前目录, 再次打包上传

第 X 次运行

你并没有崩溃 (:, 反复执行这个过程后, 你终于把缺失的动态库都补齐了

$ ls lib*
libatk-1.0.so.0         libatspi.so.0          libepoxy.so.0  libgtk-3.so.0           libwayland-egl.so.1  libXss.so.1
libatk-bridge-2.0.so.0  libcairo-gobject.so.2  libgdk-3.so.0  libwayland-cursor.so.0  libxkbcommon.so.0

函数终于可以正常运行了

{"isBase64Encoded":true,"statusCode":200,"headers":{"content-type":"image/png"},"body":"iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAAAXNSR0IArs4c6QAAIABJREFUeJzs3Xd8Tff/B/DXzZCIaOy9Y48vpdSmVFXtEETs0dqKIChVlEao1pbYKyFmrVBao5TG1qZ2iBBChiA7ef/+8HB+rqx7k3vPjdvX8/E4j0dy7uee8z7n8/mce95naqKiogVEREREREQqsDB1AERERERE9N/BBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBISIiIiIiFTDBI..."}

总结

本文介绍了如何解决在 SCF 中运行 Puppeteer 缺少动态链接库的问题. 缺失的库包括:

libatk-1.0.so.0         libatspi.so.0          libepoxy.so.0  libgtk-3.so.0           libwayland-egl.so.1  libXss.so.1
libatk-bridge-2.0.so.0  libcairo-gobject.so.2  libgdk-3.so.0  libwayland-cursor.so.0  libxkbcommon.so.0

完整的示例代码如下:

// index.js
'use strict';

process.env['LD_LIBRARY_PATH'] += ';' + __dirname;

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

exports.main_handler = async (event, context, callback) => {
    const browser = await puppeteer.launch({args: ['--no-sandbox']});
    const page = await browser.newPage();
    await page.goto('https://example.com');
    await page.screenshot({path: '/tmp/example.png'});
    await browser.close();

    let img = fs.readFileSync('/tmp/example.png');
    let data = {
        isBase64Encoded: true,
        statusCode: 200,
        headers: {'content-type': 'image/png'},
        body: img.toString('base64'),
    };
    return data;
};

你想通过 API 网关 看看效果, 没有如你所愿, 截图上的文本没有被正确显示, 但是聪明的你一定想到了, 这是字体的问题. 这个问题就留给读者自行解决啦!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

Serverless+

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券