py-suruga / pycon-jp-2020-tutorial

PyCon JP 2020 チュートリアルで利用する資料です
MIT License
10 stars 4 forks source link

気象庁のxmlに対応した天気予報を聞けるbotの作成を行う #10

Closed hrsano645 closed 4 years ago

hrsano645 commented 4 years ago

https://github.com/py-suruga/pycon-jp-2020-tutorial/issues/9#issuecomment-664775513 より、Livedoor天気APIがサービス終了するとのことで、気象庁の天気予報情報を使うbotに変更する。

やらないといけないこと(2020-07-28時点

hrsano645 commented 4 years ago

xmlフォーマットの情報 気象庁防災情報XMLフォーマット | 技術資料

DLできるxmlファイルの提供元 気象庁 | 気象庁防災情報XMLフォーマット形式電文(PULL型)

個々にあるatomフィードの長期フィード、定時のリンクで良いともう

www.data.jma.go.jp/developer/xml/feed/regular_l.xml ※:

hrsano645 commented 4 years ago

気象台の天気予報はuuidが降られている。これが同じなら、このフィードを見に行く必要はなく、全国の気象台からの予報xmlファイルを取りに行けばよいと思う。

各気象台のxmlファイルで定義されている地域名を元に天気botが答えてくれるようにするのが理想。時間なければ気象台の都道府県で...

hrsano645 commented 4 years ago

atomフィードの中身は、必要に応じて出てくる情報もあるっぽい。

uuidが固定っぽいのもなんとなくわかってきたが、観測や情報公開の時に割り当てされるらしくて、例えば静岡の天気予報は常時同じuuidでは取れないっぽい。


調べを進めていくと、気象庁XML電文をREST APIで取得できることも分かった。先端IT活用推進コンソーシアム(AITC)(旧XMLコンソーシアム)という組織が公開している

API 仕様

参考(とてもありがたい情報源): https://gist.github.com/uemuraj/06f2269f00d60688b77bfa3de6e1edd0

hrsano645 commented 4 years ago

検索するAPI側を調べてみると

今日半日ぐらいアクセスしてみて感じたこと


最終的には、地域を絞った週間天気予報を使って最新の結果を取得することにした。 検索可能な地域名を作っておけば何とかなりそう。府県のみを対象にしておく。

hrsano645 commented 4 years ago

APIの検索で、一部の地域(東京都とか)を取得することができない状態がわかったので、やはり気象庁xmlのatomフィードからとりに行った方が楽かもしれない。

hrsano645 commented 4 years ago

試しに、atomフィードから週間天気予報のみの最新一覧を出すスクリプトを書いてみた(非常に雑)

# coding: utf-8
import itertools
from bs4 import BeautifulSoup

# あらかじめatomフィードのxmlはDLしてる
with open("regular_l.xml", encoding="utf-8") as jma_xmlfile:
    soup_jma_xml = BeautifulSoup(jma_xmlfile, "xml")

# entityから、府県週間天気予報 > 各気象台の情報を一覧でだして、最新の予報の電文xmlを探しに行く

tenki_list = list()

# 週間予報のみのリストを作る
yohou_list = sorted(
    [e for e in soup_jma_xml.find_all("entry") if e.find("title", text="府県週間天気予報")],
    key=lambda e: e.author.text,
)

# groupbyで地域事の週間天気予報の電文をまとめる
station_by_yohou_list = itertools.groupby(yohou_list, lambda e: e.content.text)

for name, g in station_by_yohou_list:
    content = name
    list_g = list(g)

    # ソートで最新の週間天気予報の電文を取りに行く
    g_new = sorted(list_g, key=lambda e: e.updated.text, reverse=True)[0]

    updated = g_new.updated.text
    new_url = g_new.link["href"]

    print(f"{content}: updated:{updated} new_url:{new_url}")
    tenki_list.append(f"{content}: updated:{updated} new_url:{new_url}\n")

with open("export_tenki_list.txt", "w", encoding="utf-8") as export_file:
    export_file.writelines(tenki_list)

image

hrsano645 commented 4 years ago

気象庁のxmlのパースに苦労してしまって(xmlそんなに扱ってない故です...)進捗が悪すぎたので、AITCのAPIでも試してみる。

APIの検索で、一部の地域(東京都とか)を取得することができない状態がわかったので、やはり気象庁xmlのatomフィードからとりに行った方が楽かもしれない。

これは、検索で地域別で取れたほうが楽だったからだけど、そもそもbotが毎回リクエストを打つのはよくないので、地域ではなく全国レベルで全部取得したほうが早いと思う。

hrsano645 commented 4 years ago

気象庁のXMLのみで週間予報の一覧を出せるようにするスクリプト:

AITCのAPIのみで週間予報の一覧を出せるようにするスクリプト:未完成。途中で各気象台のjsonファイルがDLできないときがあるのであきらめた

hrsano645 commented 4 years ago

気象台名と地域名のマッピングを考えていたけど、当初はこうしようとした

KISYODAI_STATION_MAPS = {
    "東京都府県週間天気予報": ["東京", "東京地方"],
    "宮崎県府県週間天気予報": ["宮崎", "宮崎県"],
    "岡山県府県週間天気予報": ["岡山", "岡山県"],
    "千葉県府県週間天気予報": ["千葉", "千葉県"],
    "栃木県府県週間天気予報": ["栃木", "栃木県"],
    "三重県府県週間天気予報": ["三重", "三重県"],
    "福島県府県週間天気予報": ["中通り・浜通り"],
    "茨城県府県週間天気予報": ["茨城", "茨城県"],
    "新潟県府県週間天気予報": ["新潟", "新潟県"],
    "八重山地方府県週間天気予報": ["八重山地方"],
    "熊本県府県週間天気予報": ["熊本", "熊本県"],
    "岐阜県府県週間天気予報": ["岐阜", "岐阜県"],
    "鳥取県府県週間天気予報": ["鳥取", "鳥取県"],
    "群馬県府県週間天気予報": ["群馬", "群馬県"],
    "長野県府県週間天気予報": ["長野", "長野県"],
    "石川県府県週間天気予報": ["石川", "石川県"],
    "奈良県府県週間天気予報": ["奈良", "奈良県"],
    "山梨県府県週間天気予報": ["山梨", "山梨県"],
    "兵庫県府県週間天気予報": ["兵庫", "兵庫県"],
    "大東島地方府県週間天気予報": ["大東島地方"],
    "青森県府県週間天気予報": ["津軽"],
    "佐賀県府県週間天気予報": ["佐賀", "佐賀県"],
    "京都府府県週間天気予報": ["京都", "京都府"],
    "福岡県府県週間天気予報": ["福岡", "福岡県"],
    "山口県府県週間天気予報": ["山口", "山口県"],
    "宮古島地方府県週間天気予報": ["宮古島地方"],
    "岩手県府県週間天気予報": ["岩手", "岩手県", "内陸"],
    "長崎県府県週間天気予報": ["長崎", "長崎県"],
    "大阪府府県週間天気予報": ["大阪", "大阪府"],
    "大分県府県週間天気予報": ["大分", "大分県"],
    "沖縄本島地方府県週間天気予報": ["沖縄", "沖縄本島地方"],
    "埼玉県府県週間天気予報": ["埼玉", "埼玉県"],
    "石狩・空知・後志地方府県週間天気予報": ["石狩・空知・後志地方"],
    "高知県府県週間天気予報": ["高知", "高知県"],
    "網走・北見・紋別地方府県週間天気予報": ["網走・北見・紋別地方"],
    "山形県府県週間天気予報": ["山形", "山形県"],
    "島根県府県週間天気予報": ["島根", "島根県"],
    "愛知県府県週間天気予報": ["愛知", "愛知県"],
    "釧路・根室・十勝地方府県週間天気予報": ["釧路・根室地方"],
    "愛媛県府県週間天気予報": ["愛媛", "愛媛県"],
    "福井県府県週間天気予報": ["福井", "福井県"],
    "渡島・檜山地方府県週間天気予報": ["渡島・檜山地方"],
    "宗谷地方府県週間天気予報": ["宗谷地方"],
    "広島県府県週間天気予報": ["広島", "広島県"],
    "徳島県府県週間天気予報": ["徳島", "徳島県"],
    "上川・留萌地方府県週間天気予報": ["上川・留萌地方"],
    "鹿児島県府県週間天気予報": ["鹿児島", "鹿児島県", "鹿児島県(奄美地方除く)"],
    "滋賀県府県週間天気予報": ["滋賀", "滋賀県"],
    "静岡県府県週間天気予報": ["静岡", "静岡県"],
    "富山県府県週間天気予報": ["富山", "富山県"],
    "宮城県府県週間天気予報": ["宮城", "宮城県", "東部"],
    "胆振・日高地方府県週間天気予報": ["胆振・日高地方"],
    "和歌山県府県週間天気予報": ["和歌山", "和歌山県"],
    "神奈川県府県週間天気予報": ["神奈川", "神奈川県"],
    "秋田県府県週間天気予報": ["秋田", "秋田県"],
    "香川県府県週間天気予報": ["香川", "香川県"],
}

ただこれだと問題があって、キーとなる地域名と、気象台の一致はいいけど、キーとなる地域名と気象台が複数ある場合に困る。 わかりやすいのは北海道になる。 例: 北海道 -> 石狩とか釧路とか網走とかが対象になるけど、この方法だと北海道というあいまい検索はできない。

なので、以下のようにしたらいいかも。地域名(station_name)と気象台名(kisyodai_name)を辞書で両方とも複数対応しておく。天気情報を出すときに、気象台の複数の情報がリストアップされてしまうがしょうがない?

KISYODAI_STATION_MAPS = [
    {"station_name":["地域名1", "地域名2"], "kisyodai_name":["気象台名1", "気象台名2"]}
]
hrsano645 commented 4 years ago

今回は、マッピングを考えている時間もそれほどないので、正しさに目をつぶっておく...余裕があったら対応したい。 (この辺は多分VUI的な方面を見ておくと対応しやすかったかもしれない。)

hrsano645 commented 4 years ago

天気botの実体部分の今日の進捗。大体できてきたので、詰めてからテストを書いてみる

https://github.com/py-suruga/pycon-jp-2020-tutorial/blob/3c10e168e7e73ad64e4faa4149073b9b5ab02a83/pt_slackbot/botfunc/jma_weekly_weather.py

このときに、テスト用のxmlファイルを用意したくて、固定にしたい。 なので、予めDLしてあるファイルパスあたりをpytestのmonkeypatch.setattrで置き換えられるといいかも。 https://thinkami.hatenablog.com/entry/2017/03/07/065903

hrsano645 commented 4 years ago

pytestでテストを用意する

使うモジュールはこれ

テストする項目を仮ぎめ

hrsano645 commented 4 years ago

とりあえずの動くテストが作れた。雑なassertなので、上で決めたことをちゃんとやること。

https://github.com/py-suruga/pycon-jp-2020-tutorial/blob/f68b200ceb1de6947facde51545b1262124f3d72/pt_slackbot/tests/test_jma_weekly_weather.py

hrsano645 commented 4 years ago

monkeypatchを全部で動かすのが面倒なので、fixtureも使ったw

hrsano645 commented 4 years ago

今回の天気botは動作がかなり遅いので、slackbotのEvents APIのレスポンス再送条件に当たる可能性が高い(というかテスト中にしょっちゅうあたってる)

なので、xmlファイルをDLする前にレスポンスしてしまって、xmlファイルのDLが終わって天気が見れるようになったら結果を返すようにしたほうがいいかも。今のbotの共通のハンドル関数だとできないから何らかの変更が必要...

hrsano645 commented 4 years ago

共通ハンドル関数をasyncにして、何度かメッセージを受け取れるようにするとか?APIの処理待ちをするときはそれがいいはず。

hrsano645 commented 4 years ago

現在の方法でやることにした