Google API から天気の取得

Weather on the Command line という元ネタにインスパイヤされているわけですが、本来であれば、xgawk の Socket 通信と XML パースの機能で作るのですが、awk ユーザーの中には gawk 以降の新機能というよりも awk の機能の中で行うにはどうすれば良いかということに興味を持たれている方が多くいらっしゃるので、nawk を使って作ってみます。 もっとも、中身は getline での取得値を使う関係上、上記 URL のような「一行野郎」の方が便利です。

さて、通常の awk で XML をパースするのは非常に難しいですが、xml2 という便利なツールがありますので、これを用います。

例えば、今回取得する Google API から取得することができる XML は以下のようなものです。

$ wget -q -O - 'http://www.google.com/ig/api?weather=Tokyo'
<?xml version="1.0"?><xml_api_reply version="1"><weather module_id="0" tab_id=\
"0" mobile_row="0" mobile_zipped="1" row="0" section="0" ><forecast_informatio\
n><city data="Tokyo"/><postal_code data="Tokyo"/><latitude_e6 data=""/><longit\
ude_e6 data=""/><forecast_date data="2010-02-13"/><current_date_time data="201\
0-02-13 19:00:00 +0000"/><unit_system data="US"/></forecast_information><curre\
nt_conditions><condition data="Cloudy"/><temp_f data="35"/><temp_c data="2"/><\
humidity data="Humidity: 82%"/><icon data="/ig/images/weather/cloudy.gif"/><wi\
nd_condition data="Wind: NE at 1 mph"/></current_conditions><forecast_conditio\
ns><day_of_week data="Sat"/><low data=""/><high data=""/><icon data="/ig/image\
s/weather/chance_of_snow.gif"/><condition data="Chance of Snow"/></forecast_co\
nditions><forecast_conditions><day_of_week data="Sun"/><low data="32"/><high d\
ata="48"/><icon data="/ig/images/weather/mostly_sunny.gif"/><condition data="P\
artly Sunny"/></forecast_conditions><forecast_conditions><day_of_week data="Mo\
n"/><low data="35"/><high data="44"/><icon data="/ig/images/weather/chance_of_\
rain.gif"/><condition data="Chance of Rain"/></forecast_conditions><forecast_c\
onditions><day_of_week data="Tue"/><low data="37"/><high data="50"/><icon data\
="/ig/images/weather/mostly_sunny.gif"/><condition data="Partly Sunny"/></fore\
cast_conditions></weather></xml_api_reply>

つまり、改行がなく、awk で扱い辛いデータであることが分かります。 これを xml2 を通すと以下のようになります。

$ wget -q -O - 'http://www.google.com/ig/api?weather=Tokyo' | xml2
/xml_api_reply/@version=1
/xml_api_reply/weather/@module_id=0
/xml_api_reply/weather/@tab_id=0
/xml_api_reply/weather/@mobile_row=0
/xml_api_reply/weather/@mobile_zipped=1
/xml_api_reply/weather/@row=0
/xml_api_reply/weather/@section=0
/xml_api_reply/weather/forecast_information/city/@data=Tokyo
/xml_api_reply/weather/forecast_information/postal_code/@data=Tokyo
/xml_api_reply/weather/forecast_information/latitude_e6/@data
/xml_api_reply/weather/forecast_information/longitude_e6/@data
/xml_api_reply/weather/forecast_information/forecast_date/@data=2010-02-13
/xml_api_reply/weather/forecast_information/current_date_time/@data=2010-02-13 19:00:00 +0000
/xml_api_reply/weather/forecast_information/unit_system/@data=US
/xml_api_reply/weather/current_conditions/condition/@data=Cloudy
/xml_api_reply/weather/current_conditions/temp_f/@data=35
/xml_api_reply/weather/current_conditions/temp_c/@data=2
/xml_api_reply/weather/current_conditions/humidity/@data=Humidity: 82%
/xml_api_reply/weather/current_conditions/icon/@data=/ig/images/weather/cloudy.gif
/xml_api_reply/weather/current_conditions/wind_condition/@data=Wind: NE at 1 mph
/xml_api_reply/weather/forecast_conditions/day_of_week/@data=Sat
/xml_api_reply/weather/forecast_conditions/low/@data
/xml_api_reply/weather/forecast_conditions/high/@data
/xml_api_reply/weather/forecast_conditions/icon/@data=/ig/images/weather/chance_of_snow.gif
/xml_api_reply/weather/forecast_conditions/condition/@data=Chance of Snow
/xml_api_reply/weather/forecast_conditions
/xml_api_reply/weather/forecast_conditions/day_of_week/@data=Sun
/xml_api_reply/weather/forecast_conditions/low/@data=32
/xml_api_reply/weather/forecast_conditions/high/@data=48
/xml_api_reply/weather/forecast_conditions/icon/@data=/ig/images/weather/mostly_sunny.gif
/xml_api_reply/weather/forecast_conditions/condition/@data=Partly Sunny
/xml_api_reply/weather/forecast_conditions
/xml_api_reply/weather/forecast_conditions/day_of_week/@data=Mon
/xml_api_reply/weather/forecast_conditions/low/@data=35
/xml_api_reply/weather/forecast_conditions/high/@data=44
/xml_api_reply/weather/forecast_conditions/icon/@data=/ig/images/weather/chance_of_rain.gif
/xml_api_reply/weather/forecast_conditions/condition/@data=Chance of Rain
/xml_api_reply/weather/forecast_conditions
/xml_api_reply/weather/forecast_conditions/day_of_week/@data=Tue
/xml_api_reply/weather/forecast_conditions/low/@data=37
/xml_api_reply/weather/forecast_conditions/high/@data=50
/xml_api_reply/weather/forecast_conditions/icon/@data=/ig/images/weather/mostly_sunny.gif
/xml_api_reply/weather/forecast_conditions/condition/@data=Partly Sunny

ちょうど XML の node がディレクトリの階層構造のようになっており、これなら awk で扱いやすそうです。

awk は Perl や Ruby のように何でもできる言語ではありません。 それは awk が生まれた Unix という環境には補うためのコマンドがあるからです。

さて、今回のコードは以下のようになります。

#! /usr/local/bin/nawk -f
# weather.awk
# Google API を使って天気の情報を取得します
# usage: nawk -f weather.awk location

BEGIN {
    location = ARGV[1];

    print get_google_weather(location, "Condition");
    print get_google_weather(location, "Temperature");
    print get_google_weather(location, "Humidity");
    print get_google_weather(location, "Wind");
    print get_google_weather(location, "Icon");
}

function get_google_weather(location, what,
            google_url, google_api_url, url,
            wget_cmd, wget_opt, wget_exe, xml2_cmd, xml2_opt, xml2_exe,
            get_weather_exe) {

    # 何を取得したいかの情報がない場合
    if (what == "") {
        print "I can not understand what you want." > "/dev/stderr";
        exit 1;
    }

    # Google API の URL
    google_url      = "http://www.google.com/";
    google_api_url  = "ig/api?weather=";
    url             = google_url google_api_url;

    # 情報を取得するためのコマンド
    wget_cmd        = "/usr/bin/wget";
    wget_opt        = "-q -O -";
    wget_exe        = wget_cmd " " wget_opt " " url location;
    xml2_cmd        = "/usr/bin/xml2"
    xml2_opt        = "";
    xml2_exe        = xml2_cmd " " xml2_opt;
    get_weather_exe = wget_exe "|" xml2_exe;

    FS = "=";
    while ((get_weather_exe | getline) > 0) {
        # 現在の天気のみ対象
        if ($0 ~ /current_conditions/) {
            # 天気
            if (what == "Condition" && $0 ~ /\/condition\//) {
                close(get_weather_exe);
                return $2;
            }
            # 気温 (摂氏)
            if (what == "Temperature" && $0 ~ /\/temp_c\//) {
                close(get_weather_exe);
                return $2;
            }
            # 湿度
            if (what == "Humidity" && $0 ~ /\/humidity\//) {
                gsub(/[^0-9]/, "", $2);
                close(get_weather_exe);
                return $2;
            }
            # 風
            if (what == "Wind" && $0 ~ /\/wind_condition\//) {
                sub(/^Wind: /, "", $2);
                close(get_weather_exe);
                return $2;
            }
            # アイコン
            if (what == "Icon" && $0 ~ /\/icon\//) {
                sub(/\/$/, "", google_url);
                close(get_weather_exe);
                return google_url $2;
            }
        }
    }
    close(get_weather_exe);

    print "I can not know what to do for you." > "/dev/stderr";
    exit 1;
}

実行してみましょう。

$ nawk -f weather.awk Yokohama
Cloudy
2
82
N at 6 mph
http://www.google.com/ig/images/weather/cloudy.gif

DoukakuAWK_263.png

これだけ取得できると GNU screen の backtick などでステータスラインに表示したりすることも簡単そうですが、せっかくアイコンを取得しているので、ここでは Gnome の Notifier に表示させてみます。

以下のような notifier.py という Python ファイルを実行権限付きで作成しておきます。

#!/usr/bin/python

from pynotify import *
import sys

init("cli notify")
n = Notification(sys.argv[1], sys.argv[2], sys.argv[3])
n.show()

先ほどの awk スクリプトの BEGIN ブロックを以下のようにします。

BEGIN {
    location = ARGV[1];

    title = location "の天気";
    str   = get_google_weather(location, "Condition") "\n"\
            get_google_weather(location, "Temperature") " C / " \
            get_google_weather(location, "Humidity") " %\n"\
            get_google_weather(location, "Wind");
    icon  = get_google_weather(location, "Icon");

    system("wget -q -O weather.gif " icon);
    system("notifier.py '" title "' '" str "' 'weather.gif'");
}

これだけで Gnome の Notifier に表示させることができます。

tag_nawk.png tag_nawk.png tag_nawk.png tag_nawk.png