さて、モジュールの使い方に慣れてきたところでもう少し踏み込んだプログラミングをしていきましょう。
LWP::Simpleのgetメソッドでは、与えられたURLのコンテンツをHTTPのGETで取得します。一度コンテンツを取得するだけならそれでも構わないのですが、例えばスクリプトを定期的に実行する場合などは、特定のURLに対して何度もGETを発行することになります。このとき、何度も同じコンテンツを取得しにいくよりは、
といったことができれば、相手のサイトにかかる負荷が減り、効率が良いですね。
この仕組みはHTTPプロトコルでサポートされていて、「If-Modified-Sinceヘッダによる条件付きGET」なんて呼ばれたりします。
これによって、サーバ側は同じクライアントには一度だけしか文書を送信しなくて良いので負荷が減るといった仕組みです要するに同じサイトに何度もコンテンツを取得しにいく場合はIf-Modified-Sinceヘッダを使うのがマナーですよ、ということです。
さて、この条件付きGETを使ってコンテンツをGETしてくる方法を考えます。自分でHTTPヘッダに時刻を挿入してローカルにキャッシュして……とか考えるとちょっと面倒臭いですね。しかし、そこはモジュールさまさま。LWPにはこの条件付きGETを行うメソッドもしっかり用意されています。
LWP::Simpleのmirrorメソッドがそれです(リスト4)。第1引数がURL、第2引数が取得したコンテンツの保存先です。先のスクリプトをさらに改造して、条件付きGETをするように変更してみましょう(リスト5)。
mirror('http://www.example.com/', '/path/to/cache');
1 #!/usr/local/bin/perl
2 use strict;
3 use Digest::MD5 qw(md5_hex);
4 use Encode;
5 use LWP::Simple;
6 use XML::RSS;
7
8 our $CacheDir = '/tmp/rsscache';
9 if (! -e $CacheDir) {
10 mkdir $CacheDir or die "cannot create $CacheDir: $!";
11 }
12
13 my $url = shift;
14 my $cache = sprintf("%s/%s.xml", $CacheDir, Digest::MD5::md5_hex($url));
15
16 LWP::Simple::mirror($url, $cache)
17 or die "cannot get content from $url";
18
19 my $rss = XML::RSS->new;
20 $rss->parsefile($cache);
21 for (@{$rss->{items}}) {
22 print encode('euc-jp', $_->{title}), "\n";
23 }
まずは3行目。後ほどURLをMD5形式にするため、MD5を使用します。MD5を利用できるよ
うにするモジュールDigest::MD5をuseでロードしておきます。
8〜11行目では、ローカルキャッシュの保存先ディレクトリがなかったら作成しています。14行目では、キャッシュファイルのファイル名を作っています。ファイル名は何でも良いので、ここでは取得する対象のURLからユニークな文字列をMD5で作り、それをファイル名にしています。Digest::MD5モジュールのmd5_hex関数に文字列を渡せば、そのMD5が返ってきます。
16、17行目。いままではgetだったのをmirrorメソッドに変更しました。20行目では、スカラーデータ*をparseに渡していたところを、ローカルに保存したキャッシュに変更しています。このスクリプトを実行すると、先ほどと出力結果はまったく一緒ですが、Webサイトとの通信は最小限にとどめてローカルキャッシュを活用して動作するようになります。仕組みを理解するのがちょっと面倒臭いですが、実装はモジュールのおかげで簡単ですね。
LWP::Simpleはその名前に「Simple」とあるように、シンプルなHTTPクライアント用のモジュールです。もっと細かくさまざまなパラメータを制御したい場合や、オブジェクト指向プログラミングに役立てたいといった場合には、LWP::UserAgentを使います。
例えば、User-Agentやタイムアウトの時間を任意の値に設定し、Proxyサーバを通してHTTP GETを行うには、リスト6のようなコードになります。以下、簡単に解説していきましょう。
1 #!/usr/local/bin/perl
2 use strict;
3 use LWP::UserAgent;
4 use HTTP::Request;
5
6 my $request = HTTP::Request->new( GET => 'http://d.hatena.ne.jp/naoya/rss');
7
8 my $ua = LWP::UserAgent->new;
9 $ua->timeout(10);
10 $ua->proxy(['http', 'ftp'], 'http://proxy.example.com:3128/');
11 $ua->agent('UNIX USER Sample Client/0.01');
12
13 my $response = $ua->request($request);
14 if ($response->is_success) {
15 print $response->content;
16 } else {
17 die sprintf("error(%d): %s", $response->code, $response->message);
18 }
まず6行目です。LWP::UserAgentでは、リクエストを送るための方法が幾つか用意されていますが、ここではHTTP::Requestを使った方法で実装しています。まずHTTP::Requestでリクエストのインスタンスを作り、それをLWP::UserAgentのインスタンスに渡す、という手順です。HTTP::Requestでは、ほかにもリクエストヘッダをカスタマイズしたりといったことも可能です。
8〜10行目では、LWP::UserAgentのインスタンスを生成して、そこにタイムアウト値などのパラメータを設定しています。ほかにもCookieの値やリダイレクトを処理するかどうかなど幾つかの値が設定可能です。
最後に13〜18行目です。6行目で作ったHTTP::Requestインスタンスを指定してLWP::UserAgentでリクエストを行うと、返却値としてレスポンスのオブジェクト(HTTP::Responseのインスタンス)が返ってきます。このオブジェクトに、リクエストが成功したかどうかを尋ねて、成功なら結果を出力、失敗ならエラー内容を、HTTPステータスコードとメッセージとともに出力しています。
動作を細かく制御したい場合は、以上のように、
リクエスト HTTP::Request
HTTPクライアント LWP::UserAgent
レスポンス HTTP::Response
という3つのクラスを使って、プログラミングしていくことになります。
CPANに登録されているHTTPクライアントモジュールにはさまざまなものがありますが、LWP::SimpleやLWP::UserAgent以外にも次に挙げるモジュールが定番です(いずれもLWP::UserAgentのラッパーモジュール*です)。
ここまでで、XML::RSSによるRSSの料理方法を解説しました。ではRSS以外のXML文書を料理する場合にはどうしたら良いのでしょう? 次回はそのあたりについて解説します。
構造を持たないプレーンなデータのこと。ここでは文字列データを指している。
LWP::UserAgentモジュールを内部的に使用して、必要な機能のみ提供するモジュール。ラップは「くるむ」の意味。
行儀の良いWebロボットという意味。Webを巡回するために作られたソフトウェアでは、robots.txtを確認して、Web管理者の意図しないファイルにアクセスしないようにしなければいけない。Webロボットの中にはrobots.txtを無視するものもあるが、それは行儀が悪いとされている。
本記事は、オープンソースマガジン2005年7月号「作って学ぶ、今どきのWebサービス 第1回」を再構成したものです。
Copyright © ITmedia, Inc. All Rights Reserved.