AWS LambdaでWebページのスクリーンショットをDiscordのチャンネルに送る

はじめに

AWSの LambdaEventBridge を使って、FrontierのBTOセールページのキャプチャを定期的にDiscordに送信する仕組みを作成する。

Python3.12 環境で試みたがかなり面倒そうなので、puppeteer-coreにしてみた。
それでも、都合の悪い部分が出たので playwright-coreを使うことにした記事です。

環境

AWS Lambda
Node.js 20.x
playwright-core

準備

  • AWSアカウント
  • Discordサーバのチャンネル

DiscordサーバのWebHookURLを取得する

  1. 作成済みのチャンネルにて、設定画面を開く。

  2. 「連携サービス」にて、「ウェブフック」を選択する。
    discord-channel-frontier-bto-01

  3. 「新しいウェブフック」で任意の名前をつけて保存する。 discord-channel-frontier-bto-02

  4. 作成したウェブフックを選択し、「ウェブフックURLをコピー」でURLをコピーして控えておく。

上記で、ウェブフックURLを取得できる。

WebHook URLの動作確認

curlでの動作確認を行う。

curl -H "Content-Type: application/json" \
     -X POST \
     -d '{"content": "Test Message!"}' \
     [取得したWebHook URL]

上記を curlが使えるターミナルで実行すると確認できる。 discord-channel-webhook-url-confirm-01

Lambdaレイヤー用のS3バケットの作成

  1. AWSコンソールから「S3」サービスを選択する。
  2. 適当な名前をつけてバケットを作成する。

今回は、lambda-layer-for-nodejs という名前をつけた。

create-bucket-01

全体の流れ

  1. AWS LambdaにNode.jsのコードをデプロイ

  2. EventBridgeにて定期的にLambda関数を実行できるように設定

  3. 動作確認

Lambda構築

Lambdaにコードを作成する

  1. AWSコンソールにて、「Lambda」サービスを開く

  2. 「関数の作成」を選択し、作成画面を開き、下記のように作成する。

lambda-function-01 3. 作成後、index.js に下記のコードを貼り付ける。

const chromium = require('@sparticuz/chromium');
const { chromium: playwright } = require("playwright-core");
const axios = require('axios');
const FormData = require('form-data');

exports.handler = async (event, context, callback) => {
  const targetUrl = process.env.TARGET_URL;
  const webhookUrl = process.env.DISCORD_WEBHOOK_URL;

  let result = null;
  let browser = null;
  
  try {
    browser = await playwright.launch({
      args: chromium.args,
      defaultViewport: null,
      executablePath: await chromium.executablePath(),
      headless: chromium.headless === 'true',
    });

    const page = await browser.newPage({
      ignoreHTTPSErrors: true,
    });
    await page.goto(event.url || targetUrl, {waitUntil: 'domcontentloaded'});
    await page.mouse.move(0, 0)

    await autoScroll(page);

    const screenshotBuffer = await page.locator('div.block-event-page--accessory').screenshot({
      fullPage: true,
    });

    // Discordにスクリーンショットを送信
    const formData = new FormData();
    formData.append('file', screenshotBuffer, {
      filename: 'screenshot.png',
      contentType: 'image/png'
    });

    await axios.post(webhookUrl, formData, {
      headers: {
        ...formData.getHeaders(),
      },
    });
    await browser.close();
  } catch (error) {
    if (browser) {
      await browser.close();
    }
    console.error(error);
  }
  return callback(null, result);
};

async function autoScroll(page){
  await page.evaluate(async () => {
    await new Promise((resolve) => {
      let totalHeight = 0;
      const distance = 100;
      const timer = setInterval(() => {
        const scrollHeight = document.body.scrollHeight;
        window.scrollBy(0, distance);
        totalHeight += distance;
        if(totalHeight >= scrollHeight - window.innerHeight){
          clearInterval(timer);
          resolve();
        }
      }, 100);
    });
  });
}
  1. 環境変数として DISCORD_WEBHOOK_URLTARGET_URL を設定する。
環境変数
DISCORD_WEBHOOK_URL[取得したWebHookURL]
TARGET_URLhttps://www.frontier-direct.jp/direct/e/ej-sale/

環境変数は、「設定」タブから「環境変数」を選択し、「編集」をして設定する。
lambda-function-02

設定後↓
lambda-function-03

  1. Deployを実行する。

実行後、正常に更新されていればOK
lambda-function-04

上記までだと、参照できるライブラリが足りず、実行に失敗する。

そのため、ライブラリをダウンロードし、Lambdaレイヤーを作成することで動作できるようにする必要がある。

Cloud9を使ってライブラリ郡を用意する

ローカルに環境を作成しても良いが、今記事を書いているマシンには何も入れたくないので Cloud9を使ってみる。
※ローカルに 環境が入っているのであれば、そちらで構築したほうが早いのでそちらを推奨する。

  1. 「Cloud9」サービスを選択する。

  2. 「環境を作成」を押す。

  3. 下記で環境を作成した。 cloud9-create-01

  4. 作成には時間がかかるため待つ。 cloud9-create-02

  5. 作成が完了したら、Cloud9 IDE「開く」を押す。

  6. IDEが出てくればOK cloud9-create-03

  7. library.zipの作成、ターミナルを開いて下記を実行する

axiosplaywright-coreを入れる。

node -v

v20.15.0

※ランタイムと同じなのでOK

# 作業ディレクトリを作成
mkdir nodejs
cd nodejs

# axiosとplaywright-coreのインストール
npm init -y
npm i axios playwright-core
cd ../

# zipで圧縮する
zip -r library.zip nodejs
  1. chromium.zipの作成
cd ~/environment
git clone --depth=1 https://github.com/sparticuz/chromium.git && \
cd chromium && \
make chromium.zip
  1. fonts.zipの作成
cd ~/environment
mkdir fonts && \
  cd fonts && \
  curl -O https://moji.or.jp/wp-content/ipafont/IPAexfont/IPAexfont00401.zip && \
  unzip IPAexfont00401.zip && \
  mkdir .fonts && \
  cp IPAexfont00401/*.ttf .fonts/ && \
  zip -r fonts .fonts
  1. zipファイルをダウンロードする。

chromium.zipfonts.ziplibrary.zipを右クリックし、「Download」を選択する。

cloud9-create-04

上記で、Lambdaレイヤーで使うライブラリは用意できた。 11. (後始末) Cloud9の環境を削除する。

先ほど作成した環境を選択し、「削除」を押す。 cloud9-create-05

  1. ダウンロードしたzipをS3バケットにアップロードする。 cloud9-create-06

Lambdaレイヤーの作成

library レイヤーの作成

  1. AWSコンソール「Lambda」サービスを選択する。

  2. 「レイヤー」を選択し、「レイヤーの作成」を選択する。 lambda-layer-create-01

  3. libraryレイヤーを作成する。 lambda-layer-create-02

fonts レイヤーの作成

  1. fontsレイヤーを作成する。 lambda-layer-create-03

chromium レイヤーの作成

  1. chromiumレイヤーを作成する。 lambda-layer-create-04

Lambdaレイヤーの追加

chromiumレイヤーの代わりに下記でもいけるはず。
https://github.com/shelfio/chrome-aws-lambda-layer

chromiumの追加

  1. ページ下部の「レイヤー」より、「レイヤーの追加」を選択する。
    lambda-layer-add-01

  2. カスタムレイヤー「chromium」を指定して「追加」を選択する。 lambda-layer-add-02

libraryの追加

  1. ページ下部の「レイヤー」より、「レイヤーの追加」を選択する。

  2. カスタムレイヤー「library」を指定して「追加」を選択する。 lambda-layer-add-03

fontsの追加

  1. ページ下部の「レイヤー」より、「レイヤーの追加」を選択する。

  2. カスタムレイヤー「fonts」を指定して「追加」を選択する。 lambda-layer-add-04

ここまでで、レイヤーの追加は完了した。
lambda-layer-add-05

Lambda関数の設定変更

puppeteerplaywrightchromiumを使ってキャプチャを取得する実装はメモリを多く使うし、タイムアウト時間もデフォルトだと足りないので、設定を変更する必要がある。

  1. 編集している関数の「設定」タブから「一般設定」を選択する。

  2. 下記のように設定しておく。 lambda-function-setting-01

メモリ: 2048MB
タイムアウト: 3分

Lambda関数のテスト

※「Deploy」を実施済みの想定

  1. 「Test」を実施する。 test-lambda-function-01

↑できた! ただ、スクリーンショットの下の方の表示がおかしいので改善できるかを試してみる。
puppeteer-coreplaywright-coreにしてみたが改善せず。
根拠 → https://github.com/puppeteer/puppeteer/issues/1576

EventBridgeの設定

AWSコンソールにて「EventBridge」サービスを選択する。

スケジュールの作成

  1. EventBridgeのナビゲーションペイン「スケジュール」を選択し、「スケジュールを作成」を選択する。

  2. スケジュールの作成で下記のように作成し、「次へ」を選択する。 eventbridge-schedule-01

  3. ターゲットの詳細は「Lambda」とし、作成したLambda関数を選択し、「次へ」を選択する。 eventbridge-schedule-02

  4. 設定では下記のように設定。「次へ」を選択する。 eventbridge-schedule-03

  5. スケジュールに確認と作成で設定内容確認する。問題なければ「スケジュールを作成」を選択する。 eventbridge-schedule-04

  6. 作成後、スケジュール一覧に出てきていればOK! eventbridge-schedule-05

今回は、毎週金曜日の15:00にキャプチャを取るように設定したので、2024/07/05(金)の15:00 に発火できていれば最終動作確認は終了となる。

お疲れ様でした。

参考

GitHub

おわりに

最初は、Pythonで作成しようと思ったがライブラリ関連のエラーの解決がかなり大変そうだったのでNode.jsで作成することにした。
puppeteerを使っては構築できたのだが、不具合なのか実装に足りていないところがあるのかわからないが、キャプチャがおかしい部分があった。
なので、最終的には上記を解決できるplaywrightを使ってみたのだが結局解決しなかった…。
このあたりの内容は後で解決しておきたい…!

Lambdaでレイヤーを使って関数をデプロイするということをやったことがなかったのでかなり躓いたし面倒くさかった。
SAMやServerlessFrameworkを使うとこのあたりかなり楽ができるのかなーと思いながら手を動かしていたので、次回以降はそのあたりを使って作ってみたい。
ただ、Lambda知る良い機会だったので良しとする。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。