Subscribed unsubscribe Subscribe Subscribe

Web::Queryのメソッド解説

解説する前に

あんなに便利なのにネット上に情報が少ないワケ

そもそもWeb::Queryは、jQueryというJavaScriptフレームワークと全く同じ使い方で使うことができる(jQueryを使い慣れた人にとっては学習コストがゼロに等しい)のがウリなものなので、「jQuery知ってれば解説はいらないっしょ?的な理由でネット上に情報が少なかったりする。まぁ、gistを探せばサンプルはあるっちゃぁあるけれど。

最新版を入れる

バージョンが上がっていろいろ変更が加わったので、最新版のものを入れなおしてください。

cpanm --reinstall Web::Query


さて、たとえば、こんな構造のHTMLをもったWebサイトがあったとして。(URLはhttp://localhost/

<html>
    <head>
    </head>
    <body>
        <div id="foo" class="block">
            <p>Foo</p>
        </div>
        <div id="bar" class="block">
            <p>Bar</p>
        </div>
        <div id="baz" class="block">
            <p>Bar</p>
        </div>
    </body>
</html>

要素を絞り込む

ここから任意の要素を抜き出すには findを使えばOK。

use Web::Query;
my $wq = wq('http://localhost/');

# 要素名で指定する
$wq->find('p');  # すべてのp要素
$wq->find('#foo');  # id="foo" が付いた要素
$wq->find('.class');  # class="block" が付いたすべての要素
$wq->find('p[class="block"]');  # class="block" が付いたすべてのp要素
$wq->find('p.block');  # 上の略記

findの引数には CSSセレクタを使う。もしCSSセレクタがよくわからければ、このへんを見ておけばOK。

Web::Queryオブジェクト

ちなみに、wq('url') も、find の結果も、すべて Web::Query オブジェクトを返す。
「Web::Queryオブジェクトである」ということは、共通して「ドキュメントに書いてあるWeb::Queryのメソッドを使える」ということを意味する。

use Web::Query;
my $wq = wq('http://localhost/');
$wq->html;  # <html><head></head><body><div id="foo" class="block">...

my $found = $wq->find('#foo');
$found->html;  # <p id="foo">Foo</p>

指定した要素のそれぞれに何らかの処理をする

  • >each メソッドを使えばOK。

たとえば、div要素のすべてのid一覧を取得したいとき:

use Web::Query;
# すべてのdiv要素を選択する
my $all_divs = wq('http://localhost/')->find('div');  # Web::Queryオブジェクト

# 要素のそれぞれに処理をする
my @all_ids;
$all_divs->each(sub {
    my $div = $_;  # $_ に Web::Queryオブジェクトが格納されている
    push @all_ids, $div->attr('id');  # attrメソッド:指定した属性の値を取り出す
});

use Data::Dumper;
warn Dumper(\@all_ids);  # ['foo', 'bar', 'baz']

ただ、こういう場合はいちいち配列にpushするのが面倒くさいので、mapメソッドを使うといい。
mapメソッドは、Web::Queryオブジェクトが持っている各要素を関数で処理して、新たな配列リファレンスを作成する。

use Web::Query;

my $all_ids = wq('http://localhost/')->find('div')->map(sub {
    return $_->attr('id');
});

use Data::Dumper;
warn Dumper($all_ids);  # ['foo', 'bar', 'baz']

凝った条件の部分集合を得る

たとえばこんなHTML構造のサイトがあったとする。(っていうか、クックパッドがこんな感じなんだけど)

<html>
    <body>
        <div id="step_1">
        </div>
        <div id="step_2">
        </div>
        <div id="step_3">
        </div>
        <div id="foo">
        </div>
        <div id="bar">
        </div>
    </body>
</html>

このHTMLから「id名がstepではじまるdiv要素だけほしい」という場合、どうすればいいか。
こういう場合は、Web::Queryのfilterメソッドを使えばOK。
filterメソッドは、要素集合から、引数で渡した関数で偽を返した要素を全て削除したものを返す。

use Web::Query;

my $all_step = wq('http://localhost/')->find('div')->filter(sub {
    my $elem = $_;  # 各div要素をもったWeb::Queryオブジェクト
    $elem->attr('id') =~ /^step/;
    # stepから始まる文字にマッチする正規表現
    # idがstepで始まる要素以外はここで偽(undef)が返るので要素集合から削除される
});

スクレイプしたいWebサイトのHTML構造を調べる方法

MacのChromeであれば、command + option(alt) + i で デベロッパーツールが立ち上がる。(もしくは、右上のレンチのアイコンをクリック -> ツール -> デベロッパーツール)
で、Elements -> 左下の虫眼鏡 を選択したあと、画面の適当な場所をクリックすると、HTML要素でどこにあたるのかが分かる。
f:id:Cside:20111127151421p:image

ほか、わからなければ

Skypeでもメールでもコメントでも聞いてください!