AWS Lambda + HonoでGitHub Trendingを毎朝Slack通知する
はじめに
OSSなどのコードを読みたくなることはありませんか?そんなとき、自分含め、GitHub Trendingで盛り上がっているリポジトリを漁る方もいらっしゃると思います。
そこで、興味のあるリポジトリに出会える機会を増やすため、毎日GitHub Trendingのリポジトリ情報をSlackに流すようにしました。
機能要件
GitHub Trendingで見られるリポジトリ情報はAPI提供がされていないため、スクレイピングで抽出する必要があります。言語ごとのトレンドはhttps://github.com/trending/{言語名}?since=daily
から取ることができます。
また、今回はリポジトリ名とURLが最低限分かれば良いので、Slack通知する内容は
- ランキング順位
- ユーザー名/リポジトリ名
- リポジトリURL
としています。
このような要件を踏まえた結果、アーキテクチャはこのような感じとなりました。
最初はCloudflare Workersの無料枠を使用していたのですが、
- 制限の10msをスクレイピングのみで軽く超えてしまう
- 運用コスト
- Edgeを利用したくなる場面もそこまでなさそう
の3点を考慮して、AWS Lambdaに乗り換えました。
技術スタック
言語はTypeScriptで、以下の構成で作成をしています。
- メインロジック
- Bun
- Hono
- IaC
- AWS CDK
Honoの公式ドキュメントが丁寧なので、そちらに従って準備をすることができます。
GitHub Trendingからリポジトリ情報を取得
スクレイピングでは、GitHub Trendingのページを解析して、必要な情報のみを取り出します。
ページを眺めたところ、以下の2つの方法を検討することができます。
- Box-rowクラスを持つarticle要素を全て取り出した後、それぞれについてh2要素内のテキストを取り出す
- h2要素内のテキストを全て取り出した後、"/"を含むもののみを選択する
1の手法はより頑健ではありますが、2の手法の方がより高速なため、今回は2の手法を選択しています。
コアな実装は以下のような感じとなります。
import { parse, type HTMLElement } from "node-html-parser";
const root: HTMLElement = parse(body);
const articles: Array<HTMLElement> = root.getElementsByTagName("h2");
// h2タグにはリポジトリ名以外のものも含まれるので、それを排除する。
const dirtyRepoTitles: Array<string> =
selectRepoNamesFromHtmlElements(articles);
const trends = await Promise.all(
dirtyRepoTitles.map((textDirty, idx) => {
// spanタグなど由来の\nや\sが紛れ込むので取っ払う
const repoName = textDirty ? cleanInnerText(textDirty) : "";
return {
rank: idx + 1,
title: repoName,
link: `https://github.com/${repoName}`,
};
}),
);
return trends;
スクレイピング時はquerySelectorAllよりgetElementsByTagNameの方が高速なため、今回は後者を使用しています。返却するオブジェクトが異なる点は注意が必要です。
Slack通知
アクセストークンを使用して、スクレイピングしたデータを特定のチャンネルにポストをさせます。
アクセストークン取得とアプリの設定はこちらに倣って準備をしています。
実装としては、リクエストヘッダにトークンなどの情報を付加した上で、POSTリクエストを送るシンプルなものとなっています。
import { useFetch } from "./lib/useFetch";
/**
* lang: 対象となるプログラミング言語名文字列
* repos: スクレイピング結果をJSON化したオブジェクト
*/
const attachment: Attachment = {
title: `GitHub Trending [ ${lang} ] `,
text: JSON.stringify(repos),
author_name: "GitHub Trending Feeder",
color: "#00FF00",
};
const payload: Payload = {
channel: slackBotTargetChannelName,
attachments: [attachment],
};
await useFetch({
url: "https://slack.com/api/chat.postMessage",
options: {
method: "POST",
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json; charset=utf-8",
Authorization: `Bearer ${slackBotToken}`,
Accept: "application/json",
},
},
});
ここまでで、ローカルでスクレイピング+Slack通知までを行うことができるようになりました👍
AWS Lambdaでサーバレス化する
AWSのサーバレス環境で実行するためにLambdaを、特定の時間に実行がされるようにスケジューリングするためにEventBridgeが必要になります。
スケジューリングはcron式で、毎朝9時(UTCで0:00)に設定します。
// Lambda
const fn = new NodejsFunction(this, "lambda", {
entry: "lambda/index.ts",
handler: "handler",
runtime: lambda.Runtime.NODEJS_20_X,
});
fn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
});
new apigw.LambdaRestApi(this, "myapi", {
handler: fn,
});
// Eventbridge rules
new Rule(this, "schedule-cron-github-trends", {
schedule: Schedule.cron({ minute: "0", hour: "0" }),
targets: [new LambdaFunction(fn)],
});
以上で、毎朝GitHub Trendingのリポジトリ情報がSlack通知されるようになりました!✨
最後に
低コストでGitHub Trendingを追えるようになりました!🙌
Honoは初めて扱ったのですが、書きやすさに驚かされました🔥
Discussion