Google Cloud でWebスクレイピングして結果を通知するBotを作成
会社でG.I.Gプログラムに参加する機会を得たので、勉強がてら定期的にWebスクレイピングして結果をSlackに通知するBotをGoogle Cloudで構築してみました。
全体構成
Cloud Functionsでスクレイピングを実行してSlackに結果を通知するHTTP関数を用意して、Cloud Scheduler で定期的にCloud FunctionsにHTTPリクエストを送信するジョブを実行することでBotを構築します。

スクレイピングを実装
まずはローカルでスクレイピングを実行するコードを実装します。
HTMLの取得と解析にはaxiosとcheerioを利用します。
const cheerio = require('cheerio');
const axios = require('axios');
const scrape = async () => {
  const res = await axios.get(
    'https://xxxx/xxxx'
  );
  const $ = cheerio.load(res.data);
  // (省略)
  return 'スクレピングの結果';
};
const main = async () => {
  const result = await scrape();
  console.log(result);
}
main();
Slackへの通知を実装
続いてスクレイピング結果をSlackへ通知する部分を実装します。
Slackへのメッセージ送信にはIncoming WebhookでWebhook URLを発行してPOSTリクエストを送信します。
const notifyToSlack = (message) => {
  return axios.post(
    "https://hooks.slack.com/services/xxxxx/xxxxxx/xxxxx",
    {
      text: message,
    }
  );
}
const main = async () => {
  const result = await scrape();
  notifyToSlack(result);
}
main();
Google Cloud CLIのインストール
Google Cloudの構築を始める前にターミナルで操作するためにCLIツールをインストールしておきます。
ここではHomebrewでインストールをしていますが、インストール方法は何でも大丈夫です。
$ brew install --cask google-cloud-sdk
gcloud コマンドでGoogle Cloudを操作するために認証が必要なためログインをしておきます。
$ gcloud auth login
プロジェクトの作成
Google Cloud サービスを構築するためのプロジェクトをGoogle Cloudで作成しておきます。
Cloud FunctionsのHTTPハンドラ関数を実装
Google Cloudでアプリケーションを構築してきます。
Cloud Functionsの詳細なガイドについては公式のガイドを参照してください。
Cloud Functionsで関数を実行するには、Functions Framework for Node.jsでHTTPリクエストのハンドラ関数を登録する必要があります。
最初に@google-cloud/functions-frameworkをインストールします。
$ yarn add @google-cloud/functions-framework
続いてスクレイピングと通知の処理をHTTPハンドラ関数として登録します。
const functions = require("@google-cloud/functions-framework");
const cheerio = require("cheerio");
functions.http("checkReservation", async (_req, res) => {
  const result = await scrape();
  await notifyToSlack(result);
  res.send("ok");
});
// (省略)
デプロイ前にローカルでテスト用のサーバーを起動して関数が実行されるか確認してみましょう。
{
  "scripts": {
    "start": "functions-framework --target=checkReservation"
  }
}
$ yarn start
Serving function...
Function: checkReservation
Signature type: http
URL: http://localhost:8080/
HTTPリクエストを送信してSlackへメッセージが送信されるのを確認できれば成功です。
# 別タブ等で実行
$ curl http://localhost:8080/checkReservation
ok
Cloud Functionsへデプロイ
詳細は公式ガイドのCloud Functions の関数をデプロイするで確認できます。
関数をデプロイするにはgcloud functions deployコマンドを実行します。
$ gcloud functions deploy YOUR_FUNCTION_NAME \
[--gen2] \
--region=YOUR_REGION \
--runtime=YOUR_RUNTIME \
--source=YOUR_SOURCE_LOCATION \
--entry-point=YOUR_CODE_ENTRYPOINT \
TRIGGER_FLAGS
今回はHTTPリクエストでトリガーしたいので次のようにデプロイコマンドを作成しました。
--gen2は第2世代のCloud Functionsにデプロイすることを指定しています。--trigger-httpはHTTPリクエストで関数をトリガーするために指定します。--allow-unauthenticatedは認証なしで関数を呼び出し可能にするために指定します。Cloud Functionsはデフォルトで関数の呼び出しに認証が必要になっているので、このフラグを指定しています。
$ gcloud functions deploy reservation-notify \
  --gen2 
  --region=asia-northeast1 \
  --runtime=nodejs16 \
  --source=. \
  --entry-point=checkReservation \
  --trigger-http \
  --allow-unauthenticated \
  --project=reservation-notify
このコマンドをnpmスクリプトとして登録してデプロイを実行します。
{
  "scripts": {
    "deploy": "gcloud functions deploy reservation-notify --gen2 --region=asia-northeast1 --runtime=nodejs16 --source=. --entry-point=checkReservation --trigger-http --allow-unauthenticated --project=reservation-notify"
  }
}
$ yarn deploy
...
uri: https://reservation-vacancy-notify-xxxxxx.run.app
デプロイが成功したら発行されたURLが確認できるので、HTTPリクエストを送信してみます。
Slackにメッセージが送信されたら成功です。
$ curl https://reservation-vacancy-notify-xxxxxx.run.app
ok
Cloud Schedulerで定期実行
gcloud scheduler jobs create httpコマンドでHTTPリクエストのジョブを作成できます。
定期実行のスケジュールはcrontabの書式で設定し、タイムゾーンを指定する場合はList of tz database time zones
にあるタイムゾーンを指定できます。
今回は先程のCloud Functionsへ5分おきにHTTPリクエストを送信するジョブを作成してみます。
5分ほど待ってSlackにメッセージが飛んでくるのを確認できたら成功です。
$ gcloud scheduler jobs create http check-reservation \
  --schedule="*/5 * * * *" \
  --time-zone=Asia/Tokyo \
  --location=asia-northeast1 \
  --uri="https://reservation-vacancy-notify-rkqokacy7q-an.a.run.app" \
  --http-method=GET \
  --project="reservation-notify" \