0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

水文水質データベースの1時間降水量データをスクレイピング

Last updated at Posted at 2021-11-06

はじめに

長期間の連続的な降雨データが必要となったため,水文水質データベースから1時間降水量データをスクレイピングすることにしました。
降水量データを(正攻法的に)取得する方法のひとつは,気象庁アメダスのデータを取得することです。
しかし,気象庁アメダスよりデータをダウンロードする際には,データ量に制限がかけられています。
例えば,1時間降水量データは1年強くらいしか一度にダウンロードできません。

最近,台風シーズンになるとよく「100年に一度の雨」とかニュースで耳にしますが,この年数(降雨の発生確率)を求めるためには,数十年程度の降水量データが必要です。
気象庁アメダスから例えば40年分の降水量データをダウンロードしようとすると,それはそれはまあ大変な作業になります。

水文水質データベースから降水量データをクレピング

下記の説明には,正確性を欠いている部分があります。

水文水質データベースのURL構造

ここでは,山梨県の小淵沢を例に取り扱ってみます。
URLは下記の通りになっており,3つのことが読み取れます。

URL: http://www1.river.go.jp/cgi-bin/DspRainData.exe?KIND=2&ID=103081283324005&BGNDATE=20210901&ENDDATE=20211231&KAWABOU=NO

  • ID:103081283324005
  • BGNDATE:20210901
  • ENDDATE:20211231

IDとBGNDATEとENDDATEの3つのパラメータがURLを構成していることがわかります。
これらのパラメータを適当にいじってやれば,任意のページが開けます。

基本的なスクレイピングのコード

ここでは,Pythonでスクレイピングする際によく利用されるライブラリBeautifulSoupurllibrequestを使います。

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地点で複数年をスクレイピングするコードが下記のとおりです。

Water_Information_System_scraping.py
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')

おわりに

きれいなコードを書くための改善点を教えてください。

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?