Claude Media
Claude Codeで気象庁の防災情報XMLを読み、警報を社内通知にする

Claude Codeで気象庁の防災情報XMLを読み、警報を社内通知にする

気象庁の防災情報XMLは、Atomフィードで更新を知らせ、電文XMLを取りに行く形で配信されます。フィードの選び方、警報電文の読み方、取得間隔の上限、気象業務法の線引きまでをClaude Codeで実装する手順で説明します。

気象庁の防災情報XMLは、登録なしで誰でも取れる公開データです。更新を知らせるAtomフィードを定期的に読み、新しい電文のURLを見つけたらXMLを取りに行く。この「PULL型」の流れさえ押さえれば、警報の発表と解除を社内のチャットに流す仕組みは数十行で書けます。

Claude Codeに任せるのは、そのポーラー(定期取得プログラム)を書いてテストするところまでです。警報の文面を要約したり言い換えたりする役は、後述の理由で持たせません。

気象庁の防災情報XMLは「フィードで探して電文を取る」配信

気象庁の気象データ高度利用ポータルサイトは、防災情報XMLフォーマット形式の電文をPULL型で提供しています。電文の更新情報をホームページ上に掲載し、利用者は掲載された情報をもとに、任意のタイミングで電文を取得します。ユーザー登録は不要です。

つまり、気象庁側から通知が飛んでくるわけではありません。自分のプログラムが「新しい電文はあるか」を聞きに行く構造です。聞きに行く先がAtomフィードで、そこに並ぶ各エントリのリンクが電文XML本体を指します。

4種類のフィードから警報用を選ぶ

フィードは更新サイクルの異なる2系統があり、それぞれ分類が分かれています。

系統更新と保持分類警報向きか
高頻度フィード更新と保持毎分更新。直近少なくとも10分の入電を掲載分類定時 / 随時 / 地震火山 / その他警報向きか随時が該当
長期フィード更新と保持毎時更新。数日間の全入電を掲載分類定時 / 随時警報向きか取りこぼしの補完向き

分類の意味は、定時が天気概況など定時に発表されるもの、随時が警報・注意報など随時発表されるもの、地震火山が地震・火山の情報、その他が残りです。警報・注意報は「随時」なので、高頻度の随時フィードを使います。URLは次のとおりです。

  • 高頻度・随時: https://www.data.jma.go.jp/developer/xml/feed/extra.xml
  • 高頻度・定時: .../feed/regular.xml
  • 高頻度・地震火山: .../feed/eqvol.xml
  • 高頻度・その他: .../feed/other.xml
  • 長期は extra_l.xml と regular_l.xml

実際にフィードを取得すると、構造はごく単純です。ルートは feed(Atom)で、各 entry に title(電文の種別)、id(電文XMLのURL)、updated(UTC)、author(発表した気象台)、content(【大分県気象警報・注意報】注意報を解除します、のような一行要約)が入ります。id が電文URLそのものなので、そのまま重複排除のキーに使えます。

フィードの rights 要素には、利用規約へのリンクが埋め込まれています。

警報電文の中身はどこを読むか

警報・注意報の電文は、管理部(Control)・ヘッダ部(Head)・内容部(Body)の3部構成です。気象庁のよくある質問によれば、この構造は現象が違っても共通で、管理部とヘッダ部は全電文で同じ構造を取ります。内容部だけが電文ごとに固有です。

警報・注意報の内容部では、Warning 要素が区域の粒度ごとに分かれます。取得したサンプルでは、次の4種類の type が並んでいました。

Warning のtype区域の粒度
気象警報・注意報(府県予報区等)区域の粒度県単位(例: 大分県)
気象警報・注意報(一次細分区域等)区域の粒度県内の地方(例: 南部)
気象警報・注意報(市町村等をまとめた地域等)区域の粒度市町村をまとめた区域
気象警報・注意報(市町村等)区域の粒度市町村(例: 佐伯市)

社内通知なら、拠点のある市町村の粒度を見るのが素直です。各 Item の Kind/Name に「波浪注意報」のような種別名、Kind/Status に状態、Area/Name と Area/Code に区域が入ります。サンプルでは状態に「解除」と「発表警報・注意報はなし」が現れました。発表中の電文で状態がどんな語になるかは、技術資料のコード表と電文ごとの解説資料で確認してください。

同じ出来事が2種類の電文で出てくる

高頻度・随時フィードには、同じ気象台・同じ時刻の警報が2エントリ並ぶことがあります。取得したときは、気象特別警報・警報・注意報(ファイル名に VPWW53)と 気象警報・注意報(H27)(VPWW54)が、同じ秒の発表で対になっていました。両方を通知すると、1回の発表が2通になります。

どちらを採るかは、電文ごとの解説資料を読んで決める事項です。以下のコードでは、タイトルが 気象警報・注意報(H27) のエントリだけを対象にしています。タイトル中の「H27」は全角です。

Claude Codeにポーラーを作らせる

ここからがClaude Codeの出番です。いきなり「気象庁の警報を通知するツールを作って」と頼むと、仕様の穴を想像で埋められます。先に、実際の電文をfixture(テスト用の固定データ)として保存させるのが近道です。

mkdir -p jma-watch/fixtures && cd jma-watch
curl -s https://www.data.jma.go.jp/developer/xml/feed/extra.xml \
  -o fixtures/extra.xml

fixtureが手元にあれば、Claude Codeは本物の構造を読んでコードを書き、テストもその固定データで回せます。プロジェクトの CLAUDE.md には、次のような制約を書いておくと実装がぶれません。

## jma-watch の制約
- 取得先は気象庁防災情報XMLのAtomフィードのみ。取得間隔は5分以上
- If-Modified-Since を付け、304のときは電文を取りに行かない
- 取得済みの電文URLは state.json に記録し、再取得しない
- 通知文は電文の内容をそのまま転記する。要約・言い換え・独自の危険度判定はしない
- 通知には出典(気象庁防災情報XML と電文URL)を必ず付ける
- テストは fixtures/ の保存データだけで動かし、本番サーバーを叩かない

そのうえで、依頼は次のように具体的に出します。

fixtures/extra.xmlの構造を読んで、タイトルが「気象警報・注意報(H27)」のエントリだけを拾い、電文XMLを取得して、監視対象の市町村の警報・注意報を一覧にする関数を書いて。fixtureで動くテストも付けて。

Claude Codeは書いたテストを自分で実行して、落ちれば直す反復ができます。電文の構造確認を人間が目で追わずに済む点が、この題材での利点です。

動くポーラーのコード

次のコードは、実際に取得して動作を確認した最小構成です。初回実行は既存のエントリを既読にするだけで通知しません。2回目以降、state.json に無い電文だけを取りに行きます。

import json, os, sys, urllib.error, urllib.request
import xml.etree.ElementTree as ET
 
FEED = "https://www.data.jma.go.jp/developer/xml/feed/extra.xml"
STATE = "state.json"
TARGET_TITLE = "気象警報・注意報(H27)"
WATCH_AREAS = {"大分市", "別府市"}  # 監視する市町村名
ATOM = "{http://www.w3.org/2005/Atom}"
BODY = "{http://xml.kishou.go.jp/jmaxml1/body/meteorology1/}"
UA = {"User-Agent": "internal-jma-watch/0.1"}
 
def load_state():
    try:
        with open(STATE, encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        return None
 
def get(url, extra=None):
    req = urllib.request.Request(url, headers={**UA, **(extra or {})})
    return urllib.request.urlopen(req, timeout=30)
 
def main():
    state = load_state()
    first_run = state is None
    state = state or {"seen": [], "last_modified": None}
    headers = {}
    if state["last_modified"]:
        headers["If-Modified-Since"] = state["last_modified"]
    try:
        res = get(FEED, headers)
    except urllib.error.HTTPError as e:
        if e.code == 304:
            return  # 前回から更新なし
        raise
    state["last_modified"] = res.headers.get("Last-Modified")
    root = ET.fromstring(res.read())
    new = []
    for entry in root.findall(ATOM + "entry"):
        eid = entry.findtext(ATOM + "id")
        if entry.findtext(ATOM + "title") != TARGET_TITLE:
            continue
        if eid not in state["seen"]:
            new.append(eid)
    if not first_run:  # 初回は既存分を既読にするだけで通知しない
        for url in reversed(new):
            notify(url)
    state["seen"] = (state["seen"] + new)[-2000:]
    with open(STATE, "w", encoding="utf-8") as f:
        json.dump(state, f, ensure_ascii=False)
 
def notify(url):
    doc = ET.fromstring(get(url).read())
    lines = []
    for w in doc.iter(BODY + "Warning"):
        if w.get("type") != "気象警報・注意報(市町村等)":
            continue
        for item in w.findall(BODY + "Item"):
            area = item.findtext(BODY + "Area/" + BODY + "Name")
            kind = item.find(BODY + "Kind")
            name = kind.findtext(BODY + "Name")
            if area in WATCH_AREAS and name:
                lines.append(f"{area}: {name}({kind.findtext(BODY + 'Status')})")
    if lines:
        text = "気象庁の発表を転送\n" + "\n".join(lines) + f"\n出典: 気象庁防災情報XML {url}"
        print(text)  # 本番ではここを Slack Webhook などへの POST に置き換える
 
if __name__ == "__main__":
    main()

通知先は print の部分です。ここをSlackのIncoming Webhookへの POST に差し替えると、社内チャットに流れます。Slack側の作り方はClaude CodeでSlack Botを作る、LINEならClaude CodeでLINE Botの作り方にあります。

実行の確認と定期実行は、シェルから次のように行います。

python3 jma_watch.py   # 初回: 既存分を既読にして終了
python3 jma_watch.py   # 2回目以降: 新規の電文だけ処理
# crontab: 5分おきに実行
*/5 * * * * cd /opt/jma-watch && python3 jma_watch.py >> watch.log 2>&1

cronの代わりに、Claude Code Desktopの定期実行を使う手もあります。仕組みはClaude Code Desktopで定期実行タスクをスケジュールするで説明しています。ただしポーリングは決まった処理の繰り返しなので、毎回Claudeを起動する必要はありません。

取得間隔の上限と遮断の条件

PULL型で気をつけるのは、取りすぎです。気象庁は、防災情報XMLを公開しているURLに対し1日10GB以上のダウンロードを伴うアクセスがあれば、アクセス元のIPアドレスを遮断すると明記しています。遮断された場合は、一度取得したファイルを再度取得しない改修を求めています。

フィード自体は小さく、取得時で約60KBでした。毎分取っても1日で約87MB(60KB×1,440回)で、上限の10GBには遠く及びません。危ないのは、電文XMLを何度も取り直すバグです。サンプルコードが state.json に既読を残すのは、この遮断を避ける意味もあります。

間隔の下限にも注意があります。フィードのレスポンスには cache-control: max-age=60 が付いていたので、1分より短い間隔で叩いても新しい内容は返りません。上限側は、高頻度フィードが直近10分程度しか保持しないことが決めます。FAQは「10分程度」とした上で、保存を約束するものではなく予告なく変わる可能性があると断っています。5分おきにしているのは、この10分を割らない余裕を取った設計上の選択です。

If-Modified-Since に前回の Last-Modified を付けると、更新が無いときは 304 が返ります。実測でも確認できたので、サンプルコードはこの仕組みを使っています。

通知に載せてよい範囲を先に決める

公開データだからといって、何をしてもよいわけではありません。社内通知に載せる前に、次の3点を確認してください。

気象業務法の警報の制限。気象業務法の第二十三条は、気象庁以外の者は気象、地象、津波、高潮、波浪及び洪水の警報をしてはならない、と定めています(政令で定める場合は除く)。気象庁のよくある質問も、XML電文の取得に資格は要らない一方、利用方法によっては気象業務法に抵触する恐れがあるとしています。この記事のCLAUDE.mdが「要約・独自判定をしない」と書くのは、このためです。気象庁の発表をそのまま転記する形に留め、自社の判断で「危険です」と発する形にしない設計です。

予報業務の許可。第十七条は、気象庁以外の者が予報の業務を行うには気象庁長官の許可が要る、と定めています。警報の転送でなく、独自の予測を配る機能を足すときはここに当たります。

出典の記載と免責。気象庁ホームページのコンテンツは公共データ利用規約(第1.0版)に準拠した条件で使えますが、利用時に出典の記載が必要で、編集・加工した場合はその旨も書きます。FAQは、XML電文の記事利用でも出典表記を求めています。また、サーバー保守で配信が停止・遅延することがあり、利用者が電文で行う行為について気象庁は責任を負いません。迅速で確実な配信が必要なら、気象業務支援センターや予報業務の許可を受けた事業者へ相談する、とされています。

社内通知は、あくまで補助です。気象庁の公式発表や自治体の情報を置き換えるものではない、と通知文の末尾に書いておくと運用がぶれません。

つまずきやすい点

フィードが空に見える。初回実行は既読化のみで、通知は出ません。fixtureで試すか、state.json の一部を消して再実行すると動作を確かめられます。

訂正や取消の電文。FAQによれば、ヘッダ部の InfoType に「訂正」「取消」が入る電文があり、取消は一連の情報が取り消されることを示します。サンプルコードは InfoType を見ていないので、本番では「発表」以外の通知文を変える処理を足してください。

同じ情報の最新を選ぶ。一連の情報では、DateTime が最新の電文が有効です。Serial は連番や一意性が保証されないため、順序の判定に使いません。

仕様変更。電文の仕様やコード表は変更されることがあり、変更は更新情報ページに掲載され、希望者にはメールで周知されます。

熱中症特別警戒アラート。これは環境省の発表のため、気象庁の電文には出ません。

災害への備えを個人や家庭で整える側の話は、Claudeで家庭の防災計画とチェックリストを作る方法にまとまっています。

Claudeに要約させたくなったとき

警報の文面を、claude -p で社内向けにやさしく言い換えさせたくなるかもしれません。claude -p は非対話で実行でき、--output-format json などの出力形式も選べます。技術的には可能です。

ただし、言い換えた文は気象庁の発表ではなく、自社が作った文になります。前述の第二十三条の趣旨に照らすと、この一歩が線引きに最も近い場所です。通知は電文の転記と出典に留め、補助的な解説を付けたい場合も、気象庁の発表と区別できる形で別欄に置くのが無難です。

この記事を共有:XはてブLinkedIn