読者です 読者をやめる 読者になる 読者になる

ゆーすけべー日記

はてなBlogってどーなの!?

いかにしておっぱい画像をダウンロードするか〜2012

Web Essay Perl

4年以上前のBlog記事で非常に評判がよく「高校生がプログラミングをはじめるキッカケになった」というエントリーがあります。 題名は「いかにして効率よく大量のおっぱい画像をダウンロードするか」。 僕たちにとって重要な命題に対しての答えとして、 当時は8000枚近くのおっぱい画像を入手できたPerlのスクリプトが掲載されています。

Web上のリソースから情報を取得したりダウンロードするプログラムはクローラーとかスパイダーと呼ばれていて、 こうした種類のコードを書くことは楽しいですし、勉強にもなります。 上記の「高校生」に限ったことではないでしょう。

しかし、過去のBlog記事に掲載されているプログラムは古いこともあって今では動かないっぽいです.... プログラムで使っているYahoo::Searchっていうモジュールがテストでこけて入りません。 クローラーなるものの宿命か、Web上の情報を頼りにするため、 例えば使っているYahoo! APIの仕様変更を考慮しなくてはいけなかったりします。 ちなみに、残念なことですが、最近の「画像検索 API」は制限がきつくなり最大で取得できる画像の情報の上限が多くて、 1000までと少なくなっています。

そこで今回は「いかにしておっぱい画像をダウンロードするか〜2012」と題し、 「今」動くスクリプトを紹介します。

このプログラムの仕組みは非常に簡単で大雑把には以下のような流れになっています。

  1. 画像検索 APIから取得できる分だけ画像情報を入手する
  2. 画像情報から画像へのリンクを取り出し、ダウンロードする
  3. ファイルに保存
  4. 繰り返す

今回は画像検索にBing APIを使い結果的に1000枚弱の画像をすぐに集めることができました。

集まったおっぱい画像

以下は実行している様子です。

実行している様子

このような目的のコードに対しては解説しすぎてしまうより、コードを直接見てもらった方が色々といいと思いますので、 ベタッと貼付けます。「$appid」は各自Bing APIのページより取得したものを入れてください。

oppai.pl

use strict;
use warnings;
use LWP::UserAgent;
use URI;
use JSON qw/decode_json/;
use Digest::MD5 qw/md5_hex/;
use Path::Class qw/dir file/;
use Encode;
use utf8;

my $appid = '';
my $uri   = URI->new('http://api.bing.net/json.aspx');
my $ua    = LWP::UserAgent->new;
my $dir   = dir('./data');

my $page_count     = 0;
my $download_count = 0;

while (1) {
    my $offset = $page_count * 50;
    $uri->query_form(
        AppId          => $appid,
        Version        => '2.2',
        Markert        => 'ja-JP',
        Sources        => 'Image',
        'Image.Count'  => 50,
        'Image.Offset' => $offset,
        Adult          => 'off',
        Query          => 'おっぱい'
    );

    my $res = $ua->get($uri);
    die $res->status_line if $res->is_error;
    my $ref = decode_json( $res->content );
    last unless defined @{ $ref->{SearchResponse}{Image}{Results} };

    for my $entry ( @{ $ref->{SearchResponse}{Image}{Results} } ) {
        next unless $entry->{MediaUrl} =~ /\.jpg$/;
        $download_count++;
        my $filename = md5_hex( encode_utf8( $entry->{MediaUrl} ) ) . '.jpg';
        my $filepath = $dir->file($filename);
        next if -f $filepath;
        print encode_utf8("$download_count : Download... $entry->{MediaUrl}\n");
        $res = $ua->get( $entry->{MediaUrl}, ':content_file' => $filepath->stringify );
        unless ( $res->content_type =~ m/^image/ ) {
            unlink $filepath;
        }
    }
    $page_count++;
}

さてこれでもたくさんの画像をダウンロードすることが出来るのですが、 「一枚のダウンロードが終わったら次の一枚」と順番に取得を行っているのでもう少し効率的にしたいところです。 今回はCoro::LWPというモジュールを使ってある程度並列にダウンロードするスクリプトも掲載します。

oppai_coro.pl

use strict;
use warnings;
use Coro;
use Coro::LWP;
use LWP::UserAgent;
use URI;
use JSON qw/decode_json/;
use Digest::MD5 qw/md5_hex/;
use Path::Class qw/dir file/;
use Encode;
use utf8;

my $appid = '';
my $uri   = URI->new('http://api.bing.net/json.aspx');
my $ua    = LWP::UserAgent->new;
my $dir   = dir('./data');

my $page_count     = 0;
my $download_count = 0;

while (1) {
    my $offset = $page_count * 50;
    $uri->query_form(
        AppId          => $appid,
        Version        => '2.2',
        Markert        => 'ja-JP',
        Sources        => 'Image',
        'Image.Count'  => 50,
        'Image.Offset' => $offset,
        Adult          => 'off',
        Query          => 'おっぱい'
    );

    my $res = $ua->get($uri);
    die $res->status_line if $res->is_error;
    my $ref = decode_json( $res->content );
    last unless defined @{ $ref->{SearchResponse}{Image}{Results} };

    my @coros;
    for my $entry ( @{ $ref->{SearchResponse}{Image}{Results} } ) {
        next unless $entry->{MediaUrl} =~ /\.jpg$/;
        $download_count++;
        my $filename = md5_hex( encode_utf8( $entry->{MediaUrl} ) ) . '.jpg';
        my $filepath = $dir->file($filename);
        next if -f $filepath;
        print encode_utf8("$download_count : Download... $entry->{MediaUrl}\n");
        push @coros, async {
            $res = $ua->get( $entry->{MediaUrl}, ':content_file' => $filepath->stringify );
            unless ( $res->content_type =~ m/^image/ ) {
                unlink $filepath;
            }
        };
    }
    $_->join for @coros;
    $page_count++;
}

Coroを使って並列にダウンロードすると以前のスクリプトと比べて3倍〜4倍!速くなりました。 わーい。

「いかにして大量のおっぱい画像をダウンロードするか」に答えるスクリプトを紹介しました。 APIの結果の上限が1000までなので、もっと集めるには工夫が必要ですが、興味のある人は各自で考えてみてくださいね。 僕は30,000枚くらい先日このスクリプトを応用してダウンロードしましたけど!

お知らせ

メルマガでもプログラミングに関する話題を取り扱いたいと思います。興味のある方はお試し購読を!