该文章已经过期
最新的方案请参考: fc-puppeteer-demo
puppeteer.js github 地址:https://github.com/GoogleChrome/puppeteer
API: https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md
函数计算文档:https://help.aliyun.com/product/50980.html?spm=a2c4g.750001.2.6.uO2VV4
简介
本文通过实现网页截图功能案例来讲解在函数计算中如何使用 Puppeteer。另外,读者如果遇到同类问题,如:在函数计算中安装自己的共享库,同样可以参考本文章。
快速开始
如果您不想知道技术细节,而是想快速的将 Puppeteer 在函数计算中用起来,我这里提供了一个快速开始项目,基于这个项目,您可以马上写业务相关的代码,还提供快速打包和本地调试脚本(强烈推荐)。项目地址:https://github.com/awesome-fc/puppeteer-fc-starter-kit。接下来,我会介绍 Puppeteer 能在函数计算中使用的实现方法。
Puppeteer 简介
Puppeteer 是一个 node 库,他提供了一组用来操纵 Chrome 的 API, 通俗来说就是一个 headless chrome 浏览器 (当然你也可以配置成有UI的,默认是没有的)。既然是浏览器,那么我们手工可以在浏览器上做的事情 Puppeteer 都能胜任, 另外,Puppeteer 翻译成中文是”木偶”意思,所以听名字就知道,操纵起来很方便,你可以很方便的操纵他去实现:
- 生成网页截图或者 PDF
- 高级爬虫,可以爬取大量异步渲染内容的网页
- 模拟键盘输入、表单自动提交、登录网页等,实现 UI 自动化测试
- 捕获站点的时间线,以便追踪你的网站,帮助分析网站性能问题
在函数计算中使用遇到的问题
- 尺寸太大: Puppeteer 默认下载的 chrome headless 文件尺寸太大( 180左右 MB,压缩后 50 MB 以上),超过了函数计算的限制大小(限制:压缩后 50 MB ,解压后 250 MB)
- 依赖动态链接库: chrome headless 自身依赖了一些动态链接库,在函数计算运行环境中没有提供
- 目录只读: 函数计算运行环境只有 /tmp 目录有写的权限,也无法在运行时直接安装依赖包到系统目录
解决方案
将项目中依赖的 deb 包安装(或者解压)到指定目录,由于依赖的动态库文件只有 4 MB 多,所以可以安装到项目根目录下的 lib 目录中, chrome headless 文件默认尺寸会比较大,我们可以自己编译 chrome headless,自己编译的 chrome headless 文件大小为 109.6 MB,压缩后才 45.6 MB。项目代码、chrome headless 和相关动态链接库一起打包,压缩包的大小也没有超过 50 MB 的限制。如果依赖的动态链接库超过 50 MB,可以打包上传到 oss,通过 oss 下载安装到 /tmp 目录中。在本案例中,chrome headless 和 chrome headless 的相关第三方依赖库文件尺寸没有超过 50 MB。方案具体实施步骤如下所述。
实施步骤
创建项目
cd ~
mkdir screenshot
cd screenshot
npm init
注意: 项目用到了一些比较新的语法 await 和 async,所以需要函数计算运行环境选择 nodejs8
进入沙箱环境
cd ~/screenshot
fcli shell #进入 fcli 交互模式
sbox -d . -t nodejs8 #将当前目录(~/screenshot)挂载到沙盒环境的 /code 位置
注:fcli 为您提供了一个本地的沙盒环境,和函数计算服务中的函数运行环境保持一致。在沙盒环境中,您可以方便的安装第三方库,进行本地调试等操作。
安装项目依赖
package.json 如下:
{
...
"dependencies": {
"puppeteer": "^1.4.0",
"tar": "^4.4.4"
}
...
}
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true #设置跳过下载 chrome headless 的环境变量
npm install
#或者直接 npm install --ignore-scripts
安装 chrome headless 依赖的动态链接库
##在沙箱环境中
cd /code
mkdir debs
#下面的命令参数 -d 是让 apt-get 只下载安装包,不进行安装
apt-get install -d -o=dir::cache=/code/debs libnspr4 libnss3
#创建 lib 目录,用于安装 chrome headless 依赖的动态链接库
mkdir lib
#安装上面下载的库文件到指定目录
for file in $(ls /code/debs/archives)
do
dpkg -x /code/debs/archives/$file /code/lib/
done
rm -rf debs
说明:动态链接库的关联需要设置环境变量 LD_LIBRARY_PATH,在下面 nodejs 代码中设置了,具体请看编写函数
编译 chrome headless
我已经编译过 chrome headless,下载地址:https://github.com/muxiangqiu/puppeteer-fc-starter-kit/blob/master/chrome/headless_shell.tar.gz?raw=true,如果需要自己编译,可以参考我写好的脚本,或者直接运行我的脚本。
编写函数
基于 pupeteer.js 实现对网页截图,并将图片返回,代码如下:
//index.js
const puppeteer = require('puppeteer');
const tar = require('tar');
const fs = require('fs');
const path = require('path');
//判断是否存在 /tmp/headless 目录
const existsExecutableChrome = () => {
return new Promise((resolve, reject) => {
fs.exists('/tmp/headless_shell', exists => {
resolve(exists);
});
});
};
//从 oss 下载安装 chrome headless 和第三方依赖库文件,如果安装过了,则跳过
const setupChromeIfNecessary = async (context, callback) => {
if (await existsExecutableChrome()) {
return;
}
return new Promise((resolve, reject) => {
//解压到 /tmp 目录中
fs.createReadStream(config.localChromePath)
.on('error', (err) => reject(err))
.pipe(tar.x({
C: 'tmp',
}))
.on('error', (err) => reject(err))
.on('end', () => resolve());
});
};
module.exports.handler = async (event, context, callback) => {
await setupChromeIfNecessary(context, callback);
const ldLibraryPath = `${process.env['FC_FUNC_CODE_PATH']}/lib/usr/lib/x86_64-linux-gnu/`;
const browser = await puppeteer.launch({
headless: true,
//指定 chrome headless 的执行路径
executablePath: '/tmp/headless_shell',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
env: {
//chrome headless 的第三方依赖库安装到项目的 lib 目录下面,
//所以需要设置 LD_LIBRARY_PATH 环境变量,告诉系统如何加载这部分的动态库
LD_LIBRARY_PATH: `${process.env['LD_LIBRARY_PATH']}:${ldLibraryPath}`
}
});
const page = await browser.newPage();
await page.goto('https://fc.console.aliyun.com');
try {
const screenshot = await page.screenshot({
clip: {
x: 200,
y: 60,
width: 780,
height: 450
}
});
await page.close();
callback(null, screenshot))
} catch (err) {
callback(err);
}
};
总结
通过上面的步骤,项目代码压缩后大小为 47 MB 左右 ,包含 chrome headless 和相关动态链接库。冷启动运行时间: 2700 ms 左右,热启动运行时间:300 ms 左右,内存使用:350 MB 左右(注意:在创建函数的时候,内存最好设置为 512 MB)。
本解决方案适用于很多类似的场景,适用规则如下:
- 需要给函数计算运行环境安装动态链接库
- 项目尺寸超过了函数计算的限制大小
查阅参考
感谢
感谢如下同学在本案列制作和文档撰写过程中给予的建议和帮助
@倚贤 @不瞋 @夏莞 @敬畏
联系方式
电子邮件: subo.ysb@alibaba-inc.com