固定電話の市外局番一覧ページを csv にするお役立ち hack!

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

こんにちは!

今日のお題は、「総務省の固定電話の市外局番一覧から csv を作る」です。

(以下の各コードでは、前提として $html に、utf-8 なデータが入っているものとします。)

正規表現を使ってスクレイプ

まずは、正規表現でやってみます。

とにかく書くのが面倒なので、td タグの中身を取り出して、そこからテキストを抽出する方針でいきます。

HTML エンティティのデコード(HTML::Entities の decode_entities 等でデコードすることになりますが、今回は   しか出てこないのでベタに置換しました)や、いらないタグの除去やら、いろいろ自分でやることになります。

タグの配置が少し変わっただけで、正規表現の書き直しになることが多く、メンテナンス時にうんざりするかと思います。

    my ($data) = $html =~ m!<table[^>]+border=1[^>]+>(.*?)</table>!s;

    my $scraped;
    while ($data =~ m!<tr[^>]+>\s*<td[^>]+>(.+?)<td[^>]+>(.+?)<td[^>]+>(.+?)<td[^>]+>(.+?)</tr>!sgi) {
        my $code;
        @{ $code }{qw/areacode areas telcode telcodetype/} = ($1, $2, $3, $4);
        for (values(%$code)) {
            s/&nbsp;/ /sg;
            s/<[^>]+>//sg;
            s/\s+//sg;
        }
        push @{ $scraped->{codes} }, $code;
    }

Web::Scraper を使ってスクレイプ

次に、miyagawa さんの神モジュール Web::Scraper を使った方法です。

たったこれだけのコードを書くだけで、ほんとサクっとデータを取れちゃいます。

正規表現を書くのと比べて、とてもシンプルにできますね!

    my $s = scraper {
        process '//table[@border="1"]//tr', 'codes[]' => scraper {
            process '//td[1]', areacode    => 'TEXT';
            process '//td[2]', areas       => 'TEXT';
            process '//td[3]', telcode     => 'TEXT';
            process '//td[4]', telcodetype => 'TEXT';
        };
    };

    my $scraped = $s->scrape($html);

    for my $code (@{ $scraped->{codes} }) {
        for (values(%$code)) {
            s/\s+//sg;
        }
    }

Web::Scraper を爆速に!

CPAN にある Web::Scraper だと PurePerl なモジュールを使っているため、どうしてもちょっとだけ重くなります。

そういうときのために、前にこそっとつくった (僕がいつも使ってる) XML::LibXML を使えるようにした Web::Scraper を使いたいと思います。

スクリプトから use lib 等して libxml ブランチ を読み込むようにして、

Web::Scraper->use_libxml(1);

とクラスメソッドを呼ぶだけで、libxml2 の parser が使われるようになります。

libxml2 ブランチについて詳しくは Web::Scraper を XML::LibXML で爆速にする hack! - woremacxの日記 をごらんください。

ベンチしてみる

あとあとのメンテナンスでうんざりすることになる正規表現が最速なのはさておき、C で書かれたライブラリである libxml2 の威力はすごいですね!ほれぼれしちゃいますね!

$ perl bench.pl
0 Default    : 47 wallclock secs (45.86 usr +  0.02 sys = 45.88 CPU) @  0.22/s (n=10)
1 XML::LibXML:  4 wallclock secs ( 4.14 usr +  0.01 sys =  4.15 CPU) @  2.41/s (n=10)
2 regexp     :  1 wallclock secs ( 1.01 usr +  0.01 sys =  1.02 CPU) @  9.80/s (n=10)

よいスクレイピングライフを!

ベンチに使ったコード

#!/usr/bin/perl

use strict;

use lib qw(libxml/lib);

use FileHandle;
use LWP::UserAgent;
use Web::Scraper;
use Benchmark;

my $html;

sub write_csv {
    my ($name, $data) = @_;

    open my $csv, ">:encoding(utf8)", $name or die $!;
    for my $code (@$data) {
        printf $csv qq{"%s","%s","%s","%s"\n}, @{$code}{qw/areacode areas telcode telcodetype/};
    }
    close $csv;
}

sub run_scraper {
    my $use_libxml = shift;
    Web::Scraper->use_libxml($use_libxml);

    my $s = scraper {
        process '//table[@border="1"]//tr', 'codes[]' => scraper {
            process '//td[1]', areacode    => 'TEXT';
            process '//td[2]', areas       => 'TEXT';
            process '//td[3]', telcode     => 'TEXT';
            process '//td[4]', telcodetype => 'TEXT';
        };
    };

    my $scraped = $s->scrape($html);

    for my $code (@{ $scraped->{codes} }) {
        for (values(%$code)) {
            s/\s+//sg;
        }
    }

    write_csv($use_libxml ? "libxml.csv" : "default.csv", $scraped->{codes});
}

sub run_regexp {
    # 37.45/s
    my ($data) = $html =~ m!<table[^>]+border=1[^>]+>(.*?)</table>!s;

    my $scraped;
    while ($data =~ m!<tr[^>]+>\s*<td[^>]+>(.+?)<td[^>]+>(.+?)<td[^>]+>(.+?)<td[^>]+>(.+?)</tr>!sgi) {
        my $code;
        @{ $code }{qw/areacode areas telcode telcodetype/} = ($1, $2, $3, $4);
        for (values(%$code)) {
            s/&nbsp;/ /sg;
            s/<[^>]+>//sg;
            s/\s+//sg;
        }
        push @{ $scraped->{codes} }, $code;
    }

    write_csv("regexp.csv", $scraped->{codes});
}

sub main {
    my $file = "shigai_list.html";
    my $url = "http://www.soumu.go.jp/joho_tsusin/top/tel_number/shigai_list.html";

    unless (-e $file) {
        my $ua = LWP::UserAgent->new;
        $ua->get($url, ':content_file' => $file);
    }

    open my $fh, "<:encoding(cp932)", $file or die $!;
    $html = do { local $/; <$fh>; };
    close $fh;

    timethese(10, {
        '0 Default    ' => sub { run_scraper(0); },
        '1 XML::LibXML' => sub { run_scraper(1); },
        '2 regexp     ' => sub { run_regexp; },
    });
}

main;

No TrackBacks

TrackBack URL: http://perl-mongers.org/MT/mt-tb.cgi/43

Leave a comment

About this Entry

This page contains a single entry by woremacx [vaginarepos.org] published on June 2, 2008 9:16 PM.

perlでmicroformats was the previous entry in this blog.

Hashを使ってユニークにしよう! is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Categories

Pages

Creative Commons License
This blog is licensed under a Creative Commons License.
Powered by Movable Type 4.21-en