WWW::MechanizeでHTTPアクセスしてWeb::Scraper

[Web::Scraper](https://metacpan.org/pod/Web::Scraper)で生成済みのLWPオブジェクトを使いたい時がある。[WWW::Mechanize](https://metacpan.org/pod/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'} };
~~~

[ドキュメントに書かれて](https://metacpan.org/pod/Web::Scraper#scrape)いる方法であり、一番きれいな感じ。また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オブジェクトを設定する

一番上の方法がもっともきれいで（ドキュメント化されているので）長期間変更されることなく利用できそうな方法だが、一番下の方法がもっとも簡便だと思われる。実際、この調査のきっかけになったスクリプトでは一番下の方法を使った。丁寧さと利便性のバーターの中で使い分けるとよいと思う。