えっ、と思った現象です。
my $mech = WWW::Mechanize->new; my $res = $mech->get('http://〜〜〜〜.jpg'); warn "mech->content: " . length($mech->content) . "\n"; warn "res->content : " . length($res->content) . "\n"; # 結果 mech->content: 5078 res->content : 4582
Mechanize->contentって、Response->contentを返してるわけじゃないのですね。
まとめ
WEBページから圧縮されたデータを受け取る場合は、以下に気をつけましょう。
- HTTP::Response->content は使わない
- HTTP::Response->decoded_content か WWW::Mechanize->content を使う
- LWP::UserAgent->get(:content_file) は使わない
- WWW::Mechanize->save_content() を使う
HTTP::Response->contentの問題
Mechanizeの_update_pageを見てみると、
my $content = $res->decoded_content( charset => 'none' ); $content = $res->content if (not defined $content);
って感じです。ああ、確かにcontentメソッドじゃなくてdecoded_contentメソッドを優先してますね。じゃあ、decoded_contentって何よ、となるわけですが、こいつの実体はHTTP::Messageに居ます。
if (my $h = $self->header("Content-Encoding")) { /* ............. 略 .....................*/ if ($ce eq "gzip" || $ce eq "x-gzip") { /* ............. 略 .....................*/ $content_ref = \Compress::Zlib::memGunzip($$content_ref); /* ............. 略 .....................*/ } elsif ($ce eq "x-bzip2") { /* ............. 略 .....................*/ $content_ref = Compress::Bzip2::decompress($$content_ref); /* ............. 略 .....................*/ } elsif ($ce eq "deflate") { /* ............. 略 .....................*/ my $out = Compress::Zlib::uncompress($$content_ref); /* ............. 略 .....................*/
なるほど、Content-Encoding指定で圧縮された転送されて来たデータをこいつで元に戻してるのですね*1。ってことは逆に言えば、HTTP::Responseのcontentって、圧縮されたままの状態の値が入ってるわけですか。こりゃー罠だ。
普通我々が欲しいのは展開されたデータですので、Response->decoded_contentを使うかWWW::Mechanize->content経由でdecoded_contentを使うかしたほうが良さそうです。
LWP::UserAgent->getの:content_file指定の問題
LWP::UserAgent->getの:content_fileでコンテンツをファイルに落とせるわけですが、こいつはLWP/Protocol.pmのcollect()辺り見ればわかりますが、Responseオブジェクトを経由せずに飛んで来たデータをファイルにそのまま落とします。
open(OUT, ">$arg") or return HTTP::Response->new(&HTTP::Status::RC_INTERNAL_SERVER_ERROR, "Cannot write to '$arg': $!"); binmode(OUT); local($\) = ""; # ensure standard $OUTPUT_RECORD_SEPARATOR while ($content = &$collector, length $$content) { /* ............. 略 .....................*/ print OUT $$content or die "Can't write to '$arg': $!"; /* ............. 略 .....................*/ }
Content-Encoding指定されてる圧縮データを展開する機能はResponseオブジェクトが持ってるので、ファイルに保存されるのは圧縮されたデータです。
この機能はデータをメモリにとらない分効率的なのかもしれないですが、ファイルに保存された圧縮データを展開するにはレスポンスのContent-Encodingヘッダ見ながらやらなきゃいけないので、あまり得策とは言えません。