― Web Technology and Life ―

LWP::UserAgentで'accept-encoding' => 'gzip'をうまく扱う方法を今更知った話

2013-04-26
HTTPクライアントモジュールならFurlの時代(?)に、いまさら、LWP::UserAgentの話なんですが、調べると「へー」そうなんだってちょっと驚いたので、メモっておきます。とりあえず、 libwww-perlまわりの絡まり具合は半端ないっす。

スクレーピングやクローリングでgzip圧縮のファイルを受け取りたいよ!

Furlをよく調べていませんが、Furlは基本的にAPIを高速にたたくみたいな話なので、まぁこういう機能的な話はLWP::UserAgentなのかなとはなからLWP::UserAgentでやろうと思いました。で、そもそも、gzip圧縮ファイルを受け取るには、

my $ua = LWP::UserAgent->new();
$ua->default_header('Accept-Encoding' => 'gzip,deflate,sdch');

という感じで、headerにgzip圧縮形式でも受け取れますよーって宣言してあげる必要があると思ってやるんですが、

my $ua = LWP::UserAgent->new();
$ua->default_header('Accept-Encoding' => 'gzip,deflate,sdch');

my $res = $ua->get('http://example.com/');

my $html = $res->content;

warn $html; #圧縮されているからほげほげ

という感じに、それでは、この圧縮ファイルをどうやって解凍してやろうかなーと、ちょっとめんどくさそうな雰囲気を醸し出すんですね。

ここで、さてさて、どうしたものかとググるわけです。すると、『How can I accept gzip-compressed content using LWP::UserAgent?』という記事が出てきて、以下のサンプルコードが書かれているのです。

my $ua = LWP::UserAgent->new;
my $can_accept = HTTP::Message::decodable;
my $response = $ua->get('http://stackoverflow.com/feeds', 
    'Accept-Encoding' => $can_accept,
);
print $response->decoded_content;

これを、動かしてみると、何のことはなく動くんですが、「$can_acceptにgzipって入っているのかね?」と疑問に思って、ちょっとあさり始めるんですね。

warn $can_accept; # gzip,x-gzip,deflate,x-bzip2

と表示されるんですね。ふむ、コードでも見てみるかと思ってみると、こんな感じに。。。

HTTP::Message::decodableとdecoded_contentが簡単

HTTP::Message::decodableで解凍できるパターンを抽出

HTTP::Message::decodableのコードを見てみると、次のようになっています。

sub decodable
{
    # should match the Content-Encoding values that decoded_content can deal with
    my $self = shift;
    my @enc;
    # XXX preferably we should determine if the modules are available without loading
    # them here
    eval {
        require IO::Uncompress::Gunzip;
        push(@enc, "gzip", "x-gzip");
    };
    eval {
        require IO::Uncompress::Inflate;
        require IO::Uncompress::RawInflate;
        push(@enc, "deflate");
    };
    eval {
        require IO::Uncompress::Bunzip2;
        push(@enc, "x-bzip2");
    };
    # we don't care about announcing the 'identity', 'base64' and
    # 'quoted-printable' stuff
    return wantarray ? @enc : join(", ", @enc);
}

解凍系のモジュールが入っていれば(だいたい最近のバージョンではコアになっているもののようですが)、Accept-Encodingにかける内容を追加していくものですが、「should match the Content-Encoding values that decoded_content can deal with」というコメントが気になりますね。おや?「decoded_content」ってこんな感じに使うよな?と思うわけですね。

my $ua = LWP::UserAgent->new();

my $response = $ua->get('http://stackoverflow.com/feeds');

print $response->decoded_content;

decoded_contentはHTTP::ResponseじゃなくてHTTP::Messageのメソッド

$responseから生えているからHTTP::Responseのメソッドじゃないのかな?と思って、今度は、HTTP::Responseを見てみますが、確かに、decoded_contentはないんですね。ただし、まともにみると以下のようなコードが冒頭に書かれています。

package HTTP::Response;

require HTTP::Message;
@ISA = qw(HTTP::Message);
$VERSION = "6.04";

よく見ると、「HTTP::Response」は「HTTP::Message」を継承しているし、そもそも「HTTP::Response」は「HTTP::Message」のパッケージ内のモジュールなんですね。そして、しっかりHTTP::Messageにdecoded_contentメソッドが書かれています。

TTP::Message::decodableとdecoded_contentでよしなに

decoded_contentはすべて引用しないですが、かなり長くゴリゴリやっているメソッドですね。。。解凍をしているコード部分を一部引用すると以下です。

if ($ce eq "gzip" || $ce eq "x-gzip") {
    require IO::Uncompress::Gunzip;
    my $output;
    IO::Uncompress::Gunzip::gunzip($content_ref, \$output, Transparent => 0)
    or die "Can't gunzip content: $IO::Uncompress::Gunzip::GunzipError";
    $content_ref = \$output;
    $content_ref_iscopy++;
}
elsif ($ce eq "x-bzip2" or $ce eq "bzip2") {
    require IO::Uncompress::Bunzip2;
    my $output;
    IO::Uncompress::Bunzip2::bunzip2($content_ref, \$output, Transparent => 0)
    or die "Can't bunzip content: $IO::Uncompress::Bunzip2::Bunzip2Error";
    $content_ref = \$output;
    $content_ref_iscopy++;
}

というわけで、最初のググったコードに戻るとですね、

my $ua = LWP::UserAgent->new;
my $can_accept = HTTP::Message::decodable;
my $response = $ua->get('http://stackoverflow.com/feeds', 
    'Accept-Encoding' => $can_accept,
);
print $response->decoded_content;

これで、圧縮ファイルの解凍もいわゆるutf8へのdecode(Perlの内部文字列変換)もよしなやってくれるんですね。結構びっくりしました。

蛇足

まぁそれでもですね、decodeのやり方を自作したいというとき(Encode::Detectつかったり?)や、よくわからんですがdecoded_contentが対応していない圧縮形式の解凍なんているのがあったりしたら、decoded_content並に頑張らなくちゃいけないと思うとなかなかヘビーだなーと寒気を感じるのは、いまさらここでネタにした内容を知った私だけでしょうか。。。

とにかく、つまるとことオープンソースのライブラリはホントありがたいですね!!

Perl update_at : 2013-04-26T11:36:22
hirobanex.netの更新情報の取得
 RSSリーダーで購読する   
blog comments powered by Disqus