気象庁の府県天気予報を解析する

前回に続き #eeic_botthon で必要になった次第。

シャロちゃんに天気予報を言わせるため、気象庁から予報データを引っ張ってくる必要があった。これは「気象庁 | 天気予報 : 東京都」などから引っ張ってくるわけだがこのHTMLを解析しなければならない。

府県天気予報は需要がありそうなので解析ツールもどこかにあると思ったのだが、意外とないことが判明。めんどくさかったので当日発表では drk7.jp という個人サイトで配信されているjsonデータを用いたのだが、朝1度しか更新されないし、個人サイトで運営上不安なので、実際にこのHTMLをRubyで解析することにした。

RubyのHTML構文解析には、Nokogiriというgemを使う。これに関してはこのサイトが詳しいので、使うときはこれを見ながらirbを叩くのがよいだろう。search, xpathの違いがよくわからなくてハマったのだが、結果的に解決した。

最終的には以下のようなコードになった。

require 'nokogiri'
require 'open-uri'

class Forecasts
    attr_accessor :forecasts, :id, :name

    def initialize(id)
        @id = id
        doc = Nokogiri::HTML(open('http://www.jma.go.jp/jp/yoho/3' + ('%02d' % id) + '.html'))
        @name = doc.search("h1").first.text.gsub("天気予報\n: ", '')
        forecast = doc.search("table[@class='forecast']")[0]
        area_name_array = forecast.search("th[@class='th-area']").collect{|e| e.text.strip}

        day_forecasts = []
        forecast.search("th[@class='weather']").each do |e|
            day_forecast = {}
            day_name = e.text.strip
            day_forecast[:day_name] = day_name
            day_forecast[:day] = day_name.match(/[^\d](\d+)[^\d]/)[1].to_i
            day_forecast[:summary] = e.search("img")[0].[]('title').strip
            day_forecasts += [day_forecast]
        end

        tmp = forecast.search("td[@class='info']")
        info_arr = tmp.collect{|e| e.children[0].text.strip}
        wave_arr = tmp.collect{|e| e.children.count >= 3 ? e.children[2].text.strip : nil}
        rain_chance_arr = forecast.search("table[@class='rain']")
        temp_info_arr = forecast.search("td[@class='temp']")

        for i in 0...day_forecasts.count
            day_forecasts[i][:info] = info_arr[i]
            day_forecasts[i][:wave] = wave_arr[i] if wave_arr[i]

            rain_chance_str = rain_chance_arr[i].search("td[@align='right']").collect{|e| e.text}
            day_forecasts[i][:rain_chance] = rain_chance_str.collect{|str| str =~ /^\d+%/ ? str.gsub('%','').to_i : nil} if rain_chance_str.count == 4

            city_arr = temp_info_arr[i].search("td[@class='city']").collect{|e| e.text}
            min_arr = temp_info_arr[i].search("td[@class='min']").collect{|e| e.text =~ /^\d+度/ ? e.text.gsub('度','').to_i : nil}
            max_arr = temp_info_arr[i].search("td[@class='max']").collect{|e| e.text =~ /^\d+度/ ? e.text.gsub('度','').to_i : nil}
            day_forecasts[i][:temp_info] = [] if city_arr.count > 0
            for j in 0...city_arr.count
                day_forecasts[i][:temp_info] += [{:city => city_arr[j], :min => min_arr[j], :max => max_arr[j]}]
            end
        end

        @forecasts = {}
        th_arr = forecast.search("th").collect{|e| e.text.strip}.reject(&:empty?)
        area_counter = -1
        day_counter = -1
        th_arr.each do |th|
            if area_name_array[area_counter + 1] == th
                area_counter += 1
                @forecasts[area_name_array[area_counter]] = []
            elsif day_forecasts[day_counter + 1][:day_name] == th
                day_counter += 1
                @forecasts[area_name_array[area_counter]] += [day_forecasts[day_counter]]
            end
        end
    end
end

Class Forecastsにそのページの天気予報が全部入る。idにはidが、nameには府県名が入る。なおidは http://www.jma.go.jp/jp/yoho/319.html であれば 19 がこれにあたる。

実際に見たいときはこれをロードして、

require 'pp'

f = Forecasts.new(52)
puts f.name
pp f.forecasts

などと実行すればよい。idを指定すると、Hashで作られた天気予報forecastsがClassに入って返ってくる。実行結果は

鹿児島県
{"薩摩地方"=>
  [{:day_name=>"今夜9日",
    :day=>9,
    :summary=>"晴れ",
    :info=>"北の風 晴れ 所により 夜のはじめ頃 雨 で 雷を伴い 非常に 激しく 降る",
    :wave=>"波 1.5メートル うねり を伴う",
    :rain_chance=>[nil, nil, nil, 20]},
   {:day_name=>"明日10日",
    :day=>10,
    :summary=>"晴れ",
    :info=>"北の風 後 北西の風 晴れ",
    :wave=>"波 1.5メートル うねり を伴う",
    :rain_chance=>[0, 0, 10, 10],
    :temp_info=>
     [{:city=>"鹿児島", :min=>26, :max=>35},
      {:city=>"阿久根", :min=>25, :max=>31},
      {:city=>"枕崎", :min=>25, :max=>34}]},
   {:day_name=>"明後日11日",
    :day=>11,
    :summary=>"晴れ時々曇り",
    :info=>"北西の風 晴れ 時々 くもり",
    :wave=>"波 1.5メートル"}],
 "大隅地方"=>
  [{:day_name=>"今夜9日",
    :day=>9,
    :summary=>"晴れ",
    :info=>"北西の風 晴れ 曽於 では 夜のはじめ頃 雨 で 雷を伴う",
    :wave=>"波 1.5メートル うねり を伴う",
    :rain_chance=>[nil, nil, nil, 20]},
   {:day_name=>"明日10日",
    :day=>10,
    :summary=>"晴れ",
    :info=>"北西の風 後 西の風 晴れ 夜 くもり",
    :wave=>"波 1.5メートル 後 2メートル うねり を伴う",
    :rain_chance=>[0, 0, 10, 10],
    :temp_info=>[{:city=>"鹿屋", :min=>24, :max=>35}]},
   {:day_name=>"明後日11日",
    :day=>11,
    :summary=>"晴れ時々曇り",
    :info=>"北西の風 晴れ 時々 くもり",
    :wave=>"波 2メートル 後 1.5メートル うねり を伴う"}],
 "種子島地方・屋久島地方"=>
  [{:day_name=>"今夜9日",
    :day=>9,
    :summary=>"晴れ",
    :info=>"北の風 晴れ",
    :wave=>"波 1.5メートル うねり を伴う",
    :rain_chance=>[nil, nil, nil, 0]},
   {:day_name=>"明日10日",
    :day=>10,
    :summary=>"晴れ",
    :info=>"北西の風 後 西の風 晴れ",
    :wave=>"波 1.5メートル 後 2メートル うねり を伴う",
    :rain_chance=>[0, 0, 0, 0],
    :temp_info=>[{:city=>"種子島", :min=>26, :max=>33}]},
   {:day_name=>"明後日11日",
    :day=>11,
    :summary=>"晴れ時々曇り",
    :info=>"北西の風 晴れ 時々 くもり",
    :wave=>"波 2メートル 後 1.5メートル うねり を伴う"}],
 "奄美地方"=>
  [{:day_name=>"今夜9日",
    :day=>9,
    :summary=>"晴れ",
    :info=>"北東の風 後 北の風 晴れ",
    :wave=>"波 2メートル うねり を伴う",
    :rain_chance=>[nil, nil, nil, 10]},
   {:day_name=>"明日10日",
    :day=>10,
    :summary=>"晴れ",
    :info=>"北の風 後 北西の風 晴れ",
    :wave=>"波 2メートル うねり を伴う",
    :rain_chance=>[0, 0, 10, 10],
    :temp_info=>
     [{:city=>"名瀬", :min=>25, :max=>33}, {:city=>"沖永良部", :min=>26, :max=>32}]},
   {:day_name=>"明後日11日",
    :day=>11,
    :summary=>"晴れ時々曇り",
    :info=>"南西の風 晴れ 時々 くもり",
    :wave=>"波 2メートル 後 1.5メートル うねり を伴う"}]}

などとなる。

シャロちゃんは別途この :info をさらに構文解析して口語に変換するのだが、それはまたいつか。


気象庁の府県天気予報を解析する」への1件のフィードバック

  1. ピンバック: 気象予報をシャロちゃんに言わせたい! (eeic Advent Calendar 2015) | 愛のらくがき帳

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です