Web クローラーを AWS Lambda で動かした

こんにちは。 今日は年末年始に Web クローラーを作ったので、備忘録としてまとめます。

動機

妻の仕事の作業として、毎週特定の曜日に Web ページへ手入力をしている作業があり、面倒と言っていました。

退屈な作業はコンピューターにやらせてしまえ、ということで Web クローラー作ろうと思ったわけです。 (あと TypeScript の勉強にもなるとも)

構成

以下のような構成で作りました。

  • Web クローラー : puppeteer
  • 実装 : TypeScript
  • 実行環境 : AWS Lambda
  • データストア : Amazon DynamoDB
  • テスト : Jest
  • インフラの定義 : Serverless Framework, (Terraform)
  • CI/CD パイプライン : Github Actions

puppeteer

puppeteer はプログラムから API で Chrome ブラウザを制御できる Node.js のライブラリです。 JavaScript で作られており、同期処理を使って以下のように簡単に Web クローラーを実装できます。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch(); // ブラウザオブジェクト作成
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png' }); // スクショ保存

  await browser.close();
})();

もちろん、TypeScript でも書けます。

また、Chromium のヘッドレスブラウザ(GUI を使わず起動できるブラウザ)を内蔵しています。

AWS Lambda

AWS Lambda(Lambda) は AWS が提供している、サーバレスでアプリケーションコードを実行できるコンピューティングサービスです。

イベント駆動型のサービスとして使うことが一般的です。

今回は EventBridge も使って、特定の時間にイベントを発生させて Lambda を起動し、Web クローラーを実行させます。

puppeteer on AWS Lambda

Lambda は保存できるコードのサイズに圧縮時 50 MB, 非圧縮時 250 MB までの制限があります。[参考]

puppeteer はそのままだと、内蔵された Chromium ヘッドレスブラウザの容量が大きいせいでこの制限を超えてしまいます。 つまり、puppeteer のサイズを削減する必要があります。

そこで、puppeteer の Chromium ブラウザを Lambda 用に最適化して容量を削減した、chrome-aws-lambda を使います。 これにより、Lambda のコードのサイズ制限を回避できます。

ちなみに、先程のコードは下記のように置換する必要があります。

const chromium  = require('chrome-aws-lambda');

(async () => {
  const browser = await chromium.puppeteer.launch(); // ブラウザオブジェクト作成
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png' }); // スクショ保存

  await browser.close();
})();

Amazon DynamoDB

Amazon DynamoDB(DynamoDB) は AWS マネージドの NoSQL データベースサービスです。

今回、Web サイトから取得したデータの内容によって、Web クローラーを起動時に行う処理が変わるため、データを保存する必要がありました。

簡単な構造化データを保存するだけで、AWS でインフラを構築しているということもあり DynamoDB を採用しました。

Web クローラーの実装では aws-sdk を使って DynamoDB とデータをやり取りしています。

(なお、DynamoDB は色々な事情により Terraform で構築しましたが、今回は割愛します。)

Serverless Framework

Lambda と EventBridge をデプロイさせる役には Serverless Framework を使いました。

Serverless Framework はサーバレスに特化したインフラストラクチャとアプリケーションをデプロイするためのフレームワークです。 Serverless Framework では YAML ファイルにインフラを定義してデプロイをします。以下は Lambda + EventBridge の定義例になります。

service: lambda

plugins:
  - serverless-plugin-typescript # アプリケーションのコードに TypeScript を使うため

frameworkVersion: "2"

provider:
  name: aws
  region: ap-northeast-1
  runtime: nodejs14.x

functions:
  lambda:
    handler: lambdaHandler.handler # Lambda のコードを実行させるメソッド
    role: arn:aws:iam::XXXXXXXXXXXX:role/ExampleLambdaExecutionRole
    events:
      - schedule:
          enabled: true
          rate: cron(0 0 ? * MON-THU *)

Github Actions

CI/CD パイプラインには慣れていた Github Actions を使いました。 Github Actions はテストと Serverless Framework によるデプロイを実行する役になります。 なお、テストには JavaScript のテストライブラリ Jest を使っています。(今回割愛します)

Github Actions も YAML ファイルにデプロイするワークフローを定義します。 以下では、Github リポジトリからコードをチェックアウトし、Serverless Framework を使ってデプロイする例になります。

on:
  push:
    branches:
      - main

jobs:
  apply:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    strategy:
      matrix:
        node-version: [14.x]

    steps:
    - name: Check out repository # リポジトリのチェックアウト
      uses: actions/checkout@v2

    - name: Configure AWS # Serverless Framework からでプロイするため AWS Credential を設定
      uses: aws-actions/configure-aws-credentials@master
      with:
        role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
        aws-region: ${{ secrets.AWS_DEFAULT_REGION }}

    - name: serverless deploy # https://github.com/serverless/github-action を使います。コードの記述量が減って便利です。
      uses: serverless/github-action@master
      with:
        args: deploy

まとめ

今回、puppeteer, Lambda, DynamoDB を使った Web クローラーを作った話をしました。 puppeteer 初心者でしたが、空いた時間に 3 日くらいで実装できました。 もし、Web ページへ煩雑な作業があるとき、作ってみてはいかがでしょうか。

Lambda や DynamoDB は無料利用枠もあるので、簡単な Web クローラーなら無料で使えると思います(私も無料枠に収まっています)[Lambda 料金][DynamoDB 料金]