「更新されたかどうか毎回見に行くのがつらい」「仕様変更に即気づきたい」——その悩み、curl + diff + 通知で自動化できます。
この記事は Webページ(HTML / JSON / API)の“差分検知”→Slackやメールに通知までを、コピペで動く最小構成から実運用のコツまでまとめました。
できるようになること
- 指定URLの内容を定期取得して変更があった時だけ通知
- Slack Webhookまたはメールにアラート送信
- 多重実行の防止、タイムアウト/再試行、**ノイズ除去(任意のフィルタ処理)**に対応
仕組み(ざっくり)
curl
でページを取得(圧縮展開・タイムアウト・再試行つき)- 前回スナップショットと
diff
で比較 - 差分があれば、URL・ハッシュ・差分抜粋を Slack/メールに通知
- スナップショットを更新
cron
で定期実行(flock
で多重起動防止)
事前準備(必要コマンド)
- 必須:
curl
,diff
,sha256sum
,cron
- Slack通知するなら:
jq
(JSON生成用、軽量) - メール通知するなら:
mailx
(S-nail/Heirloom 推奨)またはsendmail
例:Debian/Ubuntu
sudo apt install -y curl diffutils coreutils jq s-nail
コピペで動く Bash スクリプト
保存先:
/usr/local/bin/webwatch.sh
(実行権限付与sudo chmod 755 /usr/local/bin/webwatch.sh
)
#!/usr/bin/env bash
# webwatch.sh - Webサイトの変更検知→Slack/メール通知
set -euo pipefail
# ===== 設定(要変更) =========================================
URL="https://example.com/" # 監視対象URL(HTML/JSONどちらでもOK)
NAME="example-home" # 任意の識別名(ファイル名に使用)
STATE_DIR="/var/lib/webwatch" # スナップショット保存先
LOCK="/var/lock/webwatch-${NAME}.lock" # 排他ロック
TIMEOUT=20 # curl全体タイムアウト秒
RETRY=3 # 再試行回数
# 通知(どちらか / 併用可)
SLACK_WEBHOOK="" # 例: "https://hooks.slack.com/services/XXX/YYY/ZZZ" (空なら未使用)
EMAIL_TO="" # 例: "ops@example.com" (空なら未使用)
# 監視ノイズ除去用の任意フィルタ(任意・空OK)
# 例)JSONは整形して並び替え: FILTER_CMD='jq -S .'
# 例)HTMLの本文だけ取得: FILTER_CMD='pup "main text{}"'
FILTER_CMD=""
# =============================================================
# 作業ディレクトリ
mkdir -p "$STATE_DIR"
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
SNAP="${STATE_DIR}/${NAME}.snap"
CUR="${WORKDIR}/current"
DIFF="${WORKDIR}/diff.txt"
HEADERS="${WORKDIR}/headers.txt"
USER_AGENT="webwatch/1.0 (+contact: you@example.com)"
# === 取得 ===
# -fsS: fail-silent + show errors, --compressed: 圧縮展開, -L: リダイレクト追跡
# --retry: ネットワークゆらぎ対策, --max-time: 全体タイムアウト
curl -fsS --compressed -L \
--retry "$RETRY" --retry-delay 2 --retry-connrefused \
--max-time "$TIMEOUT" \
-A "$USER_AGENT" \
-D "$HEADERS" \
-o "$CUR" \
"$URL"
# === 任意フィルタ(ノイズ除去・正規化) ===
if [[ -n "${FILTER_CMD}" ]]; then
if command -v bash >/dev/null 2>&1; then
# シェル経由でパイプ適用
bash -c "$FILTER_CMD" < "$CUR" > "${CUR}.f" || cp "$CUR" "${CUR}.f"
mv "${CUR}.f" "$CUR"
fi
fi
# === 初回:スナップショット作成のみ ===
if [[ ! -f "$SNAP" ]]; then
cp "$CUR" "$SNAP"
echo "Initialized snapshot for ${NAME} ($URL)"
exit 0
fi
# === 差分比較 ===
if diff -q "$SNAP" "$CUR" >/dev/null 2>&1; then
# 変更なし
exit 0
fi
# 変更あり → 差分抽出・ハッシュ算出
diff -u "$SNAP" "$CUR" | head -n 200 > "$DIFF" || true
OLD_HASH="$(sha256sum "$SNAP" | awk '{print $1}')"
NEW_HASH="$(sha256sum "$CUR" | awk '{print $1}')"
WHEN="$(date +'%F %T %Z')"
# === 通知メッセージ(共通) ===
MSG_FILE="${WORKDIR}/message.txt"
{
echo "🔔 変更検知: ${NAME}"
echo "URL: ${URL}"
echo "検知時刻: ${WHEN}"
echo "hash: ${OLD_HASH} → ${NEW_HASH}"
echo
echo "差分(先頭200行まで):"
cat "$DIFF"
} > "$MSG_FILE"
# === Slack通知 ===
if [[ -n "${SLACK_WEBHOOK}" ]]; then
if command -v jq >/dev/null 2>&1; then
# 改行を保持してJSON化
jq -Rs --arg pretext "🔔 変更検知: ${NAME}\n${URL}\n${WHEN}\n${OLD_HASH} → ${NEW_HASH}\n" \
'split("\n") as $lines |
{text: $pretext} + {blocks: [{type:"section", text:{type:"mrkdwn", text: $pretext}},
{type:"section", text:{type:"mrkdwn", text: ( $lines[0:200] | join("\n") )}} ]}' \
< "$MSG_FILE" \
| curl -fsS -X POST -H 'Content-type: application/json' --data @- "$SLACK_WEBHOOK" >/dev/null || true
else
echo "Slack送信をスキップ(jq が必要)" >&2
fi
fi
# === メール通知 ===
SUBJECT="[webwatch] 変更検知: ${NAME}"
if [[ -n "${EMAIL_TO}" ]]; then
if command -v mailx >/dev/null 2>&1; then
mailx -s "$SUBJECT" -- "$EMAIL_TO" < "$MSG_FILE" || true
elif command -v sendmail >/dev/null 2>&1; then
{
echo "Subject: $SUBJECT"
echo "To: $EMAIL_TO"
echo "Content-Type: text/plain; charset=UTF-8"
echo
cat "$MSG_FILE"
} | sendmail -t || true
else
echo "メール送信をスキップ(mailx/sendmail なし)" >&2
fi
fi
# === スナップショット更新 ===
cp "$CUR" "$SNAP"
ポイント:
- FILTER_CMD に
jq -S .
を入れると API/JSON の並び順が安定します。 - HTMLなら
pup 'main text{}'
で本文テキストだけにして広告や日時のノイズを排除可能。
(pup
はsudo apt install -y pup
で導入可)
cron に登録(多重実行防止つき)
例:5分おきにチェック。PATHとSHELLを明示し、
flock
で多重起動をブロック。
crontab -e
以下を追記:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
*/5 * * * * /usr/bin/flock -n /var/lock/webwatch-example.lock \
/usr/local/bin/webwatch.sh >> /var/log/webwatch-example.log 2>&1
- 監視間隔は対象サイトの負荷・規約に合わせて調整(過剰ポーリングはNG)。
- ログ出力先(
/var/log/...
)で実行状況を追跡できます。
よくある“ノイズ”を減らすコツ
- 日時/トラッキングIDの除去:
sed -E 's/[0-9]{4}-[0-9]{2}-[0-9]{2}//g'
などで置換 - 特定セクションだけ比較:
pup 'main text{}'
やhxselect
で本文抽出 - JSONは正規化:
FILTER_CMD='jq -S .'
(キー順を固定) - バイナリ/画像は対象外:テキスト化できるAPI/HTMLに限定
失敗しない設計(実務のツボ)
- タイムアウト/再試行を必ず設定(
--max-time
,--retry
) - ユーザーエージェントを明示(連絡先を含めると吉)
- 多重起動は事故の元:
flock
で必ずガード - 初回は通知しない運用にするとノイズ減(上のスクリプトは初回保存のみ)
- 差分は上限付きで送る(Slackメッセージが長すぎると拒否される)
変更検知の“次の一手”(発展)
- 差分ファイルを添付してメール送信(
mailx -a
やmutt
を使用) - 重大語をハイライト:
grep -Ei 'error|critical|deprecated'
で差分から抽出 - 複数URLの一括監視:URLリストを回すラッパースクリプト化
- Docker化:依存関係をコンテナに閉じ込め、cronはホスト側で管理
法的・倫理面の注意
- robots.txt・サイト規約を守る(スクレイピングを禁止していないか)
- レート制限・夜間の配慮・キャッシュ活用(
If-Modified-Since
など)で負荷軽減 - 個人情報や著作物の二次利用に注意(検知はOKでも再配布はNGな場合あり)
トラブルシューティング
- 通知が来ない:Slack Webhook URL の権限/有効期限、メールは MTA設定を確認
- cronでだけ失敗:PATH不足・権限・改行コード(LF)を確認
- 差分が毎回出る:フィルタで日付や動的IDを削除、または JSON 正規化
- SSLエラー:証明書ストアを更新(
ca-certificates
パッケージ)
まとめ
curl
で取得、diff
で比較、変わった時だけ通知が最小の王道。- フィルタ処理で“本当に見たい差分”に寄せるとアラート精度が上がります。
cron
×flock
×タイムアウト/再試行
で安定稼働へ。
まずは1URLから始めて、必要に応じてフィルタと通知の粒度を育てていきましょう。