PerlでWebから情報を持ってくるときのツール
まず注意
昨今のWebではhttps化が進んでいます。https の s はSecureの s です。安全な通信をするためにデータを暗号化/復号するのですが、そのためのモジュールが別途必要です。
運が良ければ、すでにインストールされているかも・・・
Perl入学式で構築した手元のMsys32(Windows7上に構築)だと
のちに出てくる
HTTP::Tiny
のサンプルスクリプトを試したところ、httpのサイト(例:http://www.hatena.ne.jp/)はアクセスできるが、httpsのサイト(例:https://www.yahoo.co.jp)にPerlのスクリプトからアクセスできず。エラーメッセージによると、
Net::SSLeay
が無いとのこと。cpanm Net::SSLeay
- インストール失敗
- エラーメッセージによると、MSYS側にSSL環境がなさそう
pacman -Syu
- MSYS側の更新がなくなるまで複数回実行&再起動
- エラー変わらず
pacman -S openssl
- MSYSのopensslのインストールはできたが、
cpanm Net::SSLeay
は失敗
pacman -S perl-Net-SSLeay
cpanm IO::Socket::SSL
- 大文字小文字を厳密に見てくるので注意
- こっちはなんか時間切れとか出てくる。きー!
pacman -S perl-IO-Socket-SSL
- インストールできた
- うごいた!
こんな感じでした。ここまでが長い。でも、動いてしまえば「許そう、全てを・・・」って気分になりませんか。うちはなります。
HTTPクライアント編
HTTPクライアントというのは、Chromeとか、Firefoxとかそういったブラウザです。HTTPサーバーにアクセスし、情報を得るものです。
プログラムからもHTTPサーバにアクセスして情報を得ることができます。
以下はPerlでのHTTPクライアントの紹介です。
HTML::Tiny
Perl 5.13.9以降であればコアモジュールとしてPerl本体と一緒にインストールされるモジュール。なので、普通にPerlをインストールすれば大概の環境では使えるはず。
$ corelist HTTP::Tiny Data for 2018-11-29 HTTP::Tiny was first released with perl v5.13.9
HTTP::Tiny のページにあるサンプルをPerl入学式にある範囲で書き直したものです。あと、対象を日本のYahoo Japanに変えています。
$response->{content}
とか、リファレンス回をやった人だと見覚えがあるのではないでしょうか。ハッシュリファレンスですね。
use strict; use warnings; use HTTP::Tiny; # Yahooのurlを変数に入れる my $yahoo_url = 'http://www.yahoo.co.jp'; # HTTP::TinyでYahooにアクセスして、getでデータを持ってくる my $response = HTTP::Tiny->new->get($yahoo_url); # データを持ってくるのに成功したかどうかを判別 if ( $response->{success} ) { # 成功してたら、HTTPのステータスコードを表示。200ならok print "$response->{status} $response->{reason}\n"; } # 失敗してたら、プログラム終了 else { print "Failed!\n"; exit(); } print $response->{content}; # ヤフーのHTMLが表示される
LWP::Simple
ここからは cpanm でのインストールが必要なモジュール。LWP というのは ibwww-perl の略称で、PerlをつかってWebにアクセスするときの基本的なモジュールです。その機能最小版です。
use strict; use warnings; use LWP::Simple; # Yahooのurlを変数に入れる my $yahoo_url = 'http://www.yahoo.co.jp'; # getでデータを持ってくる my $content = get($yahoo_url); # データ持ってくるのに成功したら表示する if ($content) { print $content; # ヤフーのHTMLが表示される }
HTTP::Tinyと大体同じですね。うちのコメントも早くも簡素になってきました。
LWP
上記 LWP::Simple の大元である、HTTPアクセスに必要なことの全てが詰まったフルセット版です。 ユーザーエージェントの変更やPOSTでのデータ送信など、HTTP通信を使った機能が詰まっています。
use strict; use warnings; use LWP::UserAgent; # Yahooのurlを変数に入れる my $yahoo_url = 'http://www.yahoo.co.jp'; # LWP::UserAgentのインスタンスを生成する my $ua = LWP::UserAgent->new(); # HTTP::Requestのインスタンスを生成するついでに、GETでアクセスしに行くページも設定する my $req = HTTP::Request->new( GET => $yahoo_url ); # uaがGETでyahooにデータを取りに行く my $res = $ua->request($req); # ページの取得に成功してたら内容を表示する if ( $res->is_success ) { print $res->content; # ヤフーのHTMLが表示される }
Furl
tokuhiromさん作成の「早い」ライブラリ。
Furlはもう一つのHTTPクライアントライブラリです。LWPはPerl5のデファクトスタンダードな HTTPクライアントですが、クリティカルなジョブでは遅すぎますし、週末のハッキングには 複雑過ぎます。Furlはこれらの問題を解決します。楽しんで下さい!
use strict; use warnings; use Furl; # Yahooのurlを変数に入れる my $yahoo_url = 'http://www.yahoo.co.jp'; # Furlのインスタンスを生成するついでに、GETでアクセスしに行くページも設定する my $res = Furl->new->get($yahoo_url); # ページの取得に成功してたら内容を表示する if ( $res->is_success ) { print $res->content; }
Webスクレイピング
ここまで紹介したものは、Webサーバーにアクセスして、そこから情報を取得するものでした。しかし、ほとんどの用途では、Webの情報が全て必要なわけではなく、ごく一部のみ必要だったりします。
そういった情報を都度、正規表現などを使って取り出してもいいですが、もっと楽はできないものでしょうか?そんなプログラマの三大美徳「怠惰」を実現させるツール、ありそうですよね。
Webサーバから返ってきたデータのうち、必要な部分だけ取り出す、こそげとる、それがスクレイピングと言われるツールです。
ここからはPerlのスクレイピングモジュールを使ってYahooニュースからトピック一覧だけ取り出してみます。
Web::Scraper
うちはこのツールをPerl CPANモジュールガイド という本で知って、ほぼこれをメインに使っています。要素の指定にXPathとCSSセレクタを利用することができます。
この本には他にもPerlのモジュールがたくさん載っているので、やや古いとはいえオススメです。
use strict; use warnings; # 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく binmode STDOUT, ':encoding(UTF-8)'; use Web::Scraper; use URI; my $yahoo_news_url = 'https://news.yahoo.co.jp/topics'; my $uri = URI->new($yahoo_news_url); # どの要素を取得するかを設定する変数 my $scraper = scraper { # タイトルを取得するスクレイパー # Xpath形式で指定 process '/html/head/title', 'page_title' => 'TEXT'; # ニュースタイトル一覧を取得するスクレイパー # CSSセレクタ形式で指定 process 'li.topicsListItem > a ', 'news_titles[]' => 'TEXT'; }; # ここでスクレイピングが実行される my $res = $scraper->scrape($uri); # ここから結果表示 print $res->{page_title} . "\n"; # タイトルの表示 for my $title ( @{ $res->{news_titles} } ) { print $title . "\n"; # ニュースのタイトルの表示 }
Web::Query
Web::Query is a yet another scraping framework, have a jQuery like interface.
Javascriptのライブラリ、JQueryに似た方法で要素を指定してスクレイピンすることができるモジュールです。実はJQuery使ったことないので、わからない・・・
でも、直感的に要素を指定できる感じで良いですね。
use strict; use warnings; # 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく binmode STDOUT, ':encoding(UTF-8)'; use Web::Query; my $yahoo_news_url = 'https://news.yahoo.co.jp/topics'; # topicsListItem 属性をもつ li タグの下にある a タグを持ってきて、 # a タグのテキスト要素を表示する wq($yahoo_news_url)->find('li.topicsListItem > a')->each( sub { print $_->text . "\n"; } );
HTML::TreeBuilder
スクレイピングツールのくくりに入れましたが、HTML::TreeBuilderはHTMLをパース(解析)するためのツールです。
正確なHTMLは<html>
の中に<head>
と<body>
タグがあり、その中にさらに複数のタグが配置されています。
これを、<html>
という太い幹から<head>
と<body>
という枝が生え、さらにその枝から別の枝が生え・・・という木構造で解析していきます。
本来は「特定の枝の先にだけ、何らかの処理を行う」ということをしたかったのですが、ちょっと今の自分ではわかりませんでしたわ・・・
use strict; use warnings; # 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく binmode STDOUT, ':encoding(UTF-8)'; use HTML::TreeBuilder; my $yahoo_url = 'https://news.yahoo.co.jp/topics'; # new_from_urlメソッドは裏でLWP::UserAgent使っているので、 # LWP入れてないと動かない my $tree = HTML::TreeBuilder->new_from_url($yahoo_url); $tree->eof(); foreach $a ( $tree->find("a") ) { # news の a タグには data-ual-gotocontent って属性が付与されてた # ところで、ual って何だろう・・・urlならわかるんだけど my $data_ual_gotocontent = $a->attr('data-ual-gotocontent') || next; if ( $data_ual_gotocontent eq 'true' ) { print $a->as_text . "\n"; } }
閑話休題
WWW::Mechanize
一部の人には刺さるかもしれません。私も刺さった人です。
今まで紹介したものは、Webサーバから情報を持ってくるものと、Webページから特定のデータを抜き出す(スクレイピングする)ものでした。
ここで紹介する WWW::Mechanize
、通称Mech は、Webサーバからデータを受け取り、そのあとにリンクをたどったり、入力欄に文字を入れてボタンを押す、なんてことができます。
以下は、Yahooの検索ページの入力欄に「Perl入学式」という文字を入力して、その検索結果を得るスクリプトです。
use strict; use warnings; use utf8; # 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく binmode STDOUT, ':encoding(UTF-8)'; use WWW::Mechanize; # Yahooの検索ページのURL my $yahoo_search_url = 'https://search.yahoo.co.jp/'; # WWW::Mechanizeのインスタンスを生成する my $mech = WWW::Mechanize->new(); # Yahooの検索ページのHTMLをGETで取得する $mech->get($yahoo_search_url); # 取得したHTMLを解析し、フォームの入力欄に文字を入れて、 # そのフォームのボタン(検索ボタン)を押す $mech->submit_form( # 最初のフォーム form_number => 0, # name属性が p の入力欄に 検索対象の文字を入れる fields => { p => 'Perl入学式' }, ); # 結果を表示する print $mech->{content};
どうでしょう、ずらっとHTMLが表示されて、その中にかすかに検索結果らしきものが見えますね・・・これを整形したい・・・と思いませんか?
うちは思います。
ということで、ここで、先に登場したスクレイピングツールを使います。WWW::MechanizeでYahoo検索した結果から、タイトルとリンクをWeb::Scraperで取り出してみます。
use strict; use warnings; use utf8; # 日本語をターミナルで表示(出力)するときに警告が出ないよう書いておく binmode STDOUT, ':encoding(UTF-8)'; use WWW::Mechanize; my $yahoo_search_url = 'https://search.yahoo.co.jp/'; my $mech = WWW::Mechanize->new(); $mech->get($yahoo_search_url); $mech->submit_form( form_number => 0, fields => { p => 'Perl入学式' }, ); my $mech_result = $mech->{content}; # -----ここまでが WWW::Mechanize の部分 # -----ここからは Web::Scraper の部分 use Web::Scraper; my $scraper = scraper { # 検索結果を取得するスクレイパーを用意しているところ # Xpath形式で指定 //と書くことで前方の要素を省略する # ここでは、 # id が web 要素のdivタグを撮ってきて、その下(直下とは限らない)にある liタグを持ってくる # という内容 process '//div[@id="web"]//li', 'search_result[]' => scraper { # さらに、その取得条件からタイトルとリンクを分けて取得し、 # ハッシュで格納する process '//a', 'title' => 'TEXT'; process '//a', 'url' => '@href'; }; }; # ここでスクレイピングが実行される # スクレイプ対象にはmechで検索した後のページを入れる my $res = $scraper->scrape( $mech_result ); # ここから結果表示 for my $result ( @{ $res->{search_result} } ) { print '-------' . "\n"; print $result->{title} . "\n"; print $result->{url} . "\n"; print '-------' . "\n"; }
Web API
さっきので終わらせようと思ったんだけど、ふと、昨今のWebからのデータ取得をいうならWeb APIは外せないのでは?と思い至りました。遅い。
Web APIは簡単にいうと、プログラムが使いやすいように整形されたデータのやり取り、というところでしょうか。詳細はググったり調べたりでお願いします。
もちろん、上記のHTTP::TinyとJSON::PPモジュールを利用することでWeb APIで提供されているデータの取得と利用が容易になります。どちらもPerlのコアモジュールです(5.13.9 以降)。
ここでは、無料で利用できるAPIとして、Livedoorの天気予報APIを利用します。
地域は東京地方、今日(2019年8月4日)の最高気温を調べてみます。
HTTP::Tiny + JSON::PP
use strict; use warnings; use HTTP::Tiny; # Livedoor天気APIのURLを変数に入れる my $livedoor_weather_url = 'http://weather.livedoor.com/forecast/webservice/json/v1?city=130010'; # HTTP::TinyでAPIにアクセスして、getでデータを持ってくる my $response = HTTP::Tiny->new->get($livedoor_weather_url); # データを持ってくるのに成功したかどうかを判別 if ( $response->{success} ) { # 成功してたら、HTTPのステータスコードを表示。200ならok print "$response->{status} $response->{reason}\n"; } # 失敗してたら、プログラム終了 else { print "Failed!\n"; exit(); } # 取得したデータはJSON形式なので、Perlで操作しやすいように変換する。 use JSON::PP; my $decoded_response = decode_json($response->{content}); # どんなデータ構造になっているかわからなくなったら、Data::Dumperを使おう! # use Data::Dumper; # print Dumper $decoded_response; # 最高気温 print $decoded_response->{forecasts}->[0]->{temperature}->{max}->{celsius}; # 34
34度!ヤダもう・・・
ところで、my $livedoor_weather_url = 'http://weather.livedoor.com/forecast/webservice/json/v1?city=130010';
の末尾の数字を 270000
に変えることで、大阪の最高気温を出したりすることができます。
ここを変数にすると、他の地域の気温を見たいときに楽ですね。
さらに、その数字を標準入力から入れられると楽かも。
さらにさらに、Webアプリから地域を選べるようにすれば・・・となります。
そんな、Webアプリからの入出力を学べる Perl入学式 2019 第5回 in東京 お待ちしております!
perl-entrance-tokyo.connpass.com
ojo
Perl入学式 in沖縄の id:AnaTofuZ さんから教えてもらったのがこのツール。
cpanm ojo
でインストールして、コマンドラインから1行、ワンライナーです。
$ perl -Mojo -E'my $content = j g("http://weather.livedoor.com/forecast/webservice/json/v1?city=130010")->{content}->{asset}->{content};say $content->{forecasts}->[0]->{temperature}->{max}->{celsius}'
ということで
PerlでWebにアクセスしてデータ集める方法でした。