はじめに
くだらない話を見たい方はクリック
気象庁のホームページには,みなさんご存知気象データをダウンロードするページがあります.
地点から,気温・降水量など様々な項目から選択してデータをcsv形式でダウンロードすることができるため,とても便利なのですが...
そんなの甘えだと思いませんか?(思いません)
冗談はさておき,ダウンロードできることをド忘れしていた私はわざわざこのページをスクレイピングしてデータを取得しました.
Pythonだから簡単にかけるとは言え,時間を浪費してしまいました.なのでその時間が無駄ではなかったと自分の中でオチをつけるために,プログラムの内容をここで紹介することにします.
本編
環境
MacOS BigSur 11.4
Python 3.8.2
Jupyter notebook
スクレイピングしたサイトURL
ソースコード
今回は数値データのみ欲しかったので「年」の項目は無視しています.
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を見ることができる(←押せばでてくるはず)ので,表示された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行がとても長い.
ちなみにこの1行が示す情報量は先程の画像の
この赤枠部分のみです.なんだか嫌になりそうですが,欲しい数値の部分をよく見ると以外と単純な構造となっています.
<td class="data_0_0_0_0">3.3</td>
これは1883年1月の日平均気温の月平均値を示しています.1月だけでなく,2月,3月...とすべて同じメソッド,クラス名を使用していることがわかります.
こういった親切なクラスの定義付けがあれば,スクレイピングはとても楽に行えます.
「よし,材料も揃ったことだし,URLにGETリクエストを送ってBeautifulSoupでちゃちゃっと要素抜き出しちゃおう」と思ったのですが,1つこのHTMLには落とし穴がありました.
それがこの部分.私が赤線を引いたわけじゃあありません.
公式サイトの補足説明によると
赤線は、観測場所を移転した場合、観測装置を変更した場合または観測の時間間隔を変更した場合に、その前後のデータが均質でないことを示します。
とのこと.
普段なら「ふーん」で済ませますが,今回はそうは行きません.というのも,この部分のクラス名は
<td class="data_1t_0_0_0">5.7</td>
となっています.なので先程のクラス名data_0_0_0_0
と合わせて,data_1t_0_0_0
も考えないといけなくなりました.
先頭のデータが一致していることを考慮するとこの場合最適な解決方法は正規表現になってくるかと思います.
正規表現とは簡単に言うと,文字列内で文字の組み合わせを照合するために用いられるパターンのことで,今回のような部分一致している情報に対しての処理に使うことができます.
詳しく説明するほど,私は正規表現に精通していないので,もっと知りたい人は分かりやすいpythonの正規表現の例を見ると良いです.
<td class="data_1t_0_0_0">5.7</td>
<td class="data_0_0_0_0">3.3</td>
この2つの要素について,正規表現を行うと以下の様な表現になります.
'<td.*?class="data_.*?">(.*?)</td>'
ここで説明しておきたいのは4つ
-
.
: 任意の一文字 -
*
: 0回以上の繰り返し -
?
: 0回または1回 -
()
: グループ化
です.これを.*?
というように組み合わせることによって,任意の一文字を0回以上繰り返すか繰り返さないか,つまり,「任意の文字列」というような解釈ができます.
この解釈はかなり複雑なので自信はあまりないですが,これにてクラス名の問題は解決しました.
ここまで来たらもうあとは簡単.もう一度ソースコードを見てみましょう.
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リクエスト
- 文字化けを避けるためのエンコーディング
を行っています.
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年1月
までのデータにするためにこのようにしています.
そしてそれを最後にkishou.txt
に書き込む.
以上により取得したデータは以下のようになり,取得完了です.
3.3
3.8
6.1
11.9
16.5
21.3
26.2
:
まとめ
データを取得することができました!コード量は少ないものの,長い道のりでしたね.
BeautifulSoup使うと思ってたのですが,正規表現を使ったので結局つかいませんでした.(^q^)
ここまで見てくれた方のなにか参考になればと幸いです.
私から最後に伝えたいことは,スクレイピングする前にダウンロードできないか見ろ!!