お天気時計の作成(その11:気象庁API移行とエラー対応・完全版)
これまで「お天気時計」のデータ取得には、livedoor天気の互換APIである「つくみじま(Tsukumijima)氏の天気予報API」を利用させていただいていました。
しかし、APIへのアクセスが不調となったため、今回は気象庁(JMA)のJSON APIから直接データを取得する方式へライブラリ(jma_weather_get.py)を書き換えて更新を行いました。
以前作成したコードとの互換性を保ちつつ、画像取得エラーの防止や表示テキストの最適化を行いましたので、備忘録としてまとめます。
1. 移行にあたって対応した課題
気象庁APIへの移行時に直面した主な課題と、今回の修正で対応したポイントです。
- 表示テキストの長文化(
203などの天気コード対応) 気象庁APIの生テキスト(
raw_weather_text)を取得すると「くもり のはじめ頃 雨 所により...」のような極めて長い文字列が返ってくる場合があります。全天気コード(100〜400番台)を辞書(code_map)で網羅し、「曇時々雨」といった短縮表現へ確実に変換するよう対応しました。特定の天気用アイコンが存在せず404エラーになる
気象庁の画像サーバーには一部の天気コード(例:
111や211など)に対応するSVG画像が存在しません。基本コード(例:101や201)へマッピングを差し替える安全策を組み込みました。**降水確率取得時の
UnboundLocalError**時刻判定ループで変数
corが未定義のまま参照されるエラーを防ぐため、関数の冒頭で初期値を設定し例外処理を強化しました。**メインプログラム側の
ValueError: invalid literal for int()**- アイコンURLの末尾から天気コード(数値)を抽出する際、URLフォーマットの差異で空文字となり数値変換エラーが発生していました。返却する
weather_urlのフォーマットを統一して解消しました。
2. 修正後のPythonライブラリ (lib/jma_weather_get.py)
上記すべての課題に対応し、エラー落ちを防ぐガード処理を追加した最新のライブラリコードです。
import datetime import os import re import urllib.request import cairosvg from PIL import Image import requests def weather_get(): # 変数の初期化(エラー時の未定義を確実に防止) cor = "0%" weather = "不明" weather_url = "https://www.jma.go.jp/bosai/forecast/img/100.svg" # 140000 = 神奈川県(横浜)の気象庁エリアコード area_code = "140000" url = f"https://www.jma.go.jp/bosai/forecast/data/forecast/{area_code}.json" headers = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" ) } try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() except requests.exceptions.RequestException as e: print("Error:{}".format(e)) return cor, weather, weather_url else: weather_json = response.json() # --- 1. 天気と天気コードの取得と変換 --- area_weather = weather_json[0]["timeSeries"][0]["areas"][0] raw_weather_text = area_weather["weathers"][0] raw_code = str(area_weather["weatherCodes"][0]).strip() # コードに基づく綺麗な天気名の変換マッピング(全コード網羅版) code_map = { # 晴れベース (100番台) "100": "晴れ", "101": "晴時々曇", "102": "晴一時雨", "103": "晴時々雨", "104": "晴一時雪", "105": "晴時々雪", "106": "晴一時雨か雪", "107": "晴時々雨か雪", "108": "晴一時雨か雷雨", "110": "晴後曇", "111": "晴後時々雨", "112": "晴後雨", "113": "晴後時々雪", "114": "晴後雪", "115": "晴後雨か雪", "116": "晴後雨か雷雨", "117": "晴後雷雨", "118": "晴後雨か雪", "119": "晴後雨か雷雨", "120": "晴後雨", "121": "晴後雨", "122": "晴後雨", "123": "晴後山沿い雷雨", "124": "晴後山沿い雪", "125": "晴後雷雨", "126": "晴後雨か雪", "127": "晴後雨か雷雨", "128": "晴後雨か雪", "130": "朝のうち霧後晴", "131": "晴明け方一時雨", "132": "晴朝晩一時雨", "140": "晴時々雨か雪", "160": "晴一時雪か雨", "170": "晴時々雪か雨", "181": "晴後特記事項なし", # 曇りベース (200番台) "200": "曇り", "201": "曇時々晴", "202": "曇一時雨", "203": "曇時々雨", "204": "曇一時雪", "205": "曇時々雪", "206": "曇一時雨か雪", "207": "曇時々雨か雪", "208": "曇一時雨か雷雨", "209": "霧", "210": "曇後晴", "211": "曇後時々雨", "212": "曇後雨", "213": "曇後時々雪", "214": "曇後雪", "215": "曇後雨か雪", "216": "曇後雨か雷雨", "217": "曇後雷雨", "218": "曇後雨か雪", "219": "曇後雨か雷雨", "220": "曇後雨", "221": "曇後雨", "222": "曇後雨", "223": "曇後日中時々晴", "224": "曇後昼頃から雨", "225": "曇後夕方から雨", "226": "曇後夜雨", "228": "曇後雪", "229": "曇後雪", "230": "曇後雪", "231": "曇海上海岸霧かモヤ", "240": "曇時々雨か雪", "250": "曇時々雨か雷雨", "260": "曇一時雪か雨", "270": "曇時々雪か雨", "281": "曇後特記事項なし", # 雨ベース (300番台) "300": "雨", "301": "雨時々晴", "302": "雨時々止む", "303": "雨時々雪", "304": "雨か雪", "306": "大雨", "308": "雨か雷雨", "309": "雨一時雪", "311": "雨後晴", "313": "雨後曇", "314": "雨後時々雪", "315": "雨後雪", "316": "雨後雨か雪", "317": "雨後雷雨", "320": "朝のうち雨後晴", "321": "朝のうち雨後曇", "322": "雨後雪か雨", "323": "雨後昼頃から晴", "324": "雨後夕方から晴", "325": "雨後夜は晴", "326": "雨後雪", "327": "雨後雪", "328": "雨一時雪", "329": "雨一時雪", "340": "雪か雨", "350": "雨か雷雨", "361": "一部に雨", "371": "一部に雨か雪", # 雪ベース (400番台) "400": "雪", "401": "雪時々晴", "402": "雪時々止む", "403": "雪時々雨", "405": "大雪", "406": "風雪強い", "407": "暴風雪", "409": "雪一時雨", "411": "雪後晴", "413": "雪後曇", "414": "雪後雨", "420": "朝のうち雪後晴", "421": "朝のうち雪後曇", "422": "雪後雨か雪", "423": "雪後雨", "425": "雪後晴", "426": "雪後曇", "427": "雪後雨", "450": "大雪", } if raw_code in code_map: weather = code_map[raw_code] elif raw_code.startswith("1"): weather = "晴れ" elif raw_code.startswith("2"): weather = "曇り" elif raw_code.startswith("3"): weather = "雨" elif raw_code.startswith("4"): weather = "雪" else: weather = "不明" # --- 2. 安全なアイコンURLの生成 (404対策) --- icon_map = { "111": "101", "112": "110", "211": "201", "311": "301", } safe_code = ( icon_map.get(raw_code, raw_code[0] + "00") if len(raw_code) == 3 else "100" ) weather_url = ( f"https://www.jma.go.jp/bosai/forecast/img/{safe_code}.svg" ) os.makedirs("./icon", exist_ok=True) # --- 3. アイコン画像のダウンロード --- try: req = urllib.request.Request(weather_url, headers=headers) with urllib.request.urlopen(req) as res_img: img_data = res_img.read() with open("./icon/temp.svg", "wb") as f: f.write(img_data) with open("./icon/" + weather_url[-7:], "wb") as f: f.write(img_data) # --- 4. SVG -> PNG 変換 --- os.environ["LC_CTYPE"] = "ja_JP.UTF-8" svg = "./icon/temp.svg" png = "./icon/temp.png" cairosvg.svg2png(url=svg, write_to=png) img = Image.open(png) except Exception as img_err: print("Image Processing Error:{}".format(img_err)) # --- 5. 現在の時間帯の降水確率を取得 --- try: pop_series = weather_json[0]["timeSeries"][1] pop_times = pop_series["timeDefines"] pop_values = pop_series["areas"][0]["pops"] now_utc = datetime.datetime.now(datetime.timezone.utc) if pop_values: cor = f"{pop_values[0]}%" for i, time_str in enumerate(pop_times): target_time = datetime.datetime.fromisoformat(time_str) if now_utc >= target_time and i < len(pop_values): cor = f"{pop_values[i]}%" except Exception as pop_err: print("POP Processing Error:{}".format(pop_err)) print("現在の降水確率 {}".format(cor)) print("today is {}".format(weather)) return cor, weather, weather_url
3. Raspberry Pi Zero 起動時自動実行の設定(復習)
本体側のプログラム変更に伴い、自動起動の設定も改めて確認を行いました。
以前投稿した「お天気時計の作成(その5:自動起動)」と同様に、cron を使って起動時に自動実行されるよう設定しています。
設定手順の復習:
crontab -e
設定内容:
@reboot sleep 30 && python3 /home/tomtomst2/python_p/weather_clock_v10.py > /home/tomtomst2/weather.log 2>&1
- ネットワークが確立するまでの時間を確保するために
sleep 30を挟んでいます。 - エラー発生時の追跡用として
weather.logへの標準出力・エラー出力のリダイレクトを設定しています。
4. まとめ
サードパーティ製APIの停止に伴い気象庁公式APIへと移行しましたが、データ構造やレスポンスの違いから初期移行時にはいくつか予期せぬエラーが発生しました。
今回の改修により、以下の改善が実現できました。
- 表示の安定化: 天気コードの完全網羅とフォールバック処理により、長い文章で画面表示が崩れる問題を解消。
- 堅牢性の向上: 画像取得エラー(404)や変数の未定義エラーを安全にバイパスする例外処理を実装。
- 運用の自動化:
cronによる起動時自動実行とログ出力環境を整備し、メンテナンスフリーでの運用を復活。
気象庁公式APIは外部サービスの存続リスクに左右されにくいため、長期的な運用においても非常に安心感があります。自作お天気時計の動作でお困りの方の参考になれば幸いです。













