LambdaとTypeScriptで作るお手軽Puppeteer実行環境

Serverlessを添えて

スプレイピングを定期実行したい機会があったので、Serverlessを使ってLambdaにTypeScript/Puppeteerをデプロイしてみました。

地味に日本語資料が少なかったので残しておきます

LambdaでPuppeteerを実行するためには

google検索だと@serverless-chrome/lambdaが上位に出てくるのですが、1年以上更新がなく私の環境ではうまく動作せず諦めました(Nodeのバージョンが合わなかったなどあるかもしれません)。

対して、Sparticuz/chrome-aws-lambdaが提供するchromium/puppeteerを利用する方法が分かりやすくおすすめです。

exports.handler = async (event: any, context: any, callback: any) => {

  const browser = await chromium.puppeteer.launch({
    args: chromium.args,
    defaultViewport: chromium.defaultViewport,
    executablePath: await chromium.executablePath,
    headless: chromium.headless,
    ignoreHTTPSErrors: true,
  });
  const page = await browser.newPage();

  // ...

この状態でビルドすると、ファイルサイズが50~60MB程度になりLambdaへのアップロード制限も楽々回避できますね

...

Deploying scrapper-lambda to stage dev (ap-northeast-1)

✔ Service deployed to stack scrapper-lambda-dev (178s)

functions:
  hello: scrapper-lambda-dev-hello (57 MB)

...

ですが、このファイルサイズでアップロードすると結構時間かかります。僕の環境では3分ほどかかりました。

この容量のうちほとんどをSparticuz/chrome-aws-lambdaが提供するchromium/lambdaが占めています。そこで、Lambdaでのレイヤー機能を使うことで毎回これらをアップロードしなくて済むようにします

こちらのリポジトリ(shelfio/chrome-aws-lambda-layer)では、パブリックに上記のライブラリがレイヤーとして提供されています。 レイヤーのARNは2022/09/08時点で東京リージョンだったら arn:aws:lambda:ap-northeast-1:764866452798:layer:chrome-aws-lambda:31ですね。

Serverlessの場合はfunctions.layers[]にふくむだけで簡単に使えるので楽ですね。

functions:
  hello:
    handler: handler.handle
    memorySize: 3008
    timeout: 60
    events:
      - schedule: rate(1 hour)
    layers:
      - arn:aws:lambda:ap-northeast-1:764866452798:layer:chrome-aws-lambda:31 # here

こうすると、アップロードサイズが7MBほどになりそれに伴いアップロードする時間も1分ほどになりました。

...

✔ Service deployed to stack scrapper-lambda-dev (62s)

functions:
  hello: scrapper-lambda-dev-hello (6.9 MB)

...

typescript

serverlessのテンプレートはJavascriptになっていたんですが、どうしてもTypeScriptがよくて書き換えました。compilerOptionsにはここら辺を設定すれば再現できると思います

  • “module”: “commonjs”
  • “esModuleInterop”: true
  • “lib”: [“DOM”]
    • Puppeteerでevaluate()する際にwindowにアクセスできないため必要です

その他

普通にデプロイして実行しても、レスポンスが帰ってこず必ずタイムアウトしてしまいます。これはPuppeteerがイベントループをブロッキングしていて、Lambdaはデフォルトではイベントループが空っぽになってからレスポンスするためです。(Puppeteerを起動させるとChrome Debugger Protocolが動作するためなのかも?)

なので明示的にこれをoffにする必要があります

exports.handler = async (event: any, context: any, callback: any) => {
  context.callbackWaitsForEmptyEventLoop = false
  // ...
  
}

コード

https://gitlab.com/morifuji/puppeteer-in-lambda


See also