前回の(1)はこちらから。
Perlのコーディング規約
いざ学んだPerlをアウトプットしようとしたときには、Perlの自由な記述に戸惑うことがあるでしょう。ここではコーディングスタイルの一例として「Perl style guide」を、そしてコーディングをサポートするモジュールとしてPerl::CriticとPerl::Tidyを簡単に紹介します。
perlstyleでコーディングスタイルを参考にする
Perlのコードをどのように書くとよいのかの一例として、Perldoc内に「Perl style guide」という形で紹介されています。ターミナルからperldoc perlstyle
で参照でき、インデントやスペースの置き方、ドキュメントの書き方の指南が提供されています。
perlstyle内でも書かれているように、次の2つは必ずコードの冒頭に書いてください。
文法チェックと警告が有効になり、変数の宣言忘れやタイプミスなどについて警告を出してくれます。これらの警告を受け付けたくないときは、そのときだけ例外的にno strict、no warnings
を使います。
Perl::CriticでPerlベストプラクティスに沿っているかを確認する
『Perlベストプラクティス』(注1)という書籍ではPerlのベストプラクティスを複数紹介しています。Perl::Criticは、Perlのコードがそのベストプラクティスに沿っているかをチェックしてくれます。インストールすると、perlctiricファイル名
でチェックができます。ホームディレクトリ以下に作成される.perlcritic
を編集することで、細かくルールを定義できます。
Perl::Tidyでコードを整形する
Perl::Tidyは、ソースコードをルールに沿って整形してくれます。インストールすると、perltidy ファイル名
で実行できます。たとえばperltidy -i=4 -nsfs ファイル名
とすると、インデントは4スペースで、セミコロンの前のスペースが除去されて出力されます。Perl::Criticと同様に.perltidyrc
にルールを定義しておくと便利です。
PerlでWebアクセスをする
Perlではモジュールを使いこなせば、巨大なWebシステムからコマンドラインツールまで簡単に実装できます。その中でもHTTP通信は、モジュールでとても簡単に実装できます。特にプログラミング初心者にとっては、実際にWebサイトやWeb APIから情報を取得することで、できることの幅が広げられるでしょう。PerlでHTTP通信をするためのモジュールでお勧めなのは、本節で紹介するLWP::UserAgentとFurlです。
LWP::UserAgentでHTTP通信をする
LWPはPerlでWebアクセスするための各種モジュールの集まりです。LWP::UserAgentはその中の一つで、ユーザエージェントを実装するクラスです。
リスト1はLWP::UserAgentを利用してWebページをダウンロードします。
モジュールは1行目のようにuse
で指定して、3~6行目でLWP::UserAgentオブジェクトのインスタンスを作成し$ua
に代入しています。4~5行目ではユーザエージェントとタイムアウトを指定しています。GETを使うために8行目ではアロー演算子を用いてget
メソッドを呼び出し、取得先を指定します。HTTP::Responceが返ってくるので変数$res
に格納し、htmlを表示させたいので$res->content
のようにcontentメソッドを使い、print
で表示させます。
実行すると図1のようにHTMLを取得できます。同様にしてステータスコードやヘッダも取得できるので、次のように出力を追加してみます。
Perlでは文字列の連結に.を使うので、改行を表す\n
をダブルクオーテーションで囲んだものと連結すると、出力時に1行ずつ改行されて表示されます。
\n
をシングルクォーテーションで囲ってしまうと改行されずに\n
をそのまま表示してしまうので、シングルクォーテーションとダブルクオーテーションの使い分けに注意してください。
また、print文ごとに\n
を付けずに特殊変数$\
に\n
を代入する方法もあります。
print文の文末には特殊変数$\
も出力されているので、\n
の代入によってprint文の文末に\n
が自動で出力されます。$\
の仕様はperldoc -v '$\
'から、特殊変数の一覧はperldoc perlvar
から参照できます。
より軽量なHTTPクライアントFurl
FurlはLWPに次いで有名なHTTP通信モジュールで、処理の速さが長所です。
Furlを使う場合もリスト2のようにLWP::UserAgentと似た感覚でHTTP通信ができます。LWPに慣れたらFurlを使うとよいでしょう。
Benchmarkで簡易ベンチマーク
BenchmarkモジュールでCPU時間をもとにパフォーマンス比較が行えるので、LWP::UserAgentとFurlで比較してみましょう。
リスト3は、ローカルのHTTPサーバへLWP::UserAgentとFurlで1,000回ずつGETリクエストしたときの比較を、cmpthese
関数で行っています。cmpthese
では図2のように結果を比較表にして、遅い順に出力します。Rateは1秒間の実行回数で表され、右に比較対象との比率を表しています。このようにベンチマーク上ではFurlのパフォーマンスが良いことがわかります。
なお、外部のサーバへこうした短時間の連続アクセスを送るのは迷惑行為となるので、自分の管理下にあるサーバを対象に実行してください。これはほかのWebアクセスするプログラムも同様です。
URL操作とパーセントエンコーディング
HTTP通信をする先のURLを1つの文字列変数で宣言して扱ってもよいのですが、マルチバイト文字や特殊文字のエスケープで問題が発生することがあるので、クエリ操作やURLエンコードの方法を知っておくと便利です。
URIモジュールを使うと、http://ja.wikipedia.org/w/api.php?action=query&prop=revisions&titles=AT%26T&rvprop=content&format=xml
のようなクエリ付きURLを手軽に作成できます。query_form
でクエリを組み立てていくと、クエリパラメータが多く複雑なものでもわかりやすいコードになります。
リスト4はURIモジュールを使って、Wikipedia APIへの検索クエリを組み立てています。パラメータの値が自動でURLエンコードされるので、titles
の値であるAT&T
の&
部分がパーセントエンコーディングされて&titles=AT%26T
のようになります。こうしないと&T
というパラメータが発生し、うまく取得できない場合が生じます。また、ホスト部分やパスだけを取得したり、変更したりもできます。
なお、速度を重視してURLエンコードだけ必要なプログラムを作りたい場合は、高機能なぶん重いURIモジュールを使うよりも、URLエンコードに特化したモジュールであるURI::Escape::XSやURL::Encode::XSを使うほうが良いこともあるので参考にしてください。
欲しい情報のみ抽出する
LWPやFurlといったモジュールを利用してPerlでHTTPクライアントが簡単に実装できました。Webページを丸ごと欲しい場合やAPIとの通信が目的であればこのままで問題はないのですが、ページの一部分だけを抜き出したい場合にはノイズが多過ぎます。そこで役立つのがスクレイピングに特化したモジュールで、本節ではWeb::ScraperとWeb::Queryを紹介します。
Web::Scraperでスクレイピングする
Web::Scraperではリスト5のような記法で抽出したい要素を記述していきます。
リスト5の3行目からのscraper{}
内のように、抽出したいタグをXPathかCSS Pathで定義していきます。ここではWebページ内のul
タグの中のli
タグで列挙されている要素からテキストとURLを抽出します。
10行目の$scraper->scrape
で取得先を引数にして実行します。ここに入れるURLは必ずURIモジュールのインスタンスでなければならないことに注意してください。scrape
メソッドに文字列を渡すとそれをHTML文字列とみなして解析をするためです。
C-styleのforは遅い
Perlでループを使うときは、次のようなインデックス付きのループではなく、リスト5の10行目のようなforeach
を使うほうがよいです。
インデックス付きのループの場合、インデックスのインクリメントや終了条件のチェックのためにパフォーマンスが悪くなります。Benchmarkモジュールを使って両者の簡単なベンチマークを実行してみるとよいでしょう。
Web::QueryでjQuery風にスクレイピングをする
Web::Queryモジュールでは、jQueryのセレクタのように要素を指定してスクレイピングができます。
リスト5をWeb::Queryで書くとリスト6のようになります。find
でタグを指定し、each
でリストの中身を一つ一つ処理していきます。
filter
メソッドでフィルタリングができます。たとえばページの全リンクタグからgihyo.jp
へ内部リンクしているものだけを取り出したい場合には、findとeachの間にfilterを入れるとよいでしょう。
ユーザエージェントを設定する
Web::ScraperやWeb::QueryではHTML文字列をそのまま渡しても解析、抽出してくれるので、LWP::UserAgentやFurlでget
したレスポンスと組み合わせることができます。
Web::Queryの内部で宣言されているLWPはユーザエージェントがデフォルト(libwww-perl
)なので、カスタムユーザエージェントを設定したい場合はこの方法を使うか、または内部変数に直接アクセスします。Web::ScraperとWeb::Queryはどちらも内部で、ourでグローバル宣言された$UserAgentにLWP::UserAgentのインスタンスが代入されています。グローバル宣言された変数はモジュールをインポートしたプログラム側からでも呼び出すことができるので、$モジュール名::変数名
で次のように代入します。
Web::ScraperとWeb::Queryの高速化
libxml2の利用できる環境[2]であれば、use Web::Scraper::LibXML;
やuse Web::Query::LibXML;
で読み込ませるだけで、HTMLの解析をlibxml2を使って行うため処理の高速化が見込めます。Perlモジュールではこういったロードするだけで処理速度の向上を見込めるモジュールがいくつも存在します。
変数やオブジェクトをダンプする
今回のようなスクレイピングした結果を配列やハッシュを使ったデータ構造に格納するとき、目的の数値や文字列がちゃんと取得できているかを、変数の中身全体を見ながらプログラムを動作させたくなります。そんなときはData::Dumperに代表されるダンプ用モジュールを使うと、変数やオブジェクトの値を追いながらのデバッグが楽に行えます。
Data::Dumperでデータ構造を丸ごと出力
変数やオブジェクトの中身を丸ごと確認するには、Data::Dumperがお勧めです。Perlのコアモジュールとして標準で入っているので追加でインストールする必要がなく手ごろです。
リスト7のように変数の前にDumper
を置いて使用します。すると図3のように日本語がエスケープされて出力されます。YAML::Dump
のようなほかのダンプ用モジュールを使えば日本語を表示できますが、モジュールの追加が困難な環境だったり、どうしてもData::Dumperで表示させたいときは、次のコードをuse Data::Dumper;
のあとあたりに書くとよいでしょう。
Data::DumperはC言語による拡張(XS)を行っており容易にオーバーライドできないので、Useperlフラグを立ててXSを避けて、日本語をエスケープしているqquote
をオーバーライドしています。
見やすくカラフルなData::Printer
Data::Printerというモジュールも便利です。コアモジュールではないため、CPANからインストールする必要があります。use Data::Printer;
、またはエイリアスであるuse DDP;
でモジュールを読み込むことができます。出力したい変数に対してp $data
と書くだけで、値だけでなくオブジェクトのメソッドもまとめて出力されます。
<続きの(3)はこちら。>