定期的に軽いタスクを実行したいときには、AWS Lambdaを始めとするFunction as a Service (FaaS)を用いると便利です。APIが提供されていれば、それを叩くだけで簡単に情報が取得できます。しかし、APIが提供されていない場合は、スクレイピングを行う必要があります。この記事では、AWS Lambda上のnode環境からPuppeteerというパッケージを用いてスクレイピングを行う方法を解説します。
Puppeteerとは
Chromiumを操作できる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.
デフォルトのChromiumは重いので、軽量版を使用する
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ダウンロードを無効にする
このままでは node_modules
にPuppeteer由来のChromiumと @serverless-chrome/lambda
由来のChromiumの両方が存在してしまうので、PuppeteerのChromium自動ダウンロードを無効化する必要があります。
そのためには、puppeteer_skip_chromium_download
環境変数を true
にします。一般的には、repositoryの package.json
と同じ階層に .npmrc
ファイルを作成して、次のように書き込みます。
puppeteer_skip_chromium_download=true
これで、容量の大きいChromiumの問題は解決です。それでは、実際に必要なパッケージを入れていきましょう。
必要なパッケージのインストール
LambdaでPuppeteerを動かすのに必要なパッケージは、
puppeteer
@serverless-chrome/lambda
: LambdaでHeadless Chromeを動かす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する処理が必要だが、それ以降は特に変わらない。