前回の
Perlのコーディング規約
いざ学んだPerlをアウトプットしようとしたときには、
perlstyleでコーディングスタイルを参考にする
Perlのコードをどのように書くとよいのかの一例として、perldoc perlstyle
で参照でき、
perlstyle内でも書かれているように、
use strict;
use warnings;
文法チェックと警告が有効になり、no strict、
を使います。
Perl::CriticでPerlベストプラクティスに沿っているかを確認する
『Perlベストプラクティス』perlctiricファイル名
でチェックができます。ホームディレクトリ以下に作成される.perlcritic
を編集することで、
Perl::Tidyでコードを整形する
Perl::Tidyは、perltidy ファイル名
で実行できます。たとえばperltidy -i=4 -nsfs ファイル名
とすると、.perltidyrc
にルールを定義しておくと便利です。
PerlでWebアクセスをする
Perlではモジュールを使いこなせば、
LWP::UserAgentでHTTP通信をする
LWPはPerlでWebアクセスするための各種モジュールの集まりです。LWP::UserAgentはその中の一つで、
リスト1はLWP::UserAgentを利用してWebページをダウンロードします。
1: use LWP::UserAgent;
2:
3: my $ua = LWP::UserAgent->new(
4: agent => "PerlHackersHub_32/1.0",
5: timeout => 10,
6: );
7: my $url = "http://gihyo.jp/dev/serial/01/perl-hackers-hub";
8: my $res = $ua->get($url);
9: print $res->content;
モジュールは1行目のようにuse
で指定して、$ua
に代入しています。4~5行目ではユーザエージェントとタイムアウトを指定しています。GETを使うために8行目ではアロー演算子を用いてget
メソッドを呼び出し、$res
に格納し、$res->content
のようにcontentメソッドを使い、print
で表示させます。
実行すると図1のようにHTMLを取得できます。同様にしてステータスコードやヘッダも取得できるので、
print $res->code."\n";
print $res->message.'\n'; # 間違えた!
print $res->header('Content-Type')."\n";
$ perl lwp.pl <!DOCTYPE html> ... <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Perl Hackers Hub:連載 gihyo.jp … 技術評論社 (省略)
Perlでは文字列の連結に.を使うので、\n
をダブルクオーテーションで囲んだものと連結すると、
\n
をシングルクォーテーションで囲ってしまうと改行されずに\n
をそのまま表示してしまうので、
また、\n
を付けずに特殊変数$\
に\n
を代入する方法もあります。
$\ = "\n";
print $res->code;
print文の文末には特殊変数$\
も出力されているので、\n
の代入によってprint文の文末に\n
が自動で出力されます。$\
の仕様はperldoc -v '$\
'から、perldoc perlvar
から参照できます。
より軽量なHTTPクライアントFurl
FurlはLWPに次いで有名なHTTP通信モジュールで、
Furlを使う場合もリスト2のようにLWP::UserAgentと似た感覚でHTTP通信ができます。LWPに慣れたらFurlを使うとよいでしょう。
use Furl;
my $furl = Furl->new(
agent => 'PerlHackersHub_32/1.0',
timeout => 10,
);
my $url = 'http://gihyo.jp/dev/serial/01/perl-hackers-hub';
my $res = $furl->get($url);
print $res->content;
Benchmarkで簡易ベンチマーク
BenchmarkモジュールでCPU時間をもとにパフォーマンス比較が行えるので、
リスト3は、cmpthese
関数で行っています。cmpthese
では図2のように結果を比較表にして、
use Benchmark qw(cmpthese);
use LWP::UserAgent;
use Furl;
必ずローカル、または管理下のサーバを対象にすること
my $url = 'http://localhost:8000/index.html';
my $lwp = LWP::UserAgent->new(timeout => 10);
my $furl = Furl->new(timeout=>10);
cmpthese(
1000, {
lwp => sub { $lwp->get($url) },
furl => sub { $furl->get($url) },
});
$ perl bench_lwp_furl.pl Rate lwp furl lwp 901/s -- -60% furl 2273/s 152% --
なお、
URL操作とパーセントエンコーディング
HTTP通信をする先のURLを1つの文字列変数で宣言して扱ってもよいのですが、
URIモジュールを使うと、http://
のようなクエリ付きURLを手軽に作成できます。query_
でクエリを組み立てていくと、
リスト4はURIモジュールを使って、titles
の値であるAT&T
の&
部分がパーセントエンコーディングされて&titles=AT%26T
のようになります。こうしないと&T
というパラメータが発生し、
use URI;
#Wikipedia API でAT&T について取得するクエリ
my $uri = URI->new('http://ja.wikipedia.org/w/api.php');
$uri->query_form(
action => 'query',
prop => 'revisions',
titles => 'AT&T',
rvprop => 'content',
format => 'xml',
);
print $uri->host; # ja.wikipedia.org
print $uri->path; # /w/api.php
print $uri->as_string;
# http://ja.wikipedia.org/w/api.php?action=query&prop..
なお、
欲しい情報のみ抽出する
LWPやFurlといったモジュールを利用してPerlでHTTPクライアントが簡単に実装できました。Webページを丸ごと欲しい場合やAPIとの通信が目的であればこのままで問題はないのですが、
Web::Scraperでスクレイピングする
Web::Scraperではリスト5のような記法で抽出したい要素を記述していきます。
1: use Web::Scraper;
2: use URI;
3: my $scraper = scraper {
4: process '#primary > div > div.readingContent01
5: > ul > li', 'items[]' => scraper {
6: process 'a', uri => '@href',
7: }
8: };
9: my $url = 'http://gihyo.jp/dev/serial/01/perl-hackers-hub';
10: my $res = $scraper->scrape( URI->new($url) );
11: foreach my $item ( @{$res->{items}} ) {
12: print $item->{uri};
13: }
リスト5の3行目からのscraper{}
内のように、ul
タグの中のli
タグで列挙されている要素からテキストとURLを抽出します。
10行目の$scraper->scrape
で取得先を引数にして実行します。ここに入れるURLは必ずURIモジュールのインスタンスでなければならないことに注意してください。scrape
メソッドに文字列を渡すとそれをHTML文字列とみなして解析をするためです。
C-styleのforは遅い
Perlでループを使うときは、foreach
を使うほうがよいです。
# インデックス付きのループ
for ( my $i=0; i<10; i++ ){ }
# こちらを使う
foreach ( 0..10 ) { }
インデックス付きのループの場合、
Web::QueryでjQuery風にスクレイピングをする
Web::Queryモジュールでは、
リスト5をWeb::Queryで書くとリスト6のようになります。find
でタグを指定し、each
でリストの中身を一つ一つ処理していきます。
use Web::Query;
my $url = 'http://gihyo.jp/dev/serial/01/perl-hackers-hub';
my $wq = Web::Query->new($url);
my $res = $wq->find('#primary > div >
div.readingContent01 > ul > li')
->each(sub {
print $_->find('a')->attr('href');
print $_->find('a')->text();
});
filter
メソッドでフィルタリングができます。たとえばページの全リンクタグからgihyo.
へ内部リンクしているものだけを取り出したい場合には、
->find('a')
->filter(
sub{my ($j,$elem) = @_;
$elem->attr('href') =~ /\Ahttp:\/\/gihyo\.jp\//; })
->each(sub{...});
ユーザエージェントを設定する
Web::ScraperやWeb::QueryではHTML文字列をそのまま渡しても解析、get
したレスポンスと組み合わせることができます。
my $furl = Furl->new( agent => 'PerlHackersHub_32/1.0' );
my $res = $furl->get( $url );
$scraper->scrape( $res->content ); # res->contet はHTML
Web::Queryの内部で宣言されているLWPはユーザエージェントがデフォルトlibwww-perl
)$モジュール名::変数名
で次のように代入します。
$Web::Scraper::UserAgent
= LWP::UserAgent->new( agent=>'PerlHackersH
ub_32/1.0');
$Web::Query::UserAgent
= LWP::UserAgent->new( agent=>'PerlHackersH
ub_32/1.0');
Web::ScraperとWeb::Queryの高速化
libxml2の利用できる環境use Web::Scraper::LibXML;
やuse Web::Query::LibXML;
で読み込ませるだけで、
変数やオブジェクトをダンプする
今回のようなスクレイピングした結果を配列やハッシュを使ったデータ構造に格納するとき、
Data::Dumperでデータ構造を丸ごと出力
変数やオブジェクトの中身を丸ごと確認するには、
リスト7のように変数の前にDumper
を置いて使用します。すると図3のように日本語がエスケープされて出力されます。YAML::Dump
のようなほかのダンプ用モジュールを使えば日本語を表示できますが、use Data::Dumper;
のあとあたりに書くとよいでしょう。
$Data::Dumper::Useperl = 1;
{
package Data::Dumper;
sub qquote{ return shift; }
}
use utf8;
use Data::Dumper;
use URI;
my $data = {
name => 'gihyo taro',
url => URI->new('http://gihyo.jp/'),
message => ' こんにちは!'
};
print Dumper $data;
$VAR1 = { 'message' => "\x{3053}\x{3093}\x{306b}\x{3061}\x{306f}\x{ff01}", 'url' => bless(do{\(my $o = 'http://gihyo.jp/')}, 'URI::http' ), 'name' => 'gihyo taro' };
Data::DumperはC言語による拡張qquote
をオーバーライドしています。
見やすくカラフルなData::Printer
Data::Printerというモジュールも便利です。コアモジュールではないため、use Data::Printer;
、use DDP;
でモジュールを読み込むことができます。出力したい変数に対してp $data
と書くだけで、
\ {
message " こんにちは!",
name "gihyo taro",
url URI::http {
Parents URI::_server
public methods (2) : canonical, default_port
private methods (0)
internals: "http://gihyo.jp/"
}
}
<続きの