サイトから XML (RSS) を取得して出力する Python のスクリプトについてまとめておきます。
具体的には、Python の標準パッケージを使って、天気予報のデータ XML を読み込みます。出力は、スクリプトの修正が簡単になるよう、テキストファイルで出力することにします。
以下の環境で動作確認をしています。
環境: Windows パソコン、Python 3.X、Anaconda、Beautiful Soup をインストールしています。
背景
機械学習の検討をしていると、サイトから過去の時系列のデータを取得して、学習用データとして使用したくなることがあります。
例えば、日付つきのログデータを自力で取得していたとき、関係がありそうな過去の天気や気温のデータをサイトから取得して合成することで、学習用データを作成したくなることがあります。これらのデータを使って機械学習を実行し、当日の天気、将来の天気予報のデータから、当日や将来の予測をするといった場合です。
わかりやすい事例では、店舗の過去の売上データがあるとき、年月日、曜日、休日と天候を合成して機械学習用の教師データを作成し、店舗の売り上げを予測するといったケースです。
また、雨が降ると渋滞するなどもよく経験すると思います。天気予報から、渋滞を予測することもかなりの確度で可能と考えられます。
こうした時系列のデータは、現状では、公共機関で RSS で公開されていることが多いです。
RSS 形式とはいっても、実質的には XML 形式のデータをダウンロードして、各タグの文字列さえ取得できれば、あとは何とかなるといった状況です。
そこで、まずは、最も典型的な事例として、天気予報のデータを取得して出力するスクリプトをまとめておくことにします。
具体的には、気象庁の天気予報のサイトを例に、XML で公開されているデータを取得します。データの取得だけできればよいのであれば、出力部分を修正することで使いまわしが可能です。
なお、各サーバーへのアクセス、取得するデータの取り扱いについては、各サイトの規定に従う必要があります。取得したデータの公開や、サーバーに負荷をかけるような使用は禁止されていることが多いため、各サイトでの確認をお願いいたします。
使い方
① 以下を参考に、パソコンにフォルダを作ってください。Windows の場合の例を示します。
例: C:\user\weather1
② ①のフォルダの中に weather_forecast1.py 等の名前でテキストファイルを作成し、下記のスクリプトをコピー&ペーストして保存してください。
③ プロンプト(または、Anaconda Prompt)を起動し、②のスクリプトを実行してください。
例: “python C:\user\weather\weather_forecast1.py”
→ 実行すると、ネットから天気予報のデータを取得して、プロンプトに表示後、weather_forecast1.txt という名前でデータを保存します。
サンプルスクリプトの説明
スクリプトの動き
スクリプトでは、気象庁のサイトの作りに応じて、xml ファイルの取得を2回行ない、天気予報のデータを取得するようにしています。
スクリプトを実行すると、まず、以下の URL (気象庁のサイト)にアクセスします。
リンク先: https://www.data.jma.go.jp/developer/xml/feed/regular_l.xml ★1
このファイルに記載されているリンクの中で、”VPFW50″ という文字列を含むリンクが1週間の天気予報を含むファイル名に対応しています。
また、”130000″ という文字列が、東京エリアに対応しています。東京以外の天気予報を取得する場合は、上記のリンクを確認し、対応するエリアの数値に差し替えてください。
つぎに、”VPFW50_130000″ という文字列(東京の1週間の天気予報に対応)を含むリンクをすべて引き抜きます。東京の1週間の天気予報に対応していますが、天気予報は時間の経過に従って何度も出されるため、複数のファイルがあります。
抽出されるリンクは複数あり、最後に抽出されるリンク(★2)が最新日時での天気予報のファイル名に対応しています。そこで、スクリプトでは、引き抜いた最後のリンク(最新のファイル名・リンク)を抽出します。
つぎに、取得したリンク(★2)にアクセスし、xml を取得します。
以下が一例です。
例: https://www.data.jma.go.jp/developer/xml/data/20220505074225_0_VPFW50_130000.xml
1週間の天気予報を記載した xml ファイルは、年月日、時刻を含んだファイル名になっています。
つまり、最新のファイル名は、固定ではなく、つねに変わっていくことになります。
加えて、過去のファイルは、順次、サーバーから消去されるようになっています。したがって、★2は、時間が経つとアクセスできなくなります。
このため、スクリプトでは、★1からファイル名を抽出し、最新の天気予報(★2)を取得しています。
この後、この xml を読み込み、ファイルの構造から、天気予報に対応する部分の文字列を引き抜いています。結果を、テキストデータで書き出します。
データの引き抜き方は、まず、上記のリンクを開き、ピラミッド構造になっている xml の各項目で、欲しい情報が上からたどっていくつ目の階層、いくつ目の項目番号に対応しているか確認します。
つぎに、その番号をたどるよう、配列の添え字を設定すると、対応する文字列を引き抜くことが可能です。xml の詳細については、記載を省略します。時間が取れれば、後日、記載を追加しようと思います。
★ XML のリンク先の修正について 2022年5月
これまで、Yahoo! の天気予報の xml を参照するサンプルコードを載せていました。ところが、2022年3月末でサービスが終了となりました。
そこでサンプルコードを、気象庁のサイトの xml を参照するよう差し替えました。そのため、解説の詳細はスキップし、取り急ぎ、動くサンプルスクリプトと関連リンク等を公開しておくことにします。
まとめ
天気予報を例に、XML データを取得する方法についてスクリプトの形でまとめました。
ネット検索をしても、実用で使える程度にまとまったサイトが見つからないようでしたので、ポイントを整理して公開しておくことにします。
これで、XML や RSS の取得なども自由自在です。
無数にあるサイトのデータを有効活用できることになります。
他にも、指定したサイトマップの XML からリンクを取得するスクリプトなどについてもまとめています。興味のある方は、関連リンクを参照してみてください。
関連リンク
・ バッチファイルで Anaconda から Python を実行する方法 【Windows 10】
・ 指定したサイトマップからリンクを取得する 【Python】
・ CSV ファイルを読み込んで機械学習と予測をする 【scikit-learn】
外部リンク
・ 気象庁 | 著作権・リンク・個人情報保護について (jma.go.jp)
・ 気象庁 | RSS配信について (jma.go.jp)
・ 気象庁 | 気象庁情報カタログ (jma.go.jp)
・ 府県予報区等 発表区域データ | 気象庁防災情報発表区域データセット (nii.ac.jp)
・ https://docs.python.org/ja/3/library/xml.etree.elementtree.html
・ https://docs.python.org/3/library/index.html
サンプルスクリプト
import urllib.request as rq1
import xml.etree.ElementTree as et1
import os
from bs4 import BeautifulSoup
# https://docs.python.org/ja/3/library/xml.etree.elementtree.html
# https://docs.python.org/3/library/index.html
def write1( file1, str1 ):
with open( file1, 'w', encoding='utf-8' ) as f1:
f1.write( str1 )
return 0
def get_urls0( url0 ):
print( url0 )
res1 = rq1.urlopen( url0 )
soup1 = BeautifulSoup( res1, 'html.parser' )
urls1 = soup1.find_all( 'link' )
a1 = []
for url1 in urls1:
url2 = url1.get( "href" )
a1.append( url2 )
a1.sort()
return a1
def get_url1( url0, str1 ):
a1 = get_urls0( url0 )
a2 = []
for i1 in range( len( a1 ) ):
if str1 in a1[i1]:
a2.append( a1[i1] )
return a2
def get_xml1( url1 ):
rq2 = rq1.Request( url1 )
with rq1.urlopen( rq2 ) as res1:
res2 = res1.read()
xml1 = et1.fromstring( res2 )
return xml1
def show_xml0( xml0 ):
print( "----------" )
print( type( xml0 ) ) # type
print( xml0 ) # xml
print( xml0.tag ) # tag
print( xml0.text ) # text
print( xml0.attrib ) # attribute
def show_xmls1( xml1 ):
show_xml0( xml1[1][0] ) # Tokyo, Week
show_xml0( xml1[2][0][0][1][0][0][0] ) # weather
show_xml0( xml1[2][0] ) # metrological inforamtion
show_xml0( xml1[2][0][0][0][0][0] ) # date and time
show_xml0( xml1[2][0][0][0][1][0] ) #
show_xml0( xml1[2][0][0][0][2][0] ) #
show_xml0( xml1[2][0][0][0][3][0] ) #
show_xml0( xml1[2][0][0][0][4][0] ) #
show_xml0( xml1[2][0][0][0][5][0] ) #
show_xml0( xml1[2][0][0][0][6][0] ) #
show_xml0( xml1[2][0][0][1][0][0][0] ) # weather forecast
show_xml0( xml1[2][0][0][1][0][0][1][0] ) #
show_xml0( xml1[2][0][0][1][0][0][1][1] ) #
show_xml0( xml1[2][0][0][1][0][0][1][2] ) #
show_xml0( xml1[2][0][0][1][0][0][1][3] ) #
show_xml0( xml1[2][0][0][1][0][0][1][4] ) #
show_xml0( xml1[2][0][0][1][0][0][1][5] ) #
show_xml0( xml1[2][0][0][1][0][0][1][6] ) #
show_xml0( xml1[2][0][0][1][0][0][2][0] ) # types
show_xml0( xml1[2][0][0][1][0][0][2][1] ) #
show_xml0( xml1[2][0][0][1][0][0][2][2] ) #
show_xml0( xml1[2][0][0][1][0][0][2][3] ) #
show_xml0( xml1[2][0][0][1][0][0][2][4] ) #
show_xml0( xml1[2][0][0][1][0][0][2][5] ) #
show_xml0( xml1[2][0][0][1][0][0][2][6] ) #
show_xml0( xml1[2][0][0][3][1][0][0] ) # probability
show_xml0( xml1[2][0][0][3][1][0][1][0] ) #
show_xml0( xml1[2][0][0][3][1][0][1][1] ) #
show_xml0( xml1[2][0][0][3][1][0][1][2] ) #
show_xml0( xml1[2][0][0][3][1][0][1][3] ) #
show_xml0( xml1[2][0][0][3][1][0][1][4] ) #
show_xml0( xml1[2][0][0][3][1][0][1][5] ) #
show_xml0( xml1[2][0][0][3][1][0][1][6] ) #
def get_weather_forecast1( xml1 ):
str1 = ""
for i1 in range( 7 ):
str2 = str( xml1[2][0][0][0][i1][0].text ) # date and time
str3 = str( xml1[2][0][0][1][0][0][1][i1].text ) # weather forecast
str4 = str( xml1[2][0][0][1][0][0][2][i1].text ) # types
str5 = str( xml1[2][0][0][3][1][0][1][i1].text ) # probability
str6 = str2 + "," + str3 + "," + str4 + "," + str5 + "\n"
str1 = str1 + str6
return str1
url0 = "https://www.data.jma.go.jp/developer/xml/feed/regular_l.xml"
str1 = "VPFW50_130000"
a1 = get_url1( url0, str1 )
url1 = a1[ len( a1 )-1 ]
print( url1 )
xml1 = get_xml1( url1 ) # xml element tree
# show_xmls1( xml1 ) # only for the analysis
str1 = get_weather_forecast1( xml1 ) # text
print( str1 )
path1 = os.path.dirname(__file__) + "/"
file1 = path1 + "weather_forecast1.txt"
write1( file1, str1 )