--- title: スタバの店舗情報をDBに登録するスクリプト author: kazu634 date: 2009-09-17 url: /2009/09/17/_1329/ wordtwit_post_info: - 'O:8:"stdClass":13:{s:6:"manual";b:0;s:11:"tweet_times";i:1;s:5:"delay";i:0;s:7:"enabled";i:1;s:10:"separation";s:2:"60";s:7:"version";s:3:"3.7";s:14:"tweet_template";b:0;s:6:"status";i:2;s:6:"result";a:0:{}s:13:"tweet_counter";i:2;s:13:"tweet_log_ids";a:1:{i:0;i:4783;}s:9:"hash_tags";a:0:{}s:8:"accounts";a:1:{i:0;s:7:"kazu634";}}' categories: - Perl - starbucks ---

ようやく完成しました。Web::Scraperって、奥が深いです。やろうと考えてから、実際にできあがるまで、かなり時間がたっているような気がする…が、あまり気にしないようにします。

ソース

#!/usr/bin/perl
use strict;
use Web::Scraper;
use URI;
use URI::Escape;
use utf8;
use YAML;
use Perl6::Say;
use Encode;
use DBI;
# =========================
# === 各県へのURLを取得 ===
# =========================
# starbucksのURLを指定
my $uri = URI->new("http://www.starbucks.co.jp/search/index.html");
# スクレイピングの設定を行う
my $scraper = scraper {
process '//area[@href=~/.+SearchPerfecture/]', 'prefs[]' => [
'@href',
        sub {
my $url         = $_->as_string;
my @url_split   = split( /=/, $url );
my $utf8_encode = pop(@url_split);
my $utf8        = uri_unescape($utf8_encode);
my $temp        = decode( 'utf8', $utf8 );
$temp = encode( 'shiftjis', $temp );
$temp = uri_escape($temp);
push( @url_split, $temp );
return join( '=', @url_split );
}
];
process '//td[@class="SelectFromPlace"]//a', 'cities[]' => '@href';
};
# スクレイピングの実行
my $result = $scraper->scrape($uri);
foreach my $page ( @{ $result->{'cities'} } ) {
my $scraper = scraper {
process '//map[@name=~/[^_]+_Map/]', 'city[]' => scraper {
process '//area[@href=~/.+result_city3.php/]', 'shops[]' => [
'@href',
                sub {
my $url = $_->as_string;
my @work;
if ( $url =~
/SearchPerfecture=([^\&]+)\&SearchCity=([^\&]+)\&SearchCity2=([^\&]+)&/
)
{
my $pref = uri_escape(
encode(
'shiftjis', decode( 'utf8', uri_unescape($1) )
)
);
my $city1 = uri_escape(
encode(
'shiftjis', decode( 'utf8', uri_unescape($2) )
)
);
my $city2 = uri_escape(
encode(
'shiftjis', decode( 'utf8', uri_unescape($3) )
)
);
$url =
s/SearchPerfecture=([^\&]+)\&SearchCity=([^\&]+)\&SearchCity2=([^\&]+)&/SearchPerfecture=$pref&SearchCity=$city1&SearchCity2=$city2&/;
return $url;
}
}
];
}
};
my $result = $scraper->scrape($page);
foreach my $city ( @{ $result->{'city'} } ) {
foreach my $page ( @{ $city->{'shops'} } ) {
# その県の店舗数を取得
my $scraper = scraper {
process
'id("Body")/div[@class="Code"]/table[@class="H3Table01"]/tbody[1]/tr[2]/td[1]/strong[2]',
'number' => 'TEXT';
};
my $result = $scraper->scrape( URI->new($page) );
# 店舗数に応じて、対応を変える
if (  == $result->{'number'} ) {
# 店舗数が0なら、何もしない
next;
}
# 店舗数が10店舗以下の場合
# そのページにしか店舗情報が存在しないので、そのページから情報を取得
elsif ( $result->{'number'} < 10 ) {
get_info($page);
sleep(3);
}
# 店舗数が10店舗より多い場合、
# そのページ以外にも店舗情報が存在するので、
# まずは店舗情報へのリンクをすべて取得する。
else {
$scraper = scraper {
process
'id("Body")/div[@class="Code"]/table[@class="ResultNavi"]/tbody[1]/tr[1]/td[2]/a',
'links[]' => [
'@href',
                        sub {
my $url = $_->as_string;
my @work;
# http://www.starbucks.co.jp/search/result_city.php?SearchPerfecture=%93%8C%8B%9E%93s&SearchCity=%8D%60%8B%E6&storelist=11
if ( $url =~
/SearchPerfecture=([^\&]+)\&SearchCity=([^\&]+)\&/
)
{
my $pref = uri_escape(
encode(
'shiftjis',
decode( 'utf8', uri_unescape($1) )
)
);
my $city = uri_escape(
encode(
'shiftjis',
decode( 'utf8', uri_unescape($2) )
)
);
$url =
s/SearchPerfecture=([^\&]+)\&SearchCity=([^\&]+)\&/SearchPerfecture=$pref&SearchCity=$city\&/;
return $url;
}
}
];
};
$result = $scraper->scrape( URI->new($page) );
foreach my $link ( @{ $result->{links} } ) {
get_info($link);
sleep(3);
}
}
}
}
}
# 各県のリンクをたどって、店舗情報を取得する
foreach my $page ( @{ $result->{prefs} } ) {
# その県の店舗数を取得
my $scraper = scraper {
process
'id("Body")/div[@class="Code"]/table[@class="H3Table01"]/tbody[1]/tr[2]/td[1]/strong[2]',
'number' => 'TEXT';
};
my $result = $scraper->scrape( URI->new($page) );
# 店舗数に応じて、対応を変える
if (  == $result->{'number'} ) {
# 店舗数が0なら、何もしない
next;
}
# 店舗数が10店舗以下の場合
# そのページにしか店舗情報が存在しないので、そのページから情報を取得
elsif ( $result->{'number'} < 10 ) {
get_info($page);
sleep(3);
}
# 店舗数が10店舗より多い場合、
# そのページ以外にも店舗情報が存在するので、
# まずは店舗情報へのリンクをすべて取得する。
else {
$scraper = scraper {
process
'id("Body")/div[@class="Code"]/table[@class="ResultNavi"]/tbody[1]/tr[1]/td[2]/a',
'links[]' => [
'@href',
                sub {
my $url = $_->as_string;
if ( $url =~ /SearchPerfecture=([^\&]+)/ ) {
my $utf8 = uri_unescape($1);
my $temp = decode( 'utf8', $utf8 );
$temp = encode( 'shiftjis', $temp );
$temp = uri_escape($temp);
$url =~
s/SearchPerfecture=([^\&]+)/SearchPerfecture=$temp/;
return $url;
}
}
];
};
$result = $scraper->scrape( URI->new($page) );
foreach my $link ( @{ $result->{links} } ) {
get_info($link);
sleep(3);
}
}
}
exit;
# ===================
# === sub routine ===
# ===================
sub get_info {
my $page = shift;
my $scraper = scraper {
process '//div[contains(@class, "Table01")]', 'stores[]' => scraper {
process '//tr[1]/td[2]',
'store_name' => [ 'TEXT', sub { s/^\s+//o; s/\s+$//o; } ];
process '//tr[2]/td[2]', 'place' => [
'TEXT',
                sub {
my $str = $_;
if ( $str =~ /(\d\d\d)[--](\d\d\d\d)(.+$)/ ) {
my $post_code = "$1-$2";
my $address   = $3;
$address =~ s/\s//g;
return {
'whole'     => $str,
'post_code' => $post_code,
'address'   => $address
};
}
}
];    # 818-0042
process '//tr[3]/td[2]',
'tel' => [ 'TEXT', sub { s/ ^ \s+//o; s/\s+$//o; } ];
process '//tr[4]/td[2]', 'nearby_station' => [
'TEXT',
                sub {
s/ ^ \s+//o;
s/\s+$//o;
}
];
process '//tr[5]/td[2]',
'open_close' => [ 'TEXT', sub { s/^\s+//o; s/\s+$//o; } ];
}
};
my $res = $scraper->scrape( URI->new($page) );
say YAML::Dump($res);
foreach my $x ( @{ $res->{'stores'} } ) {
# データベースへの接続
my $dbh =
DBI->connect( 'dbi:mysql:dbname=データベースの名前', 'ユーザ名', 'パスワード',
{ RaiseError => 1, AutoCommit =>  } );
# ステートメントハンドラの作成
# my $sth = $dbh->prepare("SELECT address FROM renoir WHERE address LIKE ?;");
my $sth = $dbh->prepare(
"INSERT INTO Starbucks (shopname, post_code, address, tel, hours, nearby) values (?, ?, ?, ?, ?, ?);"
);
$sth->execute(
$x->{'store_name'},         $x->{'place'}->{'post_code'},
$x->{'place'}->{'address'}, $x->{'tel'},
$x->{'open_close'},         $x->{'nearby_station'}
);
# ステートメントハンドラの解放
$sth->finish;
# データベースハンドラの解放
$dbh->disconnect;
}
}