Web::Scraperで生成済みのLWPオブジェクトを使いたい時がある。WWW::Mechanizeオブジェクトならなおいい。例えばログインが必要なサイトをスクレイピングするために、こうやってログインを済ませたWWW::Mechanizeインスタンスを用意した時、これをWeb::Scraperに使わせたい。
use WWW::Mechanize;
my $mech = WWW::Mechanize->new;
my $account = {
'username' => 'myname',
'password' => 'mypassword'
});
$mech->get('http://example.com/login'); # ログインページを取得
$mech->submit_form('fields' => $account); # ログイン
思いついた範囲で言えば、きれいな方法が2つとダーティーな方法が1つある。
scrapeメソッドの引数にHTTP::Responseオブジェクトを渡す
scrape
メソッドにはURIの代わりにHTTP::ResponseオブジェクトやHTMLデータなどを渡すことができる。ログイン済みのWWW::MechanizeオブジェクトでHTTPアクセスし、取得したHTTP::Responseオブジェクトを引数に渡してあげれば、要ログインページのコンテンツを解析できる。
use WWW::Mechanize;
use Web::Scraper;
# 前述のWWW::Mechanizeオブジェクト生成とログイン処理
my $mech = WWW::Mechanize->new;
my $account = {
'username' => 'myname',
'password' => 'mypassword'
});
$mech->get('http://example.com/login'); # ログインページを取得
$mech->submit_form('fields' => $account); # ログイン
# スクレイパーの生成、コンテンツの取得、解析
my $uri = URI->new('http://example.com/list');
my $scraper = scraper {
process "a", uris[] => '@href';
};
my $res = $mech->get($uri);
my $list = $scraper->scrape($res);
print join("\n", @{ $list->{'uris'} };
ドキュメントに書かれている方法であり、一番きれいな感じ。またHTML::ResponseオブジェクトやHTML文字列を渡せることを知っておくと、例えば不調時にGETと解析は別個に行い調査したり、他のHTTPクライアントモジュールや外部コマンドでの取得結果を解析するといった応用も利く。
ただし、scrape
メソッドの呼び出し1つで取得と解析が済むスマートさは損なわれる。
scraperオブジェクトにWWW::Mechanizeオブジェクトを渡す
Web::Scraperのuser_agent
メソッドを使うと、scraper
メソッドで生成したWeb::Scraperオブジェクトで使用するLWP::UserAgent(またはその継承クラス)オブジェクトを設定できる。ログイン処理済みのWWW::Mechanizeオブジェクトを渡しておけば、以降このWeb::Scraperオブジェクトは渡されたWWW::MechanizeオブジェクトでHTTPアクセスする。
use WWW::Mechanize;
use Web::Scraper;
# 前述のWWW::Mechanizeオブジェクト生成とログイン処理
my $mech = WWW::Mechanize->new;
my $account = {
'username' => 'myname',
'password' => 'mypassword'
});
$mech->get('http://example.com/login'); # ログインページを取得
$mech->submit_form('fields' => $account); # ログイン
# スクレイパーの生成、UAの設定、スクレイプ
my $uri = URI->new('http://example.com/list');
my $scraper = scraper {
process "a", uris[] => '@href';
};
$scraper->user_agent($mech);
my $list = $scraper->scrape($res);
print join("\n", @{ $list->{'uris'} };
user_agent
メソッドはドキュメントに記述がないのだけど、オブジェクト変数を直接いじるわけでもなく、そこそこきれいな感じ。scrape
メソッドの呼び出し1つで取得と解析が済み、以降繰り返しscrape
しても自動的にこのWWW::Mechanizeオブジェクトが使われてスマートな感じ。
$Web::Scraper::UserAgent変数にWWW::Mechanizeオブジェクトを設定する
Web::Scraperオブジェクトは、user_agent
メソッドで設定されたLWP::UserAgentオブジェクトがない場合、クラス変数$Web::Scraper::UserAgent
を使用する。これもない場合、新規にLWP::UserAgentオブジェクトを生成して$Web::Scraper::UserAgent
に格納し、以降はこれを使用する。ログイン処理済みのWWW::Mechanizeオブジェクトをこのクラス変数に設定しておけば、Web::ScraperはデフォルトでこのWWW::Mechanizeオブジェクトを使用する。
use WWW::Mechanize;
use Web::Scraper;
# 前述のWWW::Mechanizeオブジェクト生成とログイン処理
my $mech = WWW::Mechanize->new;
my $account = {
'username' => 'myname',
'password' => 'mypassword'
});
$mech->get('http://example.com/login'); # ログインページを取得
$mech->submit_form('fields' => $account); # ログイン
# デフォルトのUserAgentを設定しておく
$Web::Scraper::UserAgent = $mech;
# スクレイパーの生成、スクレイプ
my $uri = URI->new('http://example.com/list');
my $scraper = scraper {
process "a", uris[] => '@href';
};
my $list = $scraper->scrape($res);
print join("\n", @{ $list->{'uris'} };
ドキュメント化されていないクラス変数$Web::Scraper::UserAgent
を直接変更し、またuser_agent
メソッドで設定されたLWP::UserAgentオブジェクトがある場合には無視されることを意識する必要があるなど、すこしダーティーというか力尽くな感じがある。しかし以降はなにも意識することなく、すべてのWeb::Scraperオブジェクトで自動的にこのWWW::Mechanizeオブジェクトが使われて、もっともスマートな感じ。
まとめ
要ログインサイトのスクレイピングなど、解析はWeb::Scraperで行いたいがHTTPアクセスはWWW::Mechanizeで行いたいといった時の方法として、以下の3つを挙げた。
- scrapeメソッドの引数にHTTP::Responseオブジェクトを渡す
- scraperオブジェクトにWWW::Mechanizeオブジェクトを渡す
- $Web::Scraper::UserAgent変数にWWW::Mechanizeオブジェクトを設定する
一番上の方法がもっともきれいで(ドキュメント化されているので)長期間変更されることなく利用できそうな方法だが、一番下の方法がもっとも簡便だと思われる。実際、この調査のきっかけになったスクリプトでは一番下の方法を使った。丁寧さと利便性のバーターの中で使い分けるとよいと思う。