前回に続き #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 をさらに構文解析して口語に変換するのだが、それはまたいつか。
ピンバック: 気象予報をシャロちゃんに言わせたい! (eeic Advent Calendar 2015) | 愛のらくがき帳