はじめに
長期間の連続的な降雨データが必要となったため,水文水質データベースから1時間降水量データをスクレイピングすることにしました。
降水量データを(正攻法的に)取得する方法のひとつは,気象庁アメダスのデータを取得することです。
しかし,気象庁アメダスよりデータをダウンロードする際には,データ量に制限がかけられています。
例えば,1時間降水量データは1年強くらいしか一度にダウンロードできません。
最近,台風シーズンになるとよく「100年に一度の雨」とかニュースで耳にしますが,この年数(降雨の発生確率)を求めるためには,数十年程度の降水量データが必要です。
気象庁アメダスから例えば40年分の降水量データをダウンロードしようとすると,それはそれはまあ大変な作業になります。
水文水質データベースから降水量データをクレピング
下記の説明には,正確性を欠いている部分があります。
水文水質データベースのURL構造
ここでは,山梨県の小淵沢を例に取り扱ってみます。
URLは下記の通りになっており,3つのことが読み取れます。
- ID:103081283324005
- BGNDATE:20210901
- ENDDATE:20211231
IDとBGNDATEとENDDATEの3つのパラメータがURLを構成していることがわかります。
これらのパラメータを適当にいじってやれば,任意のページが開けます。
基本的なスクレイピングのコード
ここでは,Pythonでスクレイピングする際によく利用されるライブラリBeautifulSoup
とurllib
のrequest
を使います。
from bs4 import BeautifulSoup
from urllib import request
そして,URLをrequestライブラリで開いて,データを取得します。
url = 'http://www1.river.go.jp/cgi-bin/DspRainData.exe?KIND=2&ID=103081283324005&BGNDATE=20210901&ENDDATE=20211231&KAWABOU=NO'
# 指定したurlを開く
response = request.urlopen(url)
#urlからBeautifulSoupオブジェクトをつくる
soup = BeautifulSoup(response, features='lxml')
#urlを閉じる
response.close()
request.urlopen(url)
で,読んで字のごとくurl
を開きます。
BeautifulSoup
とは,公式ドキュメントにある通り,HTMLやXMLファイルからデータを取ってくるPythonライブラリです。
Beautiful Soup is a Python library for pulling data out of HTML and XML files. It works with your favorite parser to provide idiomatic ways of navigating, searching, and modifying the parse tree.
公式ドキュメント:https://www.crummy.com/software/BeautifulSoup/bs4/doc/
ここで,soup
の中身はこんな感じになってます。
print(soup)
#実行結果の一部
<tr>
<th bgcolor="#FFFFCC"><font size="-1">2021/09/30</font></th>
<td align="right"><font color="#0000ff" size="-1">0.0</font></td>
<td align="right" bgcolor="#e6e6e7"><font color="#0000ff" size="-1">0.0</font></td>
<td align="right"><font color="#0000ff" size="-1">0.0</font></td>
<td align="right" bgcolor="#e6e6e7"><font color="#0000ff" size="-1">0.0</font></td>
<td align="right"><font color="#0000ff" size="-1">0.0</font></td>
...
</tr>
このように,tr
タグの中身に,それぞれの時刻の降水量が入っています。
今回は降水量をスクレイピングしたいので,tr
タグに含まれるデータを取得します。
tr_data = soup.find_all('tr')
tr_data = tr_data[4:len(tr_data)-1]
soup.find_all('tr')
で,tr
タグに含まれる要素を引っこ抜きます。
引っこ抜いた最初の数行は降水量データではないので,表データの部分を抽出します。
tr_data
から降水量データだけを引っこ抜きます。
prec = []
for row in range(len(tr_data)):
tmp = tr_data[row].text
tmp = tmp.splitlines() #文字列を改行ごとに分割,リスト化
prec.append(tmp)
print(prec)
#実行結果 ['', '2021/09/01', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '閉局', '閉局', '欠測', '0.0', '0.0', 0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '0.0', '1.0', '0.0']
これで,降水量データをリストに格納することができました。
prec
はそれぞれの「日」と「降水量」を1つの要素とした,「月の日数の長さ」の二次元配列です。
扱いやすいようにpandas
のデータフレームに変換しておきます。
各要素の1成分目はなにもデータが入っていない空の成分なので,それを切り落とします。
df = pd.DataFrame(prec)
df = df.drop([0], axis=1)
df.columns = range(df.shape[1])
最終的には,1列目を日時,2列目を降水量とするのが扱いやすいと思います。
そのため,各要素の日時を取得し,それに対応する降水量を配列に収納します。
fixed_prec = [['Date Time', 'Precipitation (mm/h)']]
seq_time = [str(i) + ":00" for i in range(1, 25)]
for row in range(len(df)):
for col in range(df.shape[1]-1):
date = df.iat[row, 0]
tmp_prec = df.iat[row, col+1].strip()
if tmp_prec == '':
fixed_prec.append([date + " " + seq_time[col], 'Scraping Failuer'])
else:
fixed_prec.append([date + " " + seq_time[col], tmp_prec])
print(fixed_prec)
#実行結果
[['Date Time', 'Precipitation (mm/h)'],
['2002/01/01 1:00', '0.0'],
['2002/01/01 2:00', '0.0'],
['2002/01/01 3:00', '0.0'],
['2002/01/01 4:00', '0.0'],
['2002/01/01 5:00', '0.0'],
['2002/01/01 6:00', '0.0'],
['2002/01/01 7:00', '0.0'],
['2002/01/01 8:00', '0.0'],
...]
このように,スクレイピングができました。
後はcsvファイル等に格納して完了です。
コードまとめ
これらのコードをまとめた,1地点で複数年をスクレイピングするコードが下記のとおりです。
from bs4 import BeautifulSoup
import csv
import time
from urllib import request
import pandas as pd
place = '日向山'
ID = '103081283313060'
start_year = 2002
end_year = 2003
begin_date = []
for year in range(start_year, end_year + 1):
for month in range(1, 13):
if month < 10:
begin_date.append(str(year) + '0' + str(month) + '01')
else:
begin_date.append(str(year) + str(month) + '01')
end_date = []
for year in range(start_year, end_year + 1):
for month in range(1, 13):
if month < 10:
end_date.append(str(year) + '1231')
else:
end_date.append(str(year) + '1231')
fixed_prec = [['Date Time', 'Precipitation (mm/h)']]
start_time = time.time()
for n in range(len(begin_date)):
print('Scraping ' + str(begin_date[n]) + '...')
base_url = "http://www1.river.go.jp/cgi-bin/DspRainData.exe?KIND=2&ID=%s&BGNDATE=%s&ENDDATE=%s&KAWABOU=NO"
url = base_url % (ID, begin_date[n], end_date[n])
response = request.urlopen(url)
soup = BeautifulSoup(response, features='lxml')
response.close()
#trタグは全ての表データ
tr_data = soup.find_all('tr')
#不要な行を削除
tr_data = tr_data[4:len(tr_data)-1]
prec = []
for row in range(len(tr_data)):
tmp = tr_data[row].text
tmp = tmp.splitlines() #文字列を改行ごとに分割,リスト化
prec.append(tmp)
df = pd.DataFrame(prec)
df = df.drop([0], axis=1)
df.columns = range(df.shape[1])
seq_time = [str(i) + ":00" for i in range(1, 25)]
for row in range(len(df)):
for col in range(df.shape[1]-1):
date = df.iat[row, 0]
tmp_prec = df.iat[row, col+1].strip()
if tmp_prec == '':
fixed_prec.append([date + " " + seq_time[col], 'Scraping Failuer'])
else:
fixed_prec.append([date + " " + seq_time[col], tmp_prec])
time.sleep(1)
with open(str(start_year) + str(end_year) + place + '_precipitation_WIS.csv', 'w') as file:
writer = csv.writer(file, lineterminator='\n')
writer.writerows(fixed_prec)
end_time = time.time()
print('Caluculation time:{}'.format((end_time - start_time)/60) + ' min')
おわりに
きれいなコードを書くための改善点を教えてください。