阿里云 Serverless Computing 关注
手机版

函数计算中使用 puppeteer 的过程分析

  1. 云栖社区>
  2. 阿里云 Serverless Computing>
  3. 博客>
  4. 正文

函数计算中使用 puppeteer 的过程分析

木香丘 2018-06-12 20:54:53 浏览3630 评论0

摘要: 本文通过实现网页截图功能案例来讲解在函数计算中如何使用 Puppeteer。另外,读者如果遇到同类问题,如:在函数计算中安装自己的共享库,同样可以参考本文章。

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 为您提供了一个本地的沙盒环境,和函数计算服务中的函数运行环境保持一致。在沙盒环境中,您可以方便的安装第三方库,进行本地调试等操作。

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
r​m -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');
    await page.screenshot({
        clip: {
            x: 200,
            y: 60,
            width: 780,
            height: 450
        }
    }).then(res => {
        callback(null, res))
    }).catch(err => {
        callback(err);
    });
};

总结

通过上面的步骤,项目代码压缩后大小为 47 MB 左右 ,包含 chrome headless 和相关动态链接库。冷启动运行时间: 2700 ms 左右,热启动运行时间:300 ms 左右,内存使用:350 MB 左右(注意:在创建函数的时候,内存最好设置为 512 MB)。

本解决方案适用于很多类似的场景,适用规则如下:

  • 需要给函数计算运行环境安装动态链接库
  • 项目尺寸超过了函数计算的限制大小

查阅参考

感谢

感谢如下同学在本案列制作和文档撰写过程中给予的建议和帮助

@倚贤 @不瞋 @夏莞 @敬畏

联系方式

电子邮件: subo.ysb@alibaba-inc.com

用云栖社区APP,舒服~

【云栖快讯】云栖社区技术交流群汇总,阿里巴巴技术专家及云栖社区专家等你加入互动,老铁,了解一下?  详情请点击

网友评论