Help us understand the problem. What is going on with this article?

【最新】GoogleColaboratoryでもできる!気象庁の過去気象データをスクレイピングしてみた。

1.はじめに

1.1 前置き

学校のとある調査で気象庁のデータが大量に必要になりました。気象庁ではここでGUI操作によってscvファイルでダウンロードすることができます。しかし、それには容量制限があるので一都市ずつファイルを合成するなど人間の労力が必要になります。2,3都市ならまだしも40都市以上必要なので今回はスクレイピングすることにしました。

1.2 今回目指すもの

札幌, 室蘭, 函館, 青森, 秋田, 盛岡, 山形, 仙台, 福島, 新潟, 金沢, 富山, 長野, 宇都宮, 福井, 前橋, 熊谷, 水戸, 岐阜, 名古屋, 甲府, 銚子, 津, 静岡, 横浜, 松江, 鳥取, 京都, 彦根, 広島, 岡山, 神戸, 和歌山, 奈良, 松山, 高松, 高知, 徳島, 下関, 福岡, 佐賀, 大分, 長崎, 熊本, 鹿児島, 宮崎

以上46都市の

年月日, 陸の平均気圧(hPa), 海の平均気圧(hPa), 降水量(mm), 平均気温(℃), 平均湿度(%), 平均風速(m/s), 日照時間(h)

csv形式でスクレイピングします。

2.サイトの解析

2.1 注意事項

まずは、対象にするサイトや組織からAPIが提供されているか?ということを調べましょう。TwitterなどはAPIが提供されており簡単に好きなデータを取ることができます。この場合スクレイピングは不必要ということです。気象庁では当然のようにAPIはありません。一般人が作ったAPIはたくさんありますが(Githubにあると思う)、必要なデータが果たしてとれるのかということを吟味しなければなりません。
もう一つ。それは、サイトがしっかりスクレイピングできる構造になっているかどうかです。僕自身、スクレイピングをするのは初めてでしたので、適しているかどうかをはっきり区別できるわけではありませんが。(区別の仕方は次項で)

2.2 サイトの解析

今回は気象庁のデータサイトを例にします。データが見れる(表示されている)サイトにアクセスしてください。(気象庁ではここ)
やることは二つあります。

2.2.1 やること①:URLの解析 (読んどかないと困るゾ)

試しに札幌の2017年の1月の日ごとのデータを見てみましょう。URLは以下の通りです。

http://www.data.jma.go.jp/obd/stats/etrn/view/daily_s1.php?prec_no=14&block_no=47412&year=2017&month=1&day=&view=

URLの中の?より前の部分はサーバーへアクセスるための住所ですが、?より後ろはQueryと言われプログラミング的には「引数」、住所と対比して「手紙」「通信網」と例えることができます。
つまり、気象庁のサーバーは自らにアクセスしてきたURLからQueryを取り出しサイトを表示しているのです。
よって、後ろのQueryを編集すればほかのサイトが取れるはずです。検証してみたところ都市はprec_no(都道府県の番号)とblock_no(都市の番号)の二つで管理され省略は不可でした。年月日などはyear(年)、month(月)とday(日にち)の三つで管理され日にち以外の二つは省略不可でした。最後にサイトのテンプレートです。気象庁ではご存知の通り、表示するデータを変えることができます。風や日照時間だけにしたりだとかいくつかのテンプレートが用意されています。これはviewで表され、省略するとs1が与えられています。s1のViewは一番多くのデータを表示していたので今回はこれを使います。
2018-11-24 (5).png

2018-11-24 (6).png

↑上がs1で下がa1

(余談) Queryで設定するかPOSTで設定しているか。

今回は引数をURLに明記するかたちで渡すQueryがつかわれていましたが、完成度が高いサイトではよくPOSTがつかわれます。通信網を使い、文字では表現できないデータ(ファイルなど)を直接押して引数を渡します。それを使ってサイト上で画像を加工したり、パスワードを送ったりします。(パスワードをURLで明記したらやばいでしょ)
今回はQueryなのでURLを変えるだけですが、URLが変わってなくて怪しいなと思ったらたぶんPOSTで渡してます。開発者モードでPOSTを調べてみましょう。POSTになっても同じです。確かPOSTするAPIが純正でとれるはずなので同じようにやってみてください。

2.2.2 やること②:取りたいデータがどこにどのように表示されているか調べるためサイトを解析 (読んどかないと困るゾ)

試しに札幌の2017年の1月の日ごとのデータを見てみましょう。
http://www.data.jma.go.jp/obd/stats/etrn/view/daily_s1.php?prec_no=14&block_no=47412&year=2017&month=1&day=&view=s1
viewはs1です。次に、アクセスしているブラウザで開発者モードにしてください。サイトのソースコードが見えるやつです。最初は折り曲げられていると思います。三角を押すことで展開できると思います。ソースコードにカーソルを合わせるとその該当部分が変色します。
2018-11-24.png
これを見て真ん中の表が光ったら展開、真ん中の表が光ったら展開と繰り返していくと...

2018-11-24 (1).png

表のみが光るようになり、タグを見てみるとtableとなっています。これはHTML語で「表」という意味になります。

2018-11-24 (2).png

さらに中に入ると横一列のデータがタグで囲まれているというおぞましい光景がみえます。

2018-11-24 (3).png

カーソルを移動すると表示されている表の列も移動します。非常に楽しいです。
2018-11-24 (4).png

さて、table内のtrタグが表の一列をなしているのがわかります。名前は"mtx"だそうです。matrixの略ですね。最初の四列は横軸なので不必要です。ちなみにtrタグの中は下のようにtdタグになっています。
2018-11-24 (7).png
さて、仕組みが分かったところで実装しましょう。

4.実装

まずは、都市名、prec_no、block_noを調べ上げてlistにしましょう。以下の通りです。

 place_codeA = [14, 21, 23, 31, 32, 33, 35, 34, 36, 54, 56, 55, 48, 41, 57, 42, 43, 40, 52, 51, 49, 45, 53, 50, 46, 68, 69, 61, 60, 67, 66, 63, 65, 64, 73, 72, 74, 71, 81, 82, 85, 83, 84, 86, 88, 87]
 place_codeB = [47412, 47423, 47430, 47575, 47582, 47584, 47588, 47590, 47595, 47604, 47605, 47607, 47610, 47615, 47616, 47624, 47626, 47629, 47632, 47636, 47638, 47648, 47651, 47656, 47670, 47741, 47746, 47759, 47761, 47765, 47768, 47770, 47777, 47780, 47887, 47891, 47893, 47895, 47762, 47807, 47813, 47815, 47817, 47819, 47827, 47830]
 place_name = ["札幌", "室蘭", "函館", "青森", "秋田", "盛岡", "山形", "仙台", "福島", "新潟", "金沢", "富山", "長野", "宇都宮", "福井", "前橋", "熊谷", "水戸", "岐阜", "名古屋", "甲府", "銚子", "津", "静岡", "横浜", "松江", "鳥取", "京都", "彦根", "広島", "岡山", "神戸", "和歌山", "奈良", "松山", "高松", "高知", "徳島", "下関", "福岡", "佐賀", "大分", "長崎", "熊本", "鹿児島", "宮崎"]   

インデックス番号で等しくなっています。例えば札幌はplace_nameのなかの0番ですから、prec_codeはplace_codeAの0番。また、place_codeBの0番です。まぁ、順序が同じってことです。追加したい都市を後ろに追加すればいいだけです。(prec_noとblock_noはサイトにアクセスすればURLに書いてあります。ちゃんと追加してくださいね。)

実装に移りましょう。

main.py
#さっきの
place_codeA = [14, 21, 23, 31, 32, 33, 35, 34, 36, 54, 56, 55, 48, 41, 57, 42, 43, 40, 52, 51, 49, 45, 53, 50, 46, 68, 69, 61, 60, 67, 66, 63, 65, 64, 73, 72, 74, 71, 81, 82, 85, 83, 84, 86, 88, 87]
place_codeB = [47412, 47423, 47430, 47575, 47582, 47584, 47588, 47590, 47595, 47604, 47605, 47607, 47610, 47615, 47616, 47624, 47626, 47629, 47632, 47636, 47638, 47648, 47651, 47656, 47670, 47741, 47746, 47759, 47761, 47765, 47768, 47770, 47777, 47780, 47887, 47891, 47893, 47895, 47762, 47807, 47813, 47815, 47817, 47819, 47827, 47830]
place_name = ["札幌", "室蘭", "函館", "青森", "秋田", "盛岡", "山形", "仙台", "福島", "新潟", "金沢", "富山", "長野", "宇都宮", "福井", "前橋", "熊谷", "水戸", "岐阜", "名古屋", "甲府", "銚子", "津", "静岡", "横浜", "松江", "鳥取", "京都", "彦根", "広島", "岡山", "神戸", "和歌山", "奈良", "松山", "高松", "高知", "徳島", "下関", "福岡", "佐賀", "大分", "長崎", "熊本", "鹿児島", "宮崎"]   

import requests
from bs4 import BeautifulSoup #ダウンロードしてなかったらpipでできるからやってね。
import csv

# URLで年と月ごとの設定ができるので%sで指定した英数字を埋め込めるようにします。
base_url = "http://www.data.jma.go.jp/obd/stats/etrn/view/daily_s1.php?prec_no=%s&block_no=%s&year=%s&month=%s&day=1&view=p1"

#取ったデータをfloat型に変えるやつ。(データが取れなかったとき気象庁は"/"を埋め込んでいるから0に変える)
def str2float(str):
  try:
    return float(str)
  except:
    return 0.0


if __name__ == "__main__":
  #都市を網羅します
  for place in place_name:
    #最終的にデータを集めるリスト (下に書いてある初期値は一行目。つまり、ヘッダー。)
    All_list = [['年月日', '陸の平均気圧(hPa)', '海の平均気圧(hPa)', '降水量(mm)', '平均気温(℃)', '平均湿度(%)', '平均風速(m/s)', '日照時間(h)']]
    print(place)
    index = place_name.index(place)
    # for文で2007年~2017年までの11回。
    for year in range(2007,2018):
      print(year)
      # その年の1月~12月の12回を網羅する。
      for month in range(1,13):
        #2つの都市コードと年と月を当てはめる。
        r = requests.get(base_url%(place_codeA[index], place_codeB[index], year, month))
        r.encoding = r.apparent_encoding

        # 対象である表をスクレイピング。
        soup = BeautifulSoup(r.text)
        rows = soup.findAll('tr',class_='mtx') #タグ指定してclass名を指定するみたい。

        # 表の最初の1~4行目はカラム情報なのでスライスする。(indexだから初めは0だよ)
        rows = rows[5:]

        # 1日〜最終日までの1行を網羅し、取得します。
        for row in rows:
          data = row.findAll('td')

          #1行の中には様々なデータがあるので全部取り出す。
          # ★ポイント
          rowData = [] #初期化
          rowData.append(str(year) + "/" + str(month) + "/" + str(data[0].string))
          rowData.append(str2float(data[1].string))
          rowData.append(str2float(data[2].string))
          rowData.append(str2float(data[3].string))
          rowData.append(str2float(data[6].string))
          rowData.append(str2float(data[9].string))
          rowData.append(str2float(data[11].string))
          rowData.append(str2float(data[16].string))

          #次の行にデータを追加
          All_list.append(rowData)

    #都市ごとにデータをファイルを新しく生成して書き出す。(csvファイル形式。名前は都市名)
    with open(place + '.csv', 'w') as file:
      writer = csv.writer(file, lineterminator='\n')
      writer.writerows(All_list)

コメントを雑ではありますが書いておいたので、半分はそれで理解を。

★ポイントの解説

BeautifulSoupは名前こそ面白いものの、HTML語のデータを我らが母国語、pythonのlistのようなものに入れてくださいます。そのデータをインデックスでとればいいのであります。(何度も言うけどindexは0からだよ!)毎列、取りたいデータは同じインデックスにあるので簡単に取れます。左から一個目の「日にち」が取りたいなら0を指定すればいいし、左から4番目の「降水量」が取りたいなら3を指定すればいいんです。

他の注意事項

くどいほど言いますがindexは0からです!あとALL_listは二次元、rowDataは一次元配列です。
一次元の配列を縦にバァーっと連なっているだけです。

GoogleColaboratoryでのデータのダウンロード

GoogleColaboratoryではファイルを生成しても仮想サーバー上に生成されるだけです。仮想サーバー上にあるデータをダウンロードしましょう。

#さっきの(セルを新たに作ってやれば簡単)
place_codeA = [14, 21, 23, 31, 32, 33, 35, 34, 36, 54, 56, 55, 48, 41, 57, 42, 43, 40, 52, 51, 49, 45, 53, 50, 46, 68, 69, 61, 60, 67, 66, 63, 65, 64, 73, 72, 74, 71, 81, 82, 85, 83, 84, 86, 88, 87]
place_codeB = [47412, 47423, 47430, 47575, 47582, 47584, 47588, 47590, 47595, 47604, 47605, 47607, 47610, 47615, 47616, 47624, 47626, 47629, 47632, 47636, 47638, 47648, 47651, 47656, 47670, 47741, 47746, 47759, 47761, 47765, 47768, 47770, 47777, 47780, 47887, 47891, 47893, 47895, 47762, 47807, 47813, 47815, 47817, 47819, 47827, 47830]
place_name = ["札幌", "室蘭", "函館", "青森", "秋田", "盛岡", "山形", "仙台", "福島", "新潟", "金沢", "富山", "長野", "宇都宮", "福井", "前橋", "熊谷", "水戸", "岐阜", "名古屋", "甲府", "銚子", "津", "静岡", "横浜", "松江", "鳥取", "京都", "彦根", "広島", "岡山", "神戸", "和歌山", "奈良", "松山", "高松", "高知", "徳島", "下関", "福岡", "佐賀", "大分", "長崎", "熊本", "鹿児島", "宮崎"]   

from google.colab import files

for place in place_name:
  files.download(place + '.csv')

途中で「このサイトからの連続のファイルダウンロードを許可しますか?」みたいのが出てくるので許可を押してください。これでローカルにダウンロードできたはずです。

4.まとめ

こんかいは特定のサイトがスクレイピングできるかどうかを判断しそのサイトの規則性を見出して連続でスクレイピングしてみました。急いで書き上げたので至らなかったところもあるかと思います。ぜひ、質問してください。
Twitter: https://twitter.com/Cyber_Hacnosuke (フォローしてくださいお願いします。)
Github: https://github.com/CyberHacnoshuke

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away