Cosnomi
Cosnomi

医療×IT / 医学生 / Web(React, Flask) / 機械学習(画像認識, Keras)

Twitter / GitHub / Keybase

AWS LambdaでPuppeteerを用いたスクレイピングを行う

公式にAPIが提供されているサービスから定期的に情報を取得し、何かアクションを起こしたい場合は、AWS Lambdaを始めとする Function as a Service (FaaS) を用いると楽です。しかし、APIが提供されていない場合は、スクレイピングを行う必要があります。この記事では、AWS Lambda上のnode環境からPuppeteerというパッケージを用いてスクレイピングを行う方法を解説します。

Puppeteerとは

Chromeを操作できるnodeライブラリです。私達がブラウザで行うような操作は大抵Puppeteerでも行えます。(リンクのクリック、ログイン、SPA上での遷移、スクリーンショット撮影など)

https://github.com/GoogleChrome/Puppeteer

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

扱いが簡単だし開発体制も盤石そうなので、nodeでスクレイピングをするなら第一候補になるでしょう。

LambdaでPuppeteerを使うときの問題点

Puppeteerで使われるChromiumはPuppeteerによって自動でダウンロードされます。したがって、通常の環境でPuppeteerを用いたスクレイピングを行うにはPuppeteer以外に何かをインストールする必要はありません。デフォルトのChromiumを用いるのが最も簡単で安全です。

しかし、Lambda上でPuppeteerを動かす場合、デフォルトのChromiumを利用することはできません。なぜなら、PuppeteerにChromiumをダウンロードさせるとnode_modulesのサイズが非常に大きくなり、Lambdaの容量制限を超過してしまうからです。具体的には、Lambdaのデプロイパッケージサイズの制限は、圧縮済みで50MBですが、PuppeteerにChromiumをダウンロードさせてしまうと、これを超えてしまうということです。

そこで、PuppeteerがダウンロードするChromiumの代わりに、それよりも軽量なserverless-chromeを使います。今回は、Lambdaで利用するので、特にその中にある@serverless-chrome/lambdaを用います。

デフォルトのChromiumダウンロードを無効にする

前述の通り、PuppeteerはデフォルトでChromiumをダウンロードしようとしてしまいますが、これはLambdaで動かす上では都合が悪いので、代わりにより軽量な@serverless-chrome/lambdaを用いたのでした。

しかし、このままではnode_modulesにPuppeteer由来のChromiumと@serverless-chrome/lambda由来のChromiumの両方が存在してしまうので、PuppeteerのChromium自動ダウンロードを無効化する必要があります。

そのためには、puppeteer_skip_chromium_download環境変数をtrueにします。一般的には、repositoryのpackage.jsonと同じ階層に.npmrcファイルを作成し、

puppeteer_skip_chromium_download=true

と書き込みます。

これで、Puppeteerをnpm installするときに容量の大きいChromiumがダウンロードされることはなくなりました。それでは、実際に必要なパッケージを入れていきましょう。

必要なパッケージのインストール

LambdaでPuppeteerを動かすのに必要なパッケージは、

  1. puppeteer
  2. @serverless-chrome/lambda : LambdaでHeadless Chromeを動かす
  3. chrome-remote-interface : WebSocket URLを取得する (単に操作を楽にする程度のもの。本質的ではない。)

です。これらは、バージョンについてsensitiveなのでそれぞれの互換性を調べてpackage.jsonに書いていく必要がありますが、面倒だと思うので、執筆時点で私の趣味プロジェクトで動いているバージョン構成を紹介します。

"puppeteer": "1.18.1",
"@serverless-chrome/lambda": "1.0.0-55",
"chrome-remote-interface": "0.27.2",

個人的なプロジェクトならこれで良いと思いますが、仕事などで使う場合は各自調べてください。

実際に使ってみる

pageさえ取得できれば、Lambda上ではない普通のPuppeteerの使い方と同じなので、pageの取得までをTypeScriptで書いてみました。

import puppeteer from "puppeteer";
// 以下2つは型定義がなかった
const launchChrome = require("@serverless-chrome/lambda");
const CDP = require("chrome-remote-interface");

// 軽量版Chromiumを起動
const slsChrome = await launchChrome();

// Puppeteerに軽量版Chroniumを使わせる設定
const browser = await puppeteer.connect({
    browserWSEndpoint: (await CDP.Version()).webSocketDebuggerUrl
});
const page = await browser.newPage();
await page.goto("http://example.com")
// あとはお好きなように…

前述の通り、これ以降は普通にPuppeteerを使う場合と同じで、既に他のサイトで詳しく解説されているので本記事ではこれ以上踏み入らないことにします。

ローカルでデバッグをする

ローカル (=非Lambda)環境でデバッグをするときはchrome-launcherをnpm installする必要がありますが、これについては、特にハマることなく、また既存のコードに対する変更も必要ありませんでした。

chrome-launcherをinstallした上でlaunchChrome()すると普段使いのChromeとは別にChromeウィンドウが開き、そこで実際にスクレピングをしている様子(ページ遷移・フォーム入力)が見られるので、デバッグには非常に便利です。一応、オプションでheadlessにすることもできます。

まとめ

AWS Lambda上でPuppeteerを使うときはデプロイパッケージサイズの制限上、@serverless-chrome/lambdaのChromiumを用いて、デフォルトのChromiumダウンロードを無効化する必要がある。コード上は、最初に@serverless-chrome/lambda由来のChromiumとPuppeteerをconnectする処理が必要だが、それ以降は特に変わらない。