理系学生日記

おまえはいつまで学生気分なのか

スクリプトからの Livedoor Reader へのログイン方法

このエントリでは、(WWW::Mechanize ではなく)LWP::UserAgent を使って Livedoor Reader にログインするにはどうすれば良いのかを説明します。

WWW::Mechanize を使った方法

Livedoor Reader は(未公開ながらも)API を持っているので、これが利用できると世界が広がるのですが、当該の API を使用するためにはログインが必要です。
これはなぜかというと、API を使用するためには、ログイン時に発行される API Key を POST/GET のパラメータとして送らなければならないからです。

では、このログインをどうすれば良いのかですが、Livedoor Reader では単にログイン用フォームに POST で id/pass を送りつけるだけではログインができません。
現在もっとも使用・確立されている方法は、miyagawa さんの Plagger::Plugin::Subscription::LivedoorReader で使用されている、以下の方法だと思います。
少々割愛したソースもありますが、WWW::Mechanize を使用して http://reader.livedoor.com/reader/ にアクセスした後、ログイン用フォームに id/pass をセットして POST します。

    local $^W; # input type="search" warning
    $self->{mech}->get("http://reader.livedoor.com/reader/");

    if ($self->{mech}->content =~ /name="loginForm"/) {
        Plagger->context->log(debug => "Logging in to Livedoor Reader");
        $self->{mech}->submit_form(
            form_name => 'loginForm',
            fields => {
                livedoor_id => $self->conf->{username},
                password    => $self->conf->{password},
            },
        );

        if ( $self->{mech}->content =~ /class="headcopy"/ ) {
            Plagger->context->error("Failed to login using username & password");
        }
    }

    $self->{mech}->cookie_jar->scan(
        sub {
            my($key, $val) = @_[1,2];
            if ($key =~ /_sid/) {
                $self->{apikey} = $val;
                return;
            }
        },
    );
}

LWP::UserAgent を使いたい理由

Web サービスに対するクライアントモジュールを書く場合、どうやってテストケースを実装するかが問題になります。
実際の Web サービスをテストに使うという方法はもちろんありますが、基本的に使用すべきでないのは周知の事実です。

開発時点ならいくらかのアクセスで済むかもしれませんが、例えばこのモジュールが CPAN にアップロードされ、CPAN testers で実行されたらどうなるでしょうか。多数のサーバでテストが走り、DDoS のような状況が発生するかもしれません(まあそれほど多数ではないかもしれないけど)。対象が大きいサイトなら、なんともないかもしれませんが、数に関わらず、はっきりいってこれはやめたほうがいいですね。先方に迷惑がかかります。

HTTP通信を含むモジュールのテスト - Articles Advent Calendar 2011 Test

ここで CPAN を見渡すと、Test::LWP::UserAgent というモジュールを使えば簡単に LWP::UserAgent の Mock が作れそうです。
クライアントモジュールのコンストラクタで通信に使用する UserAgent を切り替えられるようにしておきさえすれば、ユニットテストを本番サービスを使用することなく記述することができそうです。
というわけで、Livedoor Reader 用クライアントを書き、そのテストを記述するためには、LWP::UserAgent を使いたい。

# クライアントモジュールのコンストラクタ例
sub new {
    my ($klass, %param) = @_;

    my $ua = delete $param{ua} || LWP::UserAgent->new( #hogehoge );
    return bless {
        ua => $ua,
    } => $class;
}

# テスト
my $ua = Test::LWP::UserAgent->new( #hogehoge );
$ua->map_response( qr[ #なんかの URL ] => HTTP::Response->new(hogehoge) );

my $client = WebService::Hogehoge->new( ua => $ua );

LWP::UserAgent を使ったログイン方法

ポイントは単純です。

  1. LWP::UserAgent で cookie を使用できるようにする
  2. POST メソッドでもリダイレクトを有効化する

Cookie の使用を許可するのは、Livedoor Reader 側が、ログイン時に id/pass だけでなく Cookie も参照している(と思われる)ため。
POST メソッドでもリダイレクトを有効化するのは、ログイン成功時に Livedoor Reader から HTTP 302 を返却されてくるためです*1

エラー処理等を割愛すると、実際のコードは以下のような感じになります。

use LWP::UserAgent;

# Cookie を有効化した UserAgent インスタンスを作成
my $ua = LWP::UserAgent->new( cookie_jar => {} );

# Cookie を取得
my $content = $ua->get( 'http://reader.livedoor.com/reader/' )->content;

# 隠しパラメータの token 値を取得
my ($token) = $content =~ /"_token"\s+value="(\w+)"/;

# id, pass を使用して POST
$ua->post('https://member.livedoor.com/login/index', {
    _token      => $token,
    '.next'     => 'http://reader.livedoor.com/reader/',
    '.sv'       => 'reader',
    livedoor_id => 'your id',
    password    => 'your pass',
});

# api key を取得
my $apikey;
$ua->cookie_jar->scan( sub {
    my($key, $val) = @_[1,2];
    if ($key =~ /_sid/) {
        $apikey = $val;
        return;
    }
});

*1:実際には、不成功時も 302 だったりしますが