なんちゃってクローラーでURL漁り

前のエントリ
XvfbとFirefoxとImageMagickでWebページのサムネイルを自動生成する方法 - pomo123の日記
でWebページのサムネイルを自動的に取得できるようになったので、今度はサムネイルを作成するWebページのURLを自動的に漁ってくるなんちゃってクローラーを作ってみます。クローラーについてはまったくの素人なので、本当に作ってみただけになっております。

概要

出発点となるURLを与えてやると、そのページをダウンロードして、HTMLを解析してページに含まれるリンクを抽出します。抽出したリンクは取得すべきURLリストに追加します。次に、その取得すべきURLリストから1つURLを取り出して、以降はダウンロード、リンクの抽出、リストへの追加を繰り返します。このとき、ダウンロードしたURLをアウトプットとして出力します。

なお、ダウンロードを行う際に、”以前そのURLはダウンロード済みなら再度ダウンロードしない”という条件と、”指定した階層以上のダウンロードはしない”という条件をチェックします。ここで階層とはリンクを漁る範囲の上限を決めるためののもので、最初に与えたURLのページを階層=1とし、そこで抽出したリンク先のページを階層=2、さらにそのリンク先のページは階層=3という感じで増えていきます。

環境

これは前と同じです。

  • Vista SP1 + VMWarePlayer2.0.3
  • Ubuntu 7.10 (いろいろ手間を省くためにVMWare用のイメージを利用)

インストール

useしているモジュールのうち、ローカルにないものがあれば、cpanでインストールして下さい。

$ sudo cpan
cpan> install HTML::LinkExtor

それから、perlSSL通信するために、Synapticで以下のパッケージをインストールする必要があります。

  • libssl-dev (従属関係にあるパッケージも一緒にインストール)
  • libnet-ssleay-perl (こっちはもしかしたら不要かも)

さらにcpanでCrypt::SSLeayをインストールします。

cpan> install Crypt::SSLeay

問い合わせにはすべてEnterキーでOKです。

スクリプトと実行方法

以下のスクリプトを適当なファイル名(たとえばcrawler.pl)で保存して、出発点となるURLを引数に与えます。探索する階層の深さはとりあえず3に設定してあります。

$ perl crawler.pl http://hogehoge.hoge.com >url.txt 2>debug.log

url.txtにはクローラがあさったURLが出力されます。
debug.logにはwarnで書き出しているデバッグ情報を出力されます。そこそこの量のログをはきますので、必要に応じてスクリプトの中のwarnをコメントアウトしてください。

#! /usr/bin/perl -w

use strict;
use LWP;
use HTML::LinkExtor;
use URI::URL;
use Data::Dumper;

my @queue;		#ダウンロード予定のURLのリスト
my @downloaded_list;	#ダウンロード済URLのリスト
my @extract_links;	#パーズ中に抽出されたURL
my $MAX_LAYER = 3;	#最大階層深さ
my $SLEEP     = 0;

my $start_url = $ARGV[0] or die 'need url';

push @queue, ( $start_url, 1 );
pagedownload();

1;

sub callback {
    my ( $tag, %links ) = @_;
    if ( $tag eq "a" ) {
        push @extract_links, values %links;
    }
}

sub pagedownload {
    my $url   = shift @queue;
    my $layer = shift @queue;

    @extract_links = ();
    warn "Download URL:" . $url;
    warn "Layer:" . $layer;

    if ( $layer > $MAX_LAYER ) {
        warn "layer overflow!!";
        exit;
    }

    #ダウンロード済のURLにはアクセスしない
    my @tmp = grep { $_ eq $url } @downloaded_list;
    if ( @tmp > 0 ) {
        warn "Already Downloaded!!";
        pagedownload();
    }

    my $browser  = LWP::UserAgent->new;
    my $response = $browser->get($url);

    #ダウンロード済リストに追加
    push @downloaded_list, $url;
    print $url. "\n";

    sleep $SLEEP;

    my $p = HTML::LinkExtor->new( \&callback );
    $p->parse( $response->{_content} );


    #相対パスで指定されているリンクを絶対パスのURLに調整
    my $base = $response->base;
    @extract_links = map { $_ = url( $_, $base )->abs; } @extract_links;

    #javascript(0)とか除去
    my @extract_links2 = grep { /^https:|^http:/ } @extract_links;

#    warn join( "\n", @extract_links2 );
    warn "downloaded   " . $#downloaded_list;
    warn "tobedownload " . $#queue;
    warn "===============================================\n";

    #階層情報を付加してリストに保存
    my @tmp2 = map { ( $_, $layer + 1 ) } @extract_links2;
    push @queue, @tmp2;

    #再帰呼び出し
    pagedownload();
}

ご注意

本来クローラはrobot.txtやメタタグの記述を守ったりするお作法が必要ですが、ここでは未実装です。
さらに、URLの正規化とか、エラー処理も適当(というかなし)、パフォーマンスへの配慮のも特になしなので、このスクリプトを実行にあたっては自己責任にて対応お願いいたします。