Quantcast
Channel: Vimタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 5608

Vimで天気予報

$
0
0

こんにちは
ゴリラ.vimを運営しているゴリラです。

以前Vimで電車乗り換えルート検索するプラグインを作りましたが、
今度はVimでコーディング中に天気を知りたくなるときがあるので天気情報プラグインを作りました。

何ができるの?

地域の天気情報(今日、明日)を表示する。

使い方

  1. :Weatherコマンドを実行して天気情報を知りたい地域名もしくは市名を入力する。
  2. 対象地域が見つかればウィンドウに表示されるのでj or kで選択して Enterで確定できる。キャンセルしたいときは xでウィンドウを閉じる。
  3. 日にちを選択して確定すると天気情報が表示される。

仕組み

本プラグインは以前作った乗換案内プラグインと同じ様にHTMLをパースして、分析しています。
HTTP通信とパースに関してはvital.vimWeb.HTMLモジュールを使用しています。このモジュールは裏ではcurl or wget or pythonが動いています。

プラグインの処理を大きく分けて3ステップがあります。
1. 地域一覧を検索して地域名とURLを取得する
2. 選択した地域のURLから天気情報を実際表示している画面のDOMを取得する
3. 日付を選択した後に、選択肢に応じてDOMをパースして画面を表示する

では、それぞれソースをベースに説明します。

地域一覧を取得

まずWeatherコマンドで呼ぶ関数を用意します。
input()関数を用意するとコマンドラインで入力した値がl:locationに代入されます。
weather#get_city()で地域一覧情報を取得します。

ポップアップウィンドウの選択インターフェイスはfilterオプションに 標準提供されているpopup_filter_menuを使うといい感じになります。

function! weather#weather() abort
    letl:location = input("地域名:")redraw!
    echo ""letl:url ="https://weather.yahoo.co.jp/weather/search/?p=".l:location
    letl:city_list = weather#get_city(l:url)if len(l:city_list)==# 0
        echo "地域が見つかりません"returnendifletl:list =[]for city inl:city_list
        call add(l:list, city.name)endforcall popup_clear()call popup_menu(l:list,{                \"filter":"popup_filter_menu",                \"callback":function("weather#choise_city",[l:city_list]),                \"borderchars":['-','|','-','|','+','+','+','+'],                \"title":"地域の選択",                \})endfunction

続いて、地域情報一覧を取得します。
コメントに書いてあるとおりDOMを整形して、表示に使用する地域名とURLを返す関数を用意します。

" 地域毎名と天気情報URLを取得する" ["   {'name': '埼玉県さいたま市大宮区', 'url': 'https://weather.yahoo.co.jp/weather/11/4310/11103.html'},"   {'name': '埼玉県さいたま市浦和区', 'url': 'https://weather.yahoo.co.jp/weather/11/4310/11107.html'},"   ..." ]function! weather#get_city(url) abort
    " 例外が発生することがあるので、例外処理を行うtryletl:response =s:HTML.parseURL(a:url)catch"/.*/"
        echo v:exception
        return[]endtryletl:record =[]" DOM全体から`table` タグの要素を取得するfor item inl:response.findAll("table")if has_key(item.attr,"class")" `yjw_table3` というクラスがあればテーブル内のデータを取得するif item.attr.class ==# "yjw_table3"for td in item.findAll("td")if type(td)!=# type({})&& len(td.child)==# 0
                        continue
                    endiflet child = td.child[0]if type(child)!=# type({})
                        continue
                    endif" テーブル内のデータを1行ずつdictionaryを作って配列に追加するcall add(l:record,{"name": child.value(),"url":"https:". child.attr.href})endforendifendifbreakendforreturnl:record
endfunction

やっていることはシンプルですね。
HTMLモジュールを使用するとDOMのオブジェクトを取得でき、findAll()で要素を取得できます。value()で要素内の文字データを取得できます。 childは小要素になります。

このような感じで割と力技ですが、スクレイピングはこんな感じかなぁと思ったりします。

日付を選択して天気情報のDOMから情報を取得する

続いて、取得してデータをポップアップウィンドウで表示させる処理が必要なので、次のように書きました。

function! weather#choise_city(city_list, id, idx) abort
    ifa:idx==# -1returnendifletl:city =a:city_list[a:idx-1]call popup_menu(["今日","明日"],{                \"filter":"popup_filter_menu",                \"callback":function("s:choise_day",[l:city]),                \'borderchars':['-','|','-','|','+','+','+','+'],                \"title":"日付の選択"                \})endfunction

callbackで指定する関数はポップアップウィンドウが閉じられたときに実行される関数です。名前のとおりですね。

s:choise_dayの処理は下になります。第1引数はweather#choise_cityで渡した [l:city]になります。idはポップアップウィンドウのIDで、idxは選択したメニューの順番(1から)になります。

function!s:choise_day(city, id, idx) abort
    ifa:idx==# -1returnendiftryletl:dom =s:HTML.parseURL(a:city.url)catch"/.*/"
        echo v:exception
        returnendtryletl:data =s:get_weather(l:dom,a:idx)if empty(l:data)
        echo "データがありません"returnendifletl:title =l:data.title
    letl:rows =l:data.rows

    letl:table =s:T.new({                \"columns":[{},{},{},{},{},{},{},{},{}],                \"header":l:rows[0],                \})calll:table.rows(l:rows[1:])call popup_create(l:table.stringify(),{                \"title":l:title,                \"moved":"any",                \})endfunction

日付を選択した後に、選択肢に応じてDOMをパースして画面を表示する

日にちを選択すると s:shoise_dayが呼ばれ選択した地域の天気情報を s:get_weatherでDOMを整形したデータを取得します。
こちらも同様にテーブルのデータを取得して整形して返しています。

" DOMから今日の天気情報を取得" {"     title: '今日の天気 - 6月22日(土)',"     rows: ["         [時刻,0時,3時,6時,9時,12時,15時,18時,21時],"         [天気,曇り,曇り,雨,雨,曇り,晴れ,曇り曇り], "         [気温(C),24,23,23,24,27,28,26,24],"         [湿度(%),77,80,83,78,58,53,66,72],"         [降水量(mm),0,0,6,0,3,0,0,0]"     ]" }function!s:get_weather(dom, day_kind) abort
    letl:day ="yjw_pinpoint_today"ifa:day_kind==# 2letl:day ="yjw_pinpoint_tomorrow"endifforl:item ina:dom.findAll("div")if has_key(l:item.attr,"id")ifl:item.attr.id ==# l:day
                " titleletl:h3 = item.find("h3")if type(l:h3)!=# type({})&& len(l:h3.child)==# 0return{}endifletl:h3 =l:h3.child

                letl:title = trim(l:h3[0],"\n").l:h3[1].value()" table dataletl:table = item.findAll("tr")letl:rows =[]forl:row inl:table
                    letl:columns =[]forl:column inl:row.findAll("td")call add(l:columns,l:column.value())endforcall add(l:rows,l:columns)endforreturn{"title":l:title,"rows":l:rows}endifendifendforendfunction

最後に

vital.vimはとっても便利です。テーブルのを作成する時もvital.vimを使用していますがこれもとっても便利です。
そしてポップアップウィンドウもとっても便利です。5/25に入ってからどんどん機能が完成していっていい感じになってきています。

興味ある方はぜひポップアップウィンドウも触ってみてください〜


Viewing all articles
Browse latest Browse all 5608

Trending Articles