OPENLOGI Advent Calendar 2020 の12月16日の記事です。
オープンロジは、物流フルフィルメントプラットフォームサービスを提供しており、災害時には荷物が問題なく配送できるかを知る必要があるため、配送会社の状況をチェックする必要があります。
今年は、コロナをはじめ、大雨など、オープンロジでもかなりの影響があり、多くの荷物が配送出来ない状態となりました。
12月になった現在でも、残念ながら、7月の豪雨の影響で未だに配送できない地域が存在しています。
例年は、短い期間での対応だったため、あまり問題になりませんでしたが、今年は長期化したため、監視を自動化することにしました。
今回は1〜2時間もあれば実装できるBashのShell Scriptで監視の自動化を行います。
要件
まず要件を整理します。
- 配送会社の災害情報は、同じWebページ上で更新される場合があるため、RSSでの検知が難しい
- 更新日のみの変更がされ、実際に内容は変わっていないこともあるため、前回取得したページとの差分だけが知りたい
- 結果をSlackに通知したい
- 定期的に実行したい
シンプルではあるのですが、コンテンツの管理や差分など、結構手間のかかる処理が多くなっています。
でも、Linuxのコマンドならなんとかなりそうです
スクリプト
要点は後ほど解説していきます。
#!/bin/bash
rootdir=/home/user_name
channel="#channel-example"
# 佐川(大雨)
sagawadir=$rootdir/ops/disaster/sagawa
mkdir -p $sagawadir
curl https://www2.sagawa-exp.co.jp/information/detail/163/ | xmllint --xpath "//section[contains(@class, 'ss_replace')]" --html - 2>/dev/null | lynx -dump -stdin > $sagawadir/$(date '+%Y%m%d%H%M%S').html
sagawadiff=$(diff -u $(ls -trd1 $sagawadir/* | tail -n 2))
message=""
declare -A map;
map=(
["佐川(大雨)"]="$sagawadiff"
)
for carrier in ${!map[@]};
do
if [ -z "${map[$carrier]}" ]; then
continue
fi
message="$message\n# *$carrier*\n\`\`\`\n${map[$carrier]}\n\`\`\`"
done
if [ -z "$message" ]; then
message="配送停止地域の更新はありません!"
else
message="<!here> 配送停止地域の更新があります!確認してください!\n$message"
fi
# 開発中は、echoでメッセージ内容を表示させる
# echo -e "$message"
curl -X POST 'https://slack.com/api/chat.postMessage' -d 'token=SLACK_BOT_TOKEN' -d "channel=$channel" --data-urlencode "text=$(echo -e "$message")"
解説
そんなに難しいコードではありませんが、要点となる箇所だけ解説したいと思います。
#!/bin/bash
1行目のshebangの定義です。Shellではなく、Bashを指定します。
これは、Mapが使いたかったため、Bashを使用することにしました。
実際に、このスクリプトで、複数のWebページを監視していたため、Mapで管理できるようにしています。
curl https://www2.sagawa-exp.co.jp/information/detail/163/ | xmllint --xpath "//section[contains(@class, 'ss_replace')]" --html - 2>/dev/null | lynx -dump -stdin > $sagawadir/$(date '+%Y%m%d%H%M%S').html
8行目の1行で下記のことを行っています。
-
curl
でHTMLコンテンツを取得 -
xmllint
のxpathで指定の要素を取得 - テキストベースのウェブブラウザである
lynx
で、レイアウトを整形 - ファイルとして保存
特徴としては、linx
を使用して、タグを除去しつつ、HTMLのレイアウトに整形している点です。
これによって、差分を取得する際に、きれいなレイアウトで比較できるようになっています。
sagawadiff=$(diff -u $(ls -trd1 $sagawadir/* | tail -n 2))
9行目は、少し複雑です。
まず、ls
で日付の昇順にソートし、横ではなく、縦のリストにします。
tail
を使用して、リストの末尾2件だけになるようにし、比較したい最新のファイルとして取得します。
取得した2件のファイルを、diff
のunifiled形式を使用して、Gitでお馴染みのdiff形式で表示するようにしています。
Shell Scriptならこういう処理もワンライナーで処理できるので便利です。
declare -A map;
map=(
["佐川(大雨)"]="$sagawadiff"
)
13行目の上記で、Bashにした理由のMapが出てきます。
Mapを使用することにより、複数のdiffの結果を一括で通知できるようにします。
今回は1件だけの例ですが実際には複数のWebページの監視をし、通知をまとめるようにしています。
curl -X POST 'https://slack.com/api/chat.postMessage' -d "token=SLACK_BOT_TOKEN" -d "channel=$channel" --data-urlencode "text=$(echo -e "$message")"
最後の行で、curlを使用してSlackに通知しています。
textのパラメータを指定する際には、--data-urlencode
を使用しましょう。
--data-urlencode
を使用しない場合、一部の記号がテキストとして認識されません。
Slack BotやTOKENなどの作成方法は、検索すればかなりの数がヒットするため、今回の記事では特に触れません。
Bot名は、実行されるサーバのホスト名を含んで、hostname-cron
としておくと、どこで実行されているのかわかるのでおすすめです。
また、Botにした場合は、チャンネルへの招待を忘れずにしましょう。
通知時の結果
今回のスクリプトを使用すると下記のような形で、Slackに通知することができるようになります。
cron
での設定
最後はcron
で定期実行すれば、定期的に監視・通知できる仕組みの完成です。
30 9,19 * * * /bin/bash /home/user_name/ops/disaster/disaster-monitor.sh
まとめ
Bashを使用したShell Scriptでも簡単に監視の仕組みを作ることができました。
もしかしたら、JavaScriptでも簡単に出来るのかなーと思いつつも、レイアウトの整形などは難しそうな気がするので、BashやShellのスクリプトの方がメリットがある場合も多いかもしれないですね。
ただ、BashやShellは、高水準な言語ではないので、できるだけ小さいプログラムにしておきたいとは思います。