わざわざ気象庁のオープンデータをスクレイピングしてみた

## はじめに

<details><summary>くだらない話を見たい方はクリック</summary><div>

[気象庁のホームページ](http://www.jma.go.jp/jma/index.html)には，みなさんご存知気象データをダウンロードするページがあります．

地点から，気温・降水量など様々な項目から選択してデータをcsv形式でダウンロードすることができるため，とても便利なのですが...

そんなの甘えだと思いませんか？(思いません)

冗談はさておき，ダウンロードできることをド忘れしていた私はわざわざ[このページ](http://www.data.jma.go.jp/obd/stats/etrn/view/monthly_s3.php?prec_no=62&block_no=47772&year=&month=&day=&view=)をスクレイピングしてデータを取得しました．

Pythonだから簡単にかけるとは言え，時間を浪費してしまいました．なのでその時間が無駄ではなかったと自分の中でオチをつけるために，プログラムの内容をここで紹介することにします．


</div></details>

## 本編

## 環境

MacOS BigSur 11.4
Python 3.8.2
Jupyter notebook

## スクレイピングしたサイトURL
http://www.data.jma.go.jp/obd/stats/etrn/view/monthly_s3.php?prec_no=62&block_no=47772&year=&month=&day=&view="

↓↓サイト内のデータはこんな見た目です．↓↓
![スクリーンショット 2021-08-14 23.43.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562339/07c55a30-6dee-ac55-7aca-3bc371af3d52.png)


## ソースコード

今回は数値データのみ欲しかったので「年」の項目は無視しています．

```python
import requests
from bs4 import BeautifulSoup
import re

url = "http://www.data.jma.go.jp/obd/stats/etrn/view/monthly_s3.php?prec_no=62&block_no=47772&year=&month=&day=&view="

with open("data/kishou.txt", mode="w", encoding="utf-8") as f:
    response = requests.get(url)
    response.encoding = response.apparent_encoding
    
    # bs = BeautifulSoup(response.text, "html.parser")

    pattern = '<td.*?class="data_.*?">(.*?)</td>'
    
    results = re.findall(pattern, response.text, re.S)[:-12]
    
    for result in results:
        f.write(result + "\n")
```

とまぁ，ソースコードだけ見てもサイトのHTMLを知らないんだからあまりわからないよ．

ということで，実際にURL先に飛んで，私が普段使っているChromeだと右クリックして[ページのソースを表示]をクリックすれば[そのページのHTMLを見ることができる](view-source:http://www.data.jma.go.jp/obd/stats/etrn/view/monthly_s3.php?prec_no=62&block_no=47772&year=&month=&day=&view=)(←押せばでてくるはず)ので，表示されたHTMLの一部を下記に貼り付けます．

```html
<tr class="mtx" style="text-align:right;"><td style="white-space:nowrap"><div class="a_print"><a href="../view/monthly_s1.php?prec_no=62&block_no=47772&year=1883&month=&day=&view=">1883</a></div><td class="data_0_0_0_0">3.3</td><td class="data_0_0_0_0">3.8</td><td class="data_0_0_0_0">6.1</td><td class="data_0_0_0_0">11.9</td><td class="data_0_0_0_0">16.5</td><td class="data_0_0_0_0">21.3</td><td class="data_0_0_0_0">26.2</td><td class="data_0_0_0_0">26.7</td><td class="data_0_0_0_0">22.7</td><td class="data_0_0_0_0">17.6</td><td class="data_0_0_0_0">10.0</td><td class="data_0_0_0_0">5.1</td><td class="data_0_0_0_0">14.3</td></tr>
```

うーん，1行がとても長い．
ちなみにこの１行が示す情報量は先程の画像の
![スクリーンショット 2021-08-14 23.43.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562339/fbb8a565-217f-97ec-b422-425ee7b74ba5.png)
この赤枠部分のみです．なんだか嫌になりそうですが，欲しい数値の部分をよく見ると以外と単純な構造となっています．

```html
<td class="data_0_0_0_0">3.3</td>
```
これは1883年1月の日平均気温の月平均値を示しています．1月だけでなく，2月，3月...とすべて同じメソッド，クラス名を使用していることがわかります．

こういった親切なクラスの定義付けがあれば，スクレイピングはとても楽に行えます．
「よし，材料も揃ったことだし，URLにGETリクエストを送ってBeautifulSoupでちゃちゃっと要素抜き出しちゃおう」と思ったのですが，1つこのHTMLには落とし穴がありました．

![スクリーンショット 2021-08-14 23.58.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562339/c31abc82-d914-3154-24df-03e6ca3515b5.png)

それがこの部分．私が赤線を引いたわけじゃあありません．
公式サイトの補足説明によると　

> 赤線は、観測場所を移転した場合、観測装置を変更した場合または観測の時間間隔を変更した場合に、その前後のデータが均質でないことを示します。

とのこと．

普段なら「ふーん」で済ませますが，今回はそうは行きません．というのも，この部分のクラス名は

```html
<td class="data_1t_0_0_0">5.7</td>
```
となっています．なので先程のクラス名`data_0_0_0_0`と合わせて，`data_1t_0_0_0`も考えないといけなくなりました．

先頭のデータが一致していることを考慮するとこの場合最適な解決方法は**正規表現**になってくるかと思います．

正規表現とは簡単に言うと，文字列内で文字の組み合わせを照合するために用いられるパターンのことで，今回のような部分一致している情報に対しての処理に使うことができます．

詳しく説明するほど，私は正規表現に精通していないので，もっと知りたい人は[分かりやすいpythonの正規表現の例](https://qiita.com/luohao0404/items/7135b2b96f9b0b196bf3)を見ると良いです．

```html
<td class="data_1t_0_0_0">5.7</td>
<td class="data_0_0_0_0">3.3</td>
```
この２つの要素について，正規表現を行うと以下の様な表現になります．

```python
'<td.*?class="data_.*?">(.*?)</td>'
```
ここで説明しておきたいのは4つ

+ `.`: 任意の一文字
+ `*`: ０回以上の繰り返し
+ `?`: ０回または１回
+ `()`: グループ化

です．これを`.*?`というように組み合わせることによって，**任意の一文字を0回以上繰り返すか繰り返さないか**，つまり，「任意の文字列」というような解釈ができます．

この解釈はかなり複雑なので自信はあまりないですが，これにてクラス名の問題は解決しました．

ここまで来たらもうあとは簡単．もう一度ソースコードを見てみましょう．

```python
import requests
from bs4 import BeautifulSoup
import re

url = "http://www.data.jma.go.jp/obd/stats/etrn/view/monthly_s3.php?prec_no=62&block_no=47772&year=&month=&day=&view="

with open("data/kishou.txt", mode="w", encoding="utf-8") as f:
    response = requests.get(url)
    response.encoding = response.apparent_encoding
```

+ ライブラリのインポート
+ URLの設定
+ GETリクエスト
+ 文字化けを避けるためのエンコーディング

を行っています．

```python
    pattern = '<td.*?class="data_.*?">(.*?)</td>'

    results = re.findall(pattern, response.text, re.S)[:-12]

    for result in results:
        f.write(result + "\n")
```

re.findall()でパターンに一致したテキストを抽出します．
`[:-12]`でスライスしているのは，2021年のデータ上に欠損値が存在しているため，2021年１月
までのデータにするためにこのようにしています．
そしてそれを最後に`kishou.txt`に書き込む．

以上により取得したデータは以下のようになり，取得完了です．

```text
3.3
3.8
6.1
11.9
16.5
21.3
26.2
:
```

## まとめ

データを取得することができました！コード量は少ないものの，長い道のりでしたね．

BeautifulSoup使うと思ってたのですが，正規表現を使ったので結局つかいませんでした．(^q^)

ここまで見てくれた方のなにか参考になればと幸いです．

私から最後に伝えたいことは，**スクレイピングする前にダウンロードできないか見ろ！！**