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

akimachoのはてなブログ

ICTとデザインのためのブログ

Perlでスクレイピングしてみる - Web::Query

Perl CPAN Rebuild Web プログラム

はじめに

Web::QueryというCPANモジュールを使ってRebuild.fmの出演者さんの登場回数をカウントしてみたので、紹介します。

Web::Query

Web::Queryとは、スクレイピングを行うCPANモジュールです。

metacpan.org

jQueryライクなセレクタを指定することで、ほしい情報を取得することができます(CSSセレクタといってもいいかもしれません)。

とりあえずは以下の3つについて知っていればいいと思います。

セレクタ 表記 対応するHTMLタグの例
IDセレクタ #idValue id="hoge"
クラスセレクタ .className class="foo"
タイプセレクタ element h1, divなど

例えば、http://example.com/のページからh1タグでマークアップされたテキストを取得する場合には以下のようになります。

use Web::Query;
# Web::Queryインスタンスを生成する
# Web::Query->newと同じ
my $wq = wq('http://example.com/');
print $wq->find('h1')->text, "\n";#=> "Example Domain"

このようにしてfindメソッドの引数にセレクタを渡して、ほしい情報を指定してあげます。

size(), eq()

では、セレクタに該当するコンテンツが複数あった場合はどうすればよいのでしょうか?

まずsizeメソッドを使うことで、該当するコンテンツの個数を取得することができます。そして各情報にアクセスするにはeqメソッドを使うことになります。eqの引数はスクレイピングしたコンテンツのインデックスです。

以上、sizeとeqを使うことで複数のコンテンツにアクセスすることができるようになります。

my $wq = wq('http://example.com/');
my $res = $wq->find('p');
for (my $i = 0; $i < $res->size; $i++) {
    print $res->eq($i)->text, "\n";
}

each()

ですが、Perlではfor (my $i=0; $i < n; $i++)のようにfor文を使うことは推奨されていません。

404 Blog Not Found:perl - for(;;)よりforeach

このようなfor文を避けるには、eachメソッドを使います。eachメソッドの引数はサブルーチンリファレンス(コードリファレンス)です。

sub {
  print $_->text, "\n";
}

eachメソッドに渡されたサブルーチンリファレンスの特殊変数$_には、デフォルト引数として各コンテンツがセットされることになります。

my $wq = wq('http://example.com/');
my $res = $wq->find('p');
$res->each(sub {
    print $_->text, "\n";
});

こうすることで取得できたコンテンツの数を意識しないで各コンテンツにアクセスすることができるわけですね。

Rebuild

話は変わりまして、みなさん、Rebuild.fmというpodcastを知ってますか?

rebuild.fm

Rebuild.fmは、ホストのmiyagawaさんが毎回ゲストを読んでとプログラミングやガジェットについて話すpodcastです。

宮川達彦 - Wikipedia

技術ネタに対して「他の言語ではこうなっている」、「昔はこういうものがあった」といった話が聞けてソフトウェアエンジニアがどんなことを考えているのか知ることができます。

中には知らなかったトピックも出てくるので、調べるきっかけにもなって勉強になります。例えば私はRebuildを通してHacker Newsというニュースサイトを知って、毎日目を通すようになりました。

Hacker News

あとRebuildには本編の他にAftershowという音楽やマンガ、アニメなどメインテーマから少し外れたことについておしゃべりする回ありまして、肩の力を抜いたのんびりトークが聞けて気分転換にもなります。

今日(2015/06/28)のLive放送を聞いたのですが、伊藤直也(naoya)さんからshirobakoの感想をやっと聞くことができてテンションが上がりました。IT版shirobako、ぜひみたいです(笑い)。この回は、たぶん98回目のAftershowになると思います。

スクレイピングしてみよう

さて、Rebuildではたくさんのゲストが登場するのですが、上で紹介したWeb::Queryを使って出演回数をカウントしてみました。

#!/usr/bin/env perl
use strict;
use warnings;
use Web::Query;

# 出演者リストを取得する
sub checkkStars {
    my $uri = shift;
    my $res;
    eval { $res = wq($uri)->find('.person .name') };
    if ($@) {# 404 Not Foundの場合
        return [];
    }
    my @stars;
    $res->each(
        sub {
            my $star = $_->text;
            push @stars, $star;
        }
    );
    return \@stars;
}

# 出演者リストから登場回数をカウントする
sub countStars {
    my ($counter, $casts) = @_;
    for my $cast (@$casts) {
        $counter->{$cast}++;
    }
}

my $base_uri = 'http://rebuild.fm/';
my %counter;# 各キャストの出演回数を記録するハッシュ
for my $i (1 .. 100) {
    my $uri = $base_uri . $i . '/';
    countStars(\%counter, checkkStars($uri));
    sleep(1);
    if ($i >= 45) {# 第45回目以降でないとAftershowはない
        $uri = $base_uri . $i . 'a/';
        countStars(\%counter, checkkStars($uri));
        sleep(1);
    }
}

# 降順で整列して出力
for my $key (sort { $counter{$b} <=> $counter{$a} }keys %counter) {
    print $key, ',', $counter{$key}, "\n";# CSV形式で
}

余談ですが、数十回ほど各ページにアクセスするのでサーバに迷惑をかけるのではないかと思い、スクレイピングのマナーについて調べてみました。

qiita.com

上のサイトでは、

  • robots.txtがない場合でも、サーバアクセスの間隔を1秒以上空けるようにする。

とあるので各ページへのアクセスはsleep(1)のようにして1秒以上間隔をあけるようにしたほうがいいみたいです。

結果

降順に整列した結果、以下のようになりました。やはりnaoyaさんが一番でした。naanさん、hakさん、Nさんも多いですね。あとmiyagawaさんは出演回数141回でダントツ1位なのですが、ゲストではないので省略しました。

出演者名 出演回数
naoya 32
naan 25
hak 20
N 14
kenn 11
fumiakiy 6
omo 6
typester 5
gfx 5
matz 4
gosukenator 4
hotchpotch 4
yusukebe 4
r7kamura 3
lestrrat 2
satoru 2
mootoh 2
tagomoris 2
tokuhirom 2
dice 2
kansai_takako 2
hmsk 2
a_matsuda 2
modocache 2
deeeet 2
honmax 1
rjbs 1
nagayama 1
tenderlove 1
mizchi 1
drikin 1
frsyuki 1
sekimura 1
TimToady 1
zzak 1
kiyoto 1
mrkn 1
adamrocker 1
obra 1

おわりに

長くなりましたが、Web::Queryを使えば気軽にスクレイピングができるのでおすすめです。

前にWeb::Scrapperというモジュールを紹介したのですが、JQueryを使ったことがある人にはこちらのほうが使いやすく感じると思います。

akimacho.hatenablog.com

Rebuildに関しては、あともう少しで記念すべき第100回ですね。なんかやるのでしょうか。