--- 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; } }