Puppeteer 是一个 Node.js 库, 提供了一组封装良好的接口, 使你可以通过 DevTools
协议控制 Chrome
. 本文介绍如何在 SCF
中使用 Puppeteer
.
一个截图的例子
我们使用官方仓库里的截图例子
代码语言:txt复制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 上运行
代码语言:txt复制// 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
第一次运行
在控制台上点击测试, 你可能会看到如下错误:
代码语言:txt复制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.
我们按照提示来修复这个错误, 添加启动参数:
代码语言:txt复制const browser = await puppeteer.launch({args: ['--no-sandbox']});
第二次运行
这一次, 你会遇到不一样的错误:
代码语言:txt复制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
作为一个有经验的程序员, 你知道这是运行环境里缺少了必要的动态链接库, 你也发现机器上没有这个动态链接库, 搜索发现, 可以这样解决
代码语言:txt复制$ 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
个动态链接库, 你可以选择把这些库都拷贝到当前目录, 这样就可以一劳永逸地解决依赖的问题.
你没有选择这样做, 因为这会使代码包变大许多, 你打包了代码, 再次运行.
第三次运行
问题不大, 你已经知道如何解决了
代码语言:txt复制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 次运行
你并没有崩溃 (:, 反复执行这个过程后, 你终于把缺失的动态库都补齐了
代码语言:txt复制$ 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
函数终于可以正常运行了
代码语言:txt复制{"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
完整的示例代码如下:
代码语言:txt复制// 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 网关
看看效果, 没有如你所愿, 截图上的文本没有被正确显示, 但是聪明的你一定想到了, 这是字体的问题. 这个问题就留给读者自行解决啦!