M.C.P.C.

―むり・くり―プラスコミュニケーション(更新終了)


| トップページ |

2010年12月 7日 22:52

Adobe Creative Suite 出力対応店一覧をGoogle Mapsで表示させる(18)

このエントリーをはてなブックマークに追加 mixiチェック

アドビのCS5対応プリントショップのリストのページから、Google Mapsに表示させるやつの18回目。

前回は、たまたまいらしたアドビのいわもとさんに、「出力対応店一覧の住所まちがっとるよぷんぷん」言うたら、その日の夜に直してくれはった、仕事早すぎ、ていうことをやりました。

今回は、HTMLをスクレーピングして得られる住所から、Google Maps APIのジオコーダに問い合わせするところを、辞書にしてしまおう、ということをやってみたいと思います。


さて、Google Maps APIのジオコーダに住所をぶっこむと、緯度経度が取れるってことはさんざんやっているわけですけれども、この問い合わせの処理はコストが高く(この場合は待ち時間がかかる、という意味)、しかも問い合わせの間隔が短すぎると処理が拒否されるということが分かりました。これは、Google側では短時間に大量の利用をするのは避けてほしい、と言いたいんだと読み取れます。

というわけで、一度Google Maps APIから受け取ったデータを記録しておき、次に同じ問い合わせがあったときは、先に記録しておいたものを利用する、ということをやると、Google Maps APIへの問い合わせをする必要がなくなる、ということになります。

そのあらかじめ記録しておく、というのを、前回と同様、「辞書」と言います。

つまり、「山形県山形市蔵王温泉229」の項目をみると、「lat=38.161674,long=140.395135」と書いてあるものが辞書です。また、新しい住所をGoogleへ問い合わせした結果を辞書に追記していくことになります。

今回は、辞書が特定のファイルにどんどん書き込まれていき、後で再利用できるようにします。

さて、Perlで辞書と言えば連想配列(ハッシュ)ですが、この連想配列を、文字列にする「シリアライズ」というものをやってみます。

「シリアライズ」というのは、ドラゴンクエストで言うところの「ふっかつのじゅもん」みたいなもんです。

たとえば、

なまえ: もょもと
LV: 48
HP: 229
MP: 0
G: 27671
EX: 942197
STR: 155
DEX: 130
ATK: 155
DEF: 65

↓シリアライズ

ゆうていみやおうきむこうほりいゆうじとりやまあきらぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺぺ

みたいなやつです。

実際には、Perlの標準モジュールのData::Dumperモジュールを使います。

Filename: test19.pl

use strict;
use warnings;
use Data::Dumper;
 
my $param = {
  name => 'もょもと',
  LV   => 48,
  HP   => 229,
  MP   => 0,
  G    => 27671,
  EX   => 942197,
  STR  => 155,
  DEX  => 130,
  ATK  => 155,
  DEF  => 65,
};
{
  local $Data::Dumper::Indent = 0;
  local $Data::Dumper::Terse = 1;
  print Dumper( $param ). "\n";
}
exit;
 
__END__

実行結果:

$ perl test19.pl
{'STR' => 155,'EX' => 942197,'ATK' => 155,'DEF' => 65,'name' => 'もょもと','MP' => 0,'LV' => 48,'G' => 27671,'HP' => 229,'DEX' => 130}
$

Perlの構造体(この場合は連想配列のリファレンス)が、文字列に変換された状態(ふっかつのじゅもんに相当)です。今度は、逆に文字列から連想配列に変換します。

use strict;
use warnings;
use Data::Dumper;
 
my $text = "{'STR' => 155,'EX' => 942197,'ATK' => 155,'DEF' => 65,'name' => 'もょもと','MP' => 0,'LV' => 48,'G' => 27671,'HP' => 229,'DEX' => 130}";
my $data = eval( $text );
 
print "name: $data->{name}\nLV: $data->{LV}\n";
exit;
 
__END__

実行結果:

$ perl test20.pl
name: もょもと
LV: 48
$

このように、テキストから、連想配列(のリファレンス)に戻すことができました。

これを踏まえて、今までのプログラムを書き換えて、さらにJSONとして出力するようにしてみます。JSONは、これまた「ふっかつのじゅもん」のバリエーションで、Webブラウザで動くJavaScript用のシリアライズ表現です。

Filename: test21.pl

#!/usr/bin/perl
 
use strict;
use warnings;
use utf8;
use Web::Scraper;
use JSON;
use Encode;
use WebService::Simple;
use Data::Dumper;
 
my %dict = (
  '岐阜県岐阜市長良福田町1-30'
    => '岐阜県岐阜市福田町1丁目30',
  '京都府京都市下京区烏丸松原下ル五条烏丸町407-2烏丸KT第2ビル2F'
    => '京都府京都市下京区五条烏丸町407-2',
);
my $dict2 = {};
 
if ( open my $fh, '<', 'googlemaps.dump' ) {
  my $dump = decode_utf8(do { local $/; <$fh> });
  close $fh;
  $dict2 = eval $dump; 
}
 
my $geo = WebService::Simple->new(
    response_parser => 'JSON',
    base_url => 'http://maps.google.com/maps/api/geocode/',
    param    => {
      region   => 'jp',
      language => 'ja',
      sensor   => 'false',
    },
);
 
open my $fh, '<', 'printshop.html';
my $html = decode_utf8(do { local $/; <$fh> });
close $fh;
 
my $s = scraper {
    process '//div[@class="tabcontent"][1]//table[@class="data-bordered max"]/tbody/tr', 'codes[]' => scraper {
        process '//td[1]', pref    => [ 'TEXT', sub { s/\s//g; } ];
        process '//td[2]', company => [ 'TEXT', sub { s/\s//g; } ];
        process '//td[2]//a', url => [ '@href', sub { s/\s//g; } ];
        process '//td[3]', address => [ 'TEXT', sub { s/\s//g; s/TEL:.+$//i; return $_; } ];
        process '//td[3]', phone   => [ 'TEXT', sub { s/\s//g; if(m/TEL:([\d-]+)/i){return $1;};}];
        process '//td[3]', fax     => [ 'TEXT', sub { s/\s//g; s/:/:/g; if(m/FAX:([\d-]+)/i){return $1;};undef;}];
        process '//td[4]', illustratorcs5 => [ 'TEXT',  sub { return $_ =~m|yes|i ? 1 : undef; } ];
        process '//td[5]', indesigncs5    => [ 'TEXT',  sub { return $_ =~m|yes|i ? 1 : undef; } ];
        process '//td[6]', pdfx1a         => [ 'TEXT',  sub { return $_ =~m|yes|i ? 1 : undef; } ];
        process '//td[7]', pdfx4          => [ 'TEXT',  sub { return $_ =~m|yes|i ? 1 : undef; } ];
    };
};
 
my $scraped = $s->scrape($html);
 
my $data = [];
 
foreach my $item ( @{$scraped->{codes}} ) {
  my $address = $item->{address};
  my $res = geo_get($address);
  $item->{location} = $res; 
  push @$data, $item;
}
 
print encode_json($data);
 
exit;
 
sub geo_get {
  my $address = shift;
  my $result;
  if ( !$dict2->{$address} ) {
    my $res = $geo->get( 'json', { address => ( $dict{$address} or $address ) } );
    my $val = $res->parse_response();
    $result = $val->{results}->[0]->{geometry}->{location};
    $dict2->{$address} = $result;
    local $Data::Dumper::Indent = 0;
    local $Data::Dumper::Terse = 1;
    open my $fh, '>', 'googlemaps.dump' or die $!;
    print $fh Dumper($dict2);
    close $fh;
    sleep 1;
  }
  else {
    $result = $dict2->{$address};
  }
  return $result;
}
 
__END__

実行すると、

①最初に、googlemaps.dumpファイルを読み込み、$dict2に辞書として仕込みます。
②HTMLをスクレイピングして、住所一つ一つに問い合わせします。
③問い合わせは、まず$dict2の(中に入っているリファレンスの先にある)連想配列で辞書引きします。見つかれば、連想配列から、緯度経度を取り出します。見つからなければ、Googleに問い合わせ、結果を辞書に書き込みつつ、1秒ウェイトを取り、取得した結果を使います。

こんな感じの動作をします。初回は、Google Maps APIに問い合わせをしたあと1秒のウェイトが入りますので時間がかかりますが、2回目からは、Googleに問い合わせしに行くことがないので、とても速く結果が得られます。

次回は、今回のGoogle Maps APIの辞書と、その4でやった、アドビのサイトになるべく負担をかけないデータ取得を組み合わせ、Google Mapsとしてブラウザが表示するときに必要なJSONを吐き出すようなCGIを作ります。

投稿 大野 義貴 [GoogleMaps] | |

トラックバック(0)

トラックバックURL: http://blog.dtpwiki.jp/MTOS/mt-tb.cgi/3429

コメントする