sironekotoroの日記

Perl で楽をしたい

ハッシュリファレンス使うほどのデータ構造なんて作る事も使う事も無いよなーと思っていたけど、欲しいデータはいつだって複雑なデータ構造の中にあった

この記事はPerl入学式 Advent Calendar 2017 19日目の記事です。

qiita.com

こんにちは。アドベントカレンダーの空欄は埋めていきたい欲のsironekotoroです。

Perl入学式 in 東京では、講義終了後、希望者が集ってピザを食べながら雑談をする時間を設けています。

昨日(2018/02/03)、そこで興が乗って発表したスクリプトを2つほど紹介します。

テーマは、ハッシュリファレンス使うほどのデータ構造なんて作る事も使う事も無いよなーと思っていたけど、欲しいデータはいつだって複雑なデータ構造の中にあった、です。

名状し難い欲望に名前があるかも知れない

Webページから任意の情報を取得する際に使うのがスクレイピングツールです。

が、そのまえに昔話。

かつて「Webページから欲しい情報だけ抜き出すような、都合のいい技術ないかなー」と思っていました。

当時はそれを実現できる技術の名前を知らず、また聞ける人がいなかったので、検索しようがありませんでした。やりたいことに名前がついていることに気づけば、そこからの実現速度は驚異的に上がります。

自分が知らないことも、他の人なら知っているかもしれません。ってことで、Perl入学式ではサポーターに色々と質問してくださいね。

欲しい情報だけ取得したけど、その情報はハッシュリファレンスの中にいる

Perlスクレイピングツールというと、Web::Scraperを使うのが良いです。Perlを知れば知るほど、応用範囲が深く大きく広がる素晴らしいツールです。ここでは単純な例を示します・・・しかし!

取得した情報はハッシュリファレンスの中に格納されています!なんてこった!22行目にある$resに格納されているのは、スクレイピング結果を格納したハッシュリファレンスです。

my $res = $scraper->scrape($url);

37行目ではこの$res に格納されている配列リファレンス(記事一覧)情報を、Perl入学式第3回でやった「デリファレンス」で配列に戻してしています。

my @news_titles = @{ $res->{news_titles} };

このハッシュリファレンスの中に入っているスクレイピング結果が、単なる値(スカラー)なのか、配列リファレンスなのか、そこを意識して情報を扱っていきます。

つまり、Perl入学式 第三回を復習して理解すればハッシュリファレンスからデータを引き出すのも怖く無い!スクレイピングで欲しい情報がウホウホ!ということですね。

use strict;
use warnings;
use Web::Scraper;
use Encode;
use URI;

# URIモジュールで$uriに取得先のwebページのアドレスを格納。
my $url = URI->new('https://news.yahoo.co.jp/');

# webページの欲しい情報があるところをXPathで指定する。
# process 'XPath式', 'key名' => '取得形式(HTMLかTEXTか、など)'
# process 'XPath式', 'key名[]' => '取得形式(HTMLかTEXTか、など)'
# key名の後に[]をつけると、XPathで指定した要素を配列リファレンスとして取得する
my $scraper = scraper {
    process '//title', 'title' => 'TEXT';
    process '//div[@id="epTabTop"]//h1[@class="ttl"] | //p[@class="ttl"]',
        'news_titles[]' => 'TEXT';
};

# 上記で設定された情報に従い、対象のページから情報を「スクレイピング」する。
# スクレイピングした結果はハッシュリファレンス$resに格納される。
my $res = $scraper->scrape($url);

# $resから情報を引き上げる。
# $res->{key名}でアクセスする。
my $title = $res->{title};  # Yahoo!ニュース

# 取得した情報をMacで表示する。
# Windowsで表示する場合には以下に変更する。
# print encode('cp932' , $title), "\n";
print encode_utf8($title), "\n";

print "----------\n"; #区切り線

# ページのニュースタイトルを表示する
# news_title は配列リファレンスなので、デリファレンスして配列に戻す
my @news_titles = @{ $res->{news_titles} };
# 配列の要素をfor文で表示していく
foreach my $news_title (@news_titles) {
    print encode_utf8($news_title), "\n";
}

Web::Scraperについてはこのページが初心者向け。こちらのページの例ではAmazonのページからデータを取ってきてます。

use Web::Scraper; - 今日のCPANモジュール(跡地)

うまく動かなかったら、Web::Scraperがインストールされていないかも。となると、cpanm Web::Scraperをターミナルで実行して・・・え?cpanm入ってない?木本先生にお任せ!

cpanmによるPerlのローカル環境構築 - Perlゼミ(サンプルコードPerl入門)

WebAPIから帰ってくる情報は大体JSONで、それはやっぱり複雑なデータ

WebAPIというのは、HTMLのように人間が見やすい形ではなく、構造的なデータとして提供されている情報です。Amazonの商品情報APIなどが有名ですね。

人間に厳しいデータ表示形式の何が嬉しいか?というとプログラムが解釈しやすい形になっているのが嬉しいわけです。プログラムが解釈して、人間が欲しい情報だけを利用することができるわけです。

今回は、認証不要のAPIであるライブドア社の気象情報APIを利用してみます。

weather.livedoor.com

このAPIから返されるデータをPerlで取得し、千代田区の天気を表示してみます。

かつてはAPIが返すデータにも様々な種類がありましたが、現在は大体JSONという形式で提供することが多いようです。以下のリンクにブラウザからアクセスしてみてください。

http://weather.livedoor.com/forecast/webservice/json/v1?city=130010

もしかしたらブラウザが整形してくれたかもしれません。この、人間が見るにはちょっと厳しいデータをPerlの力を借りて構造化し、そこから今日の日付と天気、明日の日付と明日の天気予報を取得してみます。

18行目で取得したjsonのデータをperlのデータ構造にしています。

my $perlish_data = decode_json($json_data);

そして、例えば35行目のように、ハッシュリファレンス$perlish_dataから情報をスカラー変数$tomorrow_weatherに格納しています。

# ハッシュリファレンス内にある明日の天気
my $tomorrow_weather = $perlish_data->{forecasts}->[1]->{telop};

なお、プログラムの末尾にprint Dumper $perlish_data;コメントアウトして置いておきます。このコメントをとることで、データがどのような構造になっているかを把握することが可能です。

use strict;
use warnings;
use LWP::Simple qw/get/;
use JSON;
use Encode;
use Data::Dumper;

# ライブドア社の天気情報APIのURL;
# 末尾の番号は千代田区を指す
my $url
    = 'http://weather.livedoor.com/forecast/webservice/json/v1?city=130010';

# get関数でページにアクセスし、応答結果を$json_dataに格納する。
my $json_data = get($url);

# json_のデータをPerlのデータ形式に変換する。
# 変換結果はハッシュリファレンスになっている。
my $perlish_data = decode_json($json_data);

# ハッシュリファレンス内にある取得時の日付
my $today_date = $perlish_data->{forecasts}->[0]->{date};

# ハッシュリファレンス内にある取得時の天気
my $today_weather = $perlish_data->{forecasts}->[0]->{telop};


# ハッシュリファレンス内にある明日の日付
my $tomorrow_date = $perlish_data->{forecasts}->[1]->{date};

# ハッシュリファレンス内にある明日の天気
my $tomorrow_weather = $perlish_data->{forecasts}->[1]->{telop};

# 取得時の日付を表示
print $today_date, "\n";

# Macで表示する場合。Windowsの場合には
# print encode('utf8',$today_weather),  "\n";
print encode_utf8($today_weather),  "\n";


print "----------\n"; #区切り線


# 取得時の日付を表示
print $tomorrow_date, "\n";

# Macで表示する場合。Windowsの場合には
# print encode('utf8',$tomorrow_weather),  "\n";
print encode_utf8($tomorrow_weather),  "\n";

# ハッシュリファレンス内のハッシュリファレンスの配列リファレンスのハッシュの・・・とデータをたどっていく。
# わからなくなったら
# print Dumper $perlish_data;

複雑なデータ構造はDumperで覗き見!

というわけで、世の中の有用なデータは大概複雑な構造の中にあることが多いです(個人の感想

そして、そういう複雑なデータで訳がわからなくなったら、Dumperで覗き見するのが一番です。

Webやいろんなデータ構造から欲しいデータを見つけ、うまいこと利用していきましょう!