AMEDASデータの取得部分をgetAMEDASdata.rbとして分離しておく。
# (c) http://d.hatena.ne.jp/kenkitii/20080331/p1 # -*- coding: utf-8 -*- $KCODE='u' require 'kconv' require 'ostruct' require 'date' require 'rubygems' require 'mechanize' require 'hpricot' HATENA_ID = '****' HATENA_PASSWORD = '****' def get_yesterday_spots_data url = "http://www.data.jma.go.jp/obd/stats/data/mdrr/synopday/data2.html" agent = WWW::Mechanize.new agent.user_agent_alias = 'Windows IE 6' page = agent.get(url) h = {} doc = Hpricot(page.body) places = (doc/:html/:body/:table/"tr.o1|tr.o2") places.each do |place| spot_infos = (place/"td.oR") break unless spot_infos[11] spot = (place/"td.o0").inner_html.toutf8 # 地点 low = spot_infos[4].inner_html.toutf8 # 最低気温 high = spot_infos[3].inner_html.toutf8 # 最高気温 avg = spot_infos[2].inner_html.toutf8 # 平均気温 rainfall = spot_infos[11].inner_html.toutf8 # 降水量 rainfall = 0 if rainfall == '--' h[spot] = OpenStruct.new({:low => low, :high => high, :avg => avg, :rainfall => rainfall}) end h end
get_yesterday_spots_dataを出力
require 'getAMEDASdata' pp get_yesterday_spots_data
とした(すごく単純)printdata.rbを動かしてみる。
air:~/Documents/Ruby user$ ruby -Ku printdata.rb {"熊本"=>#<OpenStruct rainfall=0, low="11.3", high="22.0", avg="16.2">, "阿蘇山"=>#<OpenStruct rainfall="0.0", low="3.3", high="13.1", avg="7.5">, "奈良"=>#<OpenStruct rainfall="0.0", low="7.7", high="16.2", avg="12.1">, : (中略) : "京都"=>#<OpenStruct rainfall="0.0", low="10.8", high="16.6", avg="13.3">, "館野"=>#<OpenStruct rainfall="59.0", low="9.8", high="13.3", avg="11.4">, "宇和島"=>#<OpenStruct rainfall=0, low="10.7", high="19.2", avg="14.9">, "横浜"=>#<OpenStruct rainfall="75.0", low="10.3", high="13.5", avg="11.5">}
ふむ。AMEDASデータの気温と降水量がget_yesterday_spots_data[地点]に入れられる訳ですね。それで各地のAMEDASデータはget_yesterday_spots_data[地点].lowとかget_yesterday_spots_data[地点].rainfallとかで引き出せるのか。OpenStructというのを使って、データを引き出しやすいようにしてあるのかな。
require 'ostruct' alice = OpenStruct.new alice.name = 'Alice' alice.mail = 'alice@example.com' p alice #=> #<OpenStruct name="Alice", mail="alice@example.com">
Hpricotは
では次にAMEDASデータの切り出しを行ってるHpricotの解釈を。
元データは
気象庁|最新の気象データ
であり、HTMLは
<html lang="ja"> <head> : <title>気象庁 | 毎日の全国データ一覧表 日別値</title> </head> <body> : <table class="o1"> <tr class="o1h"><td class="o0h" rowspan="3">地点</td><td class="o1h" colspan="2">気圧</td> <td class="o1h" colspan="3">気温</td><td class="o1h" colspan="2">湿度</td><td class="o1h" colspan="5">風向・風速</td><td class="o1h" rowspan="3">日照<br>時間</td><td class="o1h" colspan="3">降水量</td><td class="o1h" rowspan="3">降雪の<br>深さ<br>合計</td><td class="o1h" rowspan="3">最深<br>積雪</td><td class="o1h" colspan="2">天気概況</td></tr> <tr class="o1h"><td class="o1h">現地</td><td class="o1h">海面</td><td class="o1h" rowspan="2">平均</td><td class="o1h" rowspan="2">最高</td><td class="o1h" rowspan="2">最低</td><td class="o1h" rowspan="2">平均</td><td class="o1h" rowspan="2">最小</td><td class="o1h">平均</td><td class="o1h" colspan="2">最大</td><td class="o1h" colspan="2">最大瞬間</td><td class="o1h" rowspan="2">日合計</td><td class="o1h" colspan="2">日最大</td><td class="o1h" rowspan="2" style="width:50%">0600-1800</td><td class="o1h" rowspan="2" style="width:50%">1800-翌0600</td></tr> <tr class="o1h"><td class="o1h">平均</td><td class="o1h">平均</td><td class="o1h">風速</td><td class="o1h">風速</td><td class="o1h">風向</td><td class="o1h">風速</td><td class="o1h">風向</td><td class="o1h">1時間</td><td class="o1h">10分間</td></tr> <tr class="o1"> <td class="o0">札幌</td><td class="oR">1016.9</td><td class="oR">1020.1</td><td class="oR">10.7</td><td class="oR">15.6</td><td class="oR">7.2</td><td class="oR"> 51</td><td class="oR"> 34</td><td class="oR">4.5</td><td class="oR">10.8</td><td class="oC">南南東</td><td class="oR">15.5</td><td class="oC">南南東</td><td class="oR">6.3</td><td class="oR">--</td><td class="oR">--</td><td class="oR">--</td><td class="oR">--</td><td class="oR">--</td><td class="oL">薄曇</td><td class="oL">晴</td> </tr> : <tr class="o1"> <td class="o0">昭和</td><td class="oR">980.6</td><td class="oR">983.3</td><td class="oR">-1.9</td><td class="oR">-1.3</td><td class="oR">-2.6</td><td class="oR"> 96</td><td class="oR"> 95</td><td class="oR">18.7</td><td class="oR">30.9</td><td class="oC">北東</td><td class="oR">42.4</td><td class="oC">北東</td><td class="oR">--</td><td class="oR"> </td><td class="oR"> </td><td class="oR"> </td><td class="oR"> 41</td><td class="oR"> 49</td><td class="oL">ふぶき</td><td class="oL"> </td> </tr> </table> : (ここにも凡例説明のためtable文がある) : </body></html>
それでは、Hpricotの必要部分を抜き出して見てみる。
url = "http://www.data.jma.go.jp/obd/stats/data/mdrr/synopday/data2.html" agent = WWW::Mechanize.new page = agent.get(url) doc = Hpricot(page.body) places = (doc/:html/:body/:table/"tr.o1|tr.o2")
placesには、htmlタグ内のbodyタグ内のtableタグ内のtrタグでclassがo1かo2の部分を抜き出して入る、のだろう。
要素の中を検索する(search, /) シンボルも使える
http://mono.kmc.gr.jp/~yhara/rubyscraping/?Hpricot%2FShowcase#l8
そのようですね。
placesをppで出力
#<Hpricot::Elements[ {elem <tr class="o1"> "\r\n " {elem <td class="o0"> "\273\245\313\332" </td>} {elem <td class="oR"> "1016.9" </td>} {elem <td class="oR"> "1020.1" </td>} {elem <td class="oR"> "10.7" </td>} {elem <td class="oR"> "15.6" </td>} {elem <td class="oR"> "7.2" </td>} {elem <td class="oR"> " 51" </td>} {elem <td class="oR"> " 34" </td>} {elem <td class="oR"> "4.5" </td>} {elem <td class="oR"> "10.8" </td>} {elem <td class="oC"> "\306\356\306\356\305\354" </td>} {elem <td class="oR"> "15.5" </td>} {elem <td class="oC"> "\306\356\306\356\305\354" </td>} {elem <td class="oR"> "6.3" </td>} {elem <td class="oR"> "--" </td>} {elem <td class="oR"> "--" </td>} {elem <td class="oR"> "--" </td>} {elem <td class="oR"> "--" </td>} {elem <td class="oR"> "--" </td>} {elem <td class="oL"> "\307\366\306\336" </td>} {elem <td class="oL"> "\300\262" </td>} "\r\n " </tr>}, {elem : </tr>}]>
さらに
places.each do |place| spot_infos = (place/"td.oR") end
となっているので、placesの最上位のelemのデータのうちから、tdタグのclassがoRのものを抜き出してspot_infosに入れている。
spot_infosをppで出力
#<Hpricot::Elements[{elem <td class="oR"> "1016.9" </td>}, {elem <td class="oR"> "1020.1" </td>}, {elem <td class="oR"> "10.7" </td>}, {elem <td class="oR"> "15.6" </td>}, {elem <td class="oR"> "7.2" </td>}, {elem <td class="oR"> " 51" </td>}, {elem <td class="oR"> " 34" </td>}, {elem <td class="oR"> "4.5" </td>}, {elem <td class="oR"> "10.8" </td>}, {elem <td class="oR"> "15.5" </td>}, {elem <td class="oR"> "6.3" </td>}, {elem <td class="oR"> "--" </td>}, {elem <td class="oR"> "--" </td>}, {elem <td class="oR"> "--" </td>}, {elem <td class="oR"> "--" </td>}, {elem <td class="oR"> "--" </td>}]> #<Hpricot::Elements[{elem <td class="oR"> "1018.0" </td>}, :
なお、places.each do |place|イテレータにある
break unless spot_infos[11]
は、凡例説明部分を削除するためですな。「各要素の単位」の説明のテーブルは11項目だけなので、spot_infos[11]が存在しないので。
データを切り出す
spot = (place/"td.o0").inner_html.toutf8 # 地点 low = spot_infos[4].inner_html.toutf8 # 最低気温 high = spot_infos[3].inner_html.toutf8 # 最高気温 avg = spot_infos[2].inner_html.toutf8 # 平均気温 rainfall = spot_infos[11].inner_html.toutf8 # 降水量 rainfall = 0 if rainfall == '--'
spotはtdのclassがo0のところを取ってきてるので、地名(地点名)ですね。
Hpricot#inner_htmlというのが活躍している。
inner_htmlで要素の中身のHTML片が取れる。
http://mono.kmc.gr.jp/~yhara/rubyscraping/?Hpricot%2FShowcase#l5
spot, low, high, avg, rainfallをppで出力
"札幌" "7.2" "15.6" "10.7" 0
んで、Hpricot#inner_htmlをString#toutf8というメソッドでUTF-8にしているのですね。
脱線
ところで、
inner_textでタグなしのテキストが取れる。
ともあるのだけど、タグ付きでいいのでしょうか。
まぁ、Hpricot#inner_htmlでもHpricot#inner_textでもタグが出てこないので(今回の場合は)良いだけど。
あ、to_htmlだとタグ付きだ。
to_htmlを使うと、中身だけでなくそのタグ自身を含むHTMLが取れる。
http://mono.kmc.gr.jp/~yhara/rubyscraping/?Hpricot%2FShowcase#l6
出力してみよう。
"<td class=\"o0\">札幌</td>" "<td class=\"oR\">7.2</td>" "<td class=\"oR\">15.6</td>" "<td class=\"oR\">10.7</td>" "<td class=\"oR\">--</td>"
そうか、対象となるタグの中身にさらにタグがあったらばHpricot#inner_textじゃないとテキストのみにならないのか。今回は既にタグ一つにまでしてあるからなぁ。
復帰
結局、元のHTMLにclassが付いていると、とても便利ということですね。