はてなグラフへのポストを行うスクリプトの理解

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">

OpenStructで動的構造体を作る - rubyco(るびこ)の日記

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が付いていると、とても便利ということですね。