この日記にあるもの

わたしはだれ

サイドバーのプロファイルをご覧下さい。

アイマス関連の活動、動画やSSはねこふみにて。

ニコマス関連の活動は、ニコニコ部で。

日常のつぶやきはTwitterに。

データベース

アイドルマスター」タグ付き全動画情報データベースの更新状況と公開場所は「ニコニコ部の日記」です。
データベースについての説明はこちらです。
なお、データベース収集・分析のアイデアがありましたらお声をかけてください。

試験的にデータベースの更新をお知らせするRSSを出力しています。

http://aoineko.ciao.jp/nicodata.rss

GMスクリプト

更新が面倒なのでサボってます……。

Fast Look up NicoInfoは、マウス選択箇所やリンク先の動画情報(ニコニコ動画のもの)を表示します。

Pixiv Slideshowは、Pixivの「お気に入りユーザー新着イラスト」ページに“スライドショウ”を追加します。スライドショウでは「次へ」「前へ」のリンクを使って、イラストを見ることができます。

AlbumArtDownloaderのAmazon(.jp)を直した

foobar2000から連携させて使っているAlbumArtDownloaderのAmazon(.jp)スクリプトが動かなくなったので直した。

# amazon-common.boo 37行目
#resultsPage = GetPage(GetPageStream(url, null, true), PageEncoding)
resultsPage = GetPage(GetPageStream(url, null, true)

# amazon-jp.boo
override protected def GetUrl(artist as string, album as string) as string:
	return "http://www.amazon.co.jp/gp/search?search-alias=popular&field-artist=${EncodeUrl(artist)}&field-title=${EncodeUrl(album)}&sort=relevancerank"

何をやったかというと、Amazonが返してくる結果がUTF8になっていたのでCP932で扱っていたところをUTF8にするようにした。

UTF8だとSystem.Text.Encoding.GetEncoding()ではなくSystem.Text.Encoding.UTF8.GetString()となる。
UTF8時のGetEncoding()に相当する処理を探せ出せなかったので、共通モジュールutil.booを追いかけた。

def GetPage(url as string):
	return GetPage(GetPageStream(url))
def GetPage(pageStream as Stream):
	return GetPage(pageStream, Encoding.UTF8)
def GetPage(pageStream as Stream, encoding as Encoding):
	return StreamReader(pageStream, encoding).ReadToEnd()


def EncodeUrl(url as string):
	return EncodeUrl(url, Encoding.UTF8)

def EncodeUrl(url as string, encoding as Encoding):
	return System.Web.HttpUtility.UrlEncode(url, encoding)

ということで、引数encodingを与えなければUTF8として扱ってくれるようなので、そのように修正した。
できればamazon-common.booには手を入れたくなかったが、いつもAmazon.co.jpの結果しか使っていないからまぁいいか。

なお、booスクリプトのコードにsyntax highlightが対応していないようなのでpythonにしてあります。booスクリプトのページにpython likeと書いてあったので。

シンデレラガールズの勢いをちょっと見てみる in pixiv

モゲマスのキャラがどれだけ流行ってるのかなーと思ったので、ざっとタグデータを拾ってみました。見ての通り、3つ以上出現したタグを一覧で。

perlのWWW::MechanizeとWWW::Scraperで小一時間ほどでした。スクリプトはこちら

タグ 登場数
アイドルマスターシンデレラガールズ 180
双葉杏 37
三村かな子 32
ぽっちゃり 24
諸星きらり 9
天海春香 8
小日向美穂 7
龍崎薫 6
上条春菜 6
島村卯月 6
巨乳 6
奥山沙織 6
前川みく 6
神崎蘭子 5
萩原雪歩 5
川島瑞樹 5
C81 5
ナターリア 5
福山舞 5
如月千早 4
水本ゆかり 4
渋谷凛 4
中野有香 4
喜多見柚 4
柊志乃 4
我那覇響 4
だいたいあってる 4
星井美希 3
三船美優 3
双海真美 3
佐城雪美 3
赤城みりあ 3
佐々木千枝 3
城ヶ崎莉嘉 3
ふともも 3
ふとましい 3
緒方智絵里 3
神谷奈緒 3
東郷あい 3
響をいじめ隊 3

OK,時代はニートとぽっちゃりだ!

あと、何気に春香さん強い。そして響はいじめたいほどかわいい!

すでにC81というタグが見える当たり、みなさん行動が早いですね。冬コミ後にもっと変化が出るかも。

測ってみた

前回のエントリーブクマコメントに、id:gfxさんからCoroからFurlを使う方法を書いていただいていたので、同じ環境でベンチしてみました。

gfx++

Coro+Furlのコードはhttps://gist.github.com/665488から。

use strict;
use warnings;
use 5.0100;
use Furl;
use AnyEvent::HTTP;
use Coro;
use Coro::Select;
use Coro::AnyEvent;
use Coro::Semaphore;
use Benchmark qw/timethese cmpthese/;


# request
my $url = 'http://ext.nicovideo.jp/api/getthumbinfo/';
my $retry = 3;

open my $fh, '<:utf8', '_tmp';
my @list;
for (0 .. 100) {
	my $line = <$fh>;
	chomp $line;
	push @list, $url . $line;
}
close $fh;

cmpthese timethese 1, {
	CoroFurl => sub {
		my @coros;
		foreach my $url(@list) {
			chomp $url;
		    push @coros, async {
		        print "fetching $url\n";
		        my $furl = Furl->new();
		        my $res = $furl->get($url);
		        print "$url: ", $res->status_line, "\n";
		    };
		}

		$_->join for @coros;
	},
	Coro => sub {
		my $sem = Coro::Semaphore->new(5);
		my @coros;
		for my $video_id (@list) {
			chomp $video_id;
			push @coros, async {
				my $guard = $sem->guard;
				http_get $video_id, Coro::rouse_cb;
				my ($data, $hdr) = Coro::rouse_wait;
				if ($hdr->{Status} =~ /^2/) {
					 #print "finish, $hdr->{Status} $hdr->{Reason} $hdr->{URL}\n";
				} else {
					 warn "error, $hdr->{Status} $hdr->{Reason} $hdr->{URL}\n";
				}
			};
		}
		$_->join for @coros;
	},
};

で、結果。Furlは早いし、Coro::Select使うだけで簡単でいいですね。

Benchmark: timing 1 iterations of Coro, CoroFurl...
      Coro: 22 wallclock secs ( 0.38 usr +  0.11 sys =  0.49 CPU) @  2.06/s (n=1)
            (warning: too few iterations for a reliable count)
  CoroFurl:  8 wallclock secs ( 0.05 usr +  0.05 sys =  0.09 CPU) @ 10.64/s (n=1)
            (warning: too few iterations for a reliable count)
           Rate     Coro CoroFurl
Coro     2.06/s       --     -81%
CoroFurl 10.6/s     416%       --

APIへの問い合わせを高速化したい

結論

Furlは非常に速い。でも、AnyEventの方が仕事が早く終わった。

今回は、相手サーバーが応答してデータを取得するまでの時間の方がボトルネック

なお、結果として次の動画を作るのにとっても役に立った。

ときどきAEでAPI叩いているとエラーが返ってきましたけど。

背景

nicomas.sqlite用にデータを取得するperlスクリプトの実行に時間がかかっているのを何とかしたい。

135,000件以上の「アイドルマスター」タグのついた動画があるので、時間がかかるのはしょうがないけど、工夫できないだろうか。

なお、環境はstrawberry perl 5.12.1 built for MSWin32-x86-multi-threadをWindows XPで動かしています。

現状

  1. タグ検索ページをスクレイピングして動画IDを取得
  2. 動画IDリストをもとに、APIへ問い合わせ
  3. 取得データをパースしてDBに格納

一番時間がかかるのはタグ検索ページのスクレイピング。でもこれはニコニコ動画側の負荷を考えてウェイトをいれているし、これ以上早くする気は無い。


二番目がAPI問い合わせなので、ここを改善する。コードはだいたい下のとおり。

# もともとはLWPを使った、いたって普通のコード
my $enc = Encode::find_encoding('utf8');
my $ua = LWP::UserAgent->new;
for my $url (@urls) {
    my $res = $ua->get($url);
    if ($res->is_success) {
        my $content = $enc->decode('utf-8', $res->content);
        # do something
    }
}

Furl

参考情報とベンチマーク

にひりずむ::しんぷる - 初めての Furlを参考にした。
Kazuho's Weblog: 5x performance - switching from LWP to Furl & Net::DNS::LiteのNet::DNS::Liteを使う方法は、Net::DNS::Liteが上手く動いてくれなかったので試していない。

さくっとベンチマークを取ってみた。実際に使うサブルーチンをコピペした。

なお、localhost:5000ではplackupでhttp://ext.nicovideo.jp/api/gettumbinfo/sm9のデータを返すように書いたpsgiを動かしている。

use strict;
use warnings;
use 5.0100;
use Furl::HTTP;
use LWP::UserAgent;
use Benchmark qw/timethese cmpthese/;

# setup vars
my $timeout_in_seconds = 5;
my $count = 1000;

# create HTTP object
my $furl = Furl::HTTP->new(
    timeout   => $timeout_in_seconds,
);

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

# request param
my $url = 'http://localhost:5000/';
my $retry = 3;


cmpthese timethese $count, {
	Furl => sub {
		furl_get($furl, $url, $retry);
	},
	LWP => sub {
		lwp_get($ua, $url, $retry);
	},
};

sub furl_get {
	my ($furl, $url, $retry, $wait) = @_;
	$retry ? $retry += 1 : $retry = 1;
	$wait = 0 unless $wait;

	my ($minor_version, $status, $message, $headers, $content);
	while ($retry) {
		($minor_version, $status, $message, $headers, $content) = $furl->request(
		    method => 'GET',
		    url    => $url,
		);
		if ($status == 200) {
			last;
		} else {
			$retry--;
			sleep $wait if $wait;
		}
	}
	return ($status, $content);
}

sub lwp_get {
	my ($lwp, $url, $retry, $wait) = @_;
	$retry ? $retry += 1 : $retry = 1;
	$wait = 0 unless $wait;

	my $res;
	while ($retry) {
		$res = $lwp->get($url,);
		if ($res->is_success) {
			last;
		} else {
			$retry--;
			sleep $wait if $wait;
		}
	}
	return ($res->code, $res->content);
}

結果はこちら。

Benchmark: timing 1000 iterations of Furl, LWP...
      Furl:  3 wallclock secs ( 0.78 usr +  0.48 sys =  1.27 CPU) @ 789.89/s (n=1000)
       LWP:  6 wallclock secs ( 2.42 usr +  0.76 sys =  3.19 CPU) @ 313.77/s (n=1000)
      Rate  LWP Furl
LWP  314/s   -- -60%
Furl 790/s 152%   --

AnyEvent + Coro編

use strict;
use warnings;
use 5.0100;
use Furl::HTTP;
use AnyEvent::HTTP;
use Coro;
use Coro::AnyEvent;
use Coro::Semaphore;
use Benchmark qw/timethese cmpthese/;

# 中略

open my $fh, '<:utf8', 'video_id_list.txt';
my @list;
for (0 .. 100) {
	my $line = <$fh>;
	chomp $line;
	push @list, $url . $line;
}
close $fh;

cmpthese timethese $count, {
	Furl => sub {
		for (@list) { furl_get($furl, $_, $retry) }
	},
	Coro => sub {
		my $sem = Coro::Semaphore->new(5);
		my @coros;
		for my $video_id (@list) {
			chomp $video_id;
			push @coros, async {
				my $guard = $sem->guard;
				http_get $video_id, Coro::rouse_cb;
				my ($data, $hdr) = Coro::rouse_wait;
				if ($hdr->{Status} =~ /^2/) {
					 #print "finish, $hdr->{Status} $hdr->{Reason} $hdr->{URL}\n";
				} else {
					 warn "error, $hdr->{Status} $hdr->{Reason} $hdr->{URL}\n";
				}
			};
		}
		$_->join for @coros;
	},
};

sub furl_get {
	my ($furl, $url, $retry, $wait) = @_;
	$retry ? $retry += 1 : $retry = 1;
	$wait = 0 unless $wait;

	my ($minor_version, $status, $message, $headers, $content);
	while ($retry) {
		($minor_version, $status, $message, $headers, $content) = $furl->request(
		    method => 'GET',
		    url    => $url,
		);
		if ($status == 200) {
			last;
		} else {
			$retry--;
			#sleep $wait if $wait;
		}
	}
	return ($status, $content);
}

AnyEvent::HTTPは同一ホストへのアクセスを4に制限しているので、セマフォで並列を5にする必要はなさそうです。ちょっとCoroを触ってみたかったので、上のようなコードになっています。

結果はこちら。

Benchmark: timing 1 iterations of Coro, Furl...
      Coro: 24 wallclock secs ( 0.25 usr +  0.09 sys =  0.34 CPU) @  2.91/s (n=1)
            (warning: too few iterations for a reliable count)
      Furl:  6 wallclock secs ( 0.08 usr +  0.08 sys =  0.16 CPU) @  6.41/s (n=1)
            (warning: too few iterations for a reliable count)
       Rate Coro Furl
Coro 2.91/s   -- -55%
Furl 6.41/s 121%   --

ご覧の通り、Furlの方が処理は速いけど、AnyEvent::HTTPの方が全ての仕事をやり終えるのが早い。ローカルでデータをパースしたりするともっと差が出るようです。

検証が足りない気もするけど、自分の用途では役に立ったのでここまで。なお、データ収集スクリプトでの実戦投入はまだやってません。