【個人開発】Google Places APIの限界を超える「座標グリッド検索」を作った話
はじめに
「全国の金属工場をリストアップしたい」
営業リスト作成のために Google Places API を使い始めたのですが、すぐに壁にぶつかりました。APIの仕様上、どうしても「漏れ」が発生してしまうのです。
この記事では、その壁を乗り越えるために設計・実装した「座標グリッド検索システム」について、ぶつかった問題から解決策まで書いていきます。
ぶつかった壁
1. Googleの「Type」問題
Google Places API には type パラメータがあり、restaurant や hospital などのカテゴリで絞り込みができます。
しかし、Googleが決めたカテゴリは日本の実情に合っていないことが多いのです。
例えば:
- ドラッグストアは今やスーパー的存在だが、
drugstoreとsupermarketは別カテゴリ - 「金属工場」のような業種はそもそも適切なtypeが存在しない
結局、type 指定ではなく キーワード検索(Text Search API) を使うことにしました。
2. 「60件の壁」
Places API は1回の検索で最大60件しか返しません(20件 × 3ページ)。
密集地域で検索すると、すぐにこの上限に達してしまいます。上限に達したということは、本来ヒットするはずの店舗が取得できていないということです。
3. 駅名ベース検索の限界
最初は「東京駅から30km」「大阪駅から30km」のように、主要駅を起点に検索していました。
しかし、この方法には問題がありました:
【駅名ベースの問題点】
1. 県境で漏れが発生
→ 埼玉と群馬の県境付近は、どちらの駅からも遠い
2. 検索クエリにバイアスがかかる
→ 「埼玉県 久喜駅 金属工場」と検索すると
Googleが「埼玉県」を重視してしまい、
県境の群馬側の工場が出てこない
3. ポイント+距離では「面」を網羅できない
→ 円と円の隙間ができる
設計思想:駅名から座標へ
発想の転換
駅名ベースをやめて、純粋に座標と半径だけで検索する方式に切り替えました。
# 従来:駅名ベース
query = "埼玉県 久喜駅 金属 工場" # ← 県名がバイアスになる
# 新方式:座標ベース
query = "金属 工場"
location = "36.0621,139.6669" # ← 任意の座標
radius = 20000 # ← 密度に応じて調整
これにより、県境を意識せず、純粋に「面」として検索できるようになりました。
地域密度に応じた動的グリッド
日本全国を均一なグリッドで区切ると、関東は検索しすぎ、北海道は検索し足りない、ということになります。
そこで、地域の密度に応じてグリッドの細かさを変える設計にしました。
REGION_CONFIG = {
"北海道": {
"radius_km": 50, # 過疎地域 → 大きい半径
"grid_step_km": 100, # 粗いグリッド
},
"関東": {
"radius_km": 20, # 密集地域 → 小さい半径
"grid_step_km": 40, # 細かいグリッド
},
"近畿": {
"radius_km": 20, # 密集地域
"grid_step_km": 40,
},
# ... 他の地域も同様
}
「重複なし・漏れなし」の設計
グリッドの間隔をどう決めるか?
grid_step = radius × 2 とすることで、円がちょうど接する形になり、重複なし・漏れなしを実現しています。
grid_step = 40km, radius = 20km の場合:
┌──20km──┐
●────────●────────●
│ ○ │ ○ │ ← 円がちょうど接する
●────────●────────●
│ ○ │ ○ │
●────────●────────●
グリッド点間隔 = 半径 × 2 = 40km
実装のポイント
緯度による経度補正
地球は球体なので、緯度が高くなるほど経度1度あたりの距離が短くなります。
北海道と沖縄で同じ「50km東へ移動」をするなら、経度の増分を調整する必要があります。
import math
def offset_coords(lat, lng, km_north, km_east):
"""座標を指定km移動"""
# 緯度方向:1度 ≒ 111km(どこでもほぼ一定)
new_lat = lat + (km_north / 111)
# 経度方向:緯度によって1度あたりの距離が変わる
new_lng = lng + (km_east / (111 * math.cos(math.radians(lat))))
return round(new_lat, 4), round(new_lng, 4)
place_id による重複除去
複数のグリッドポイントから検索すると、同じ店舗が重複してヒットします。
Google Places API が返す place_id は店舗ごとに一意なので、これを使って重複を除去しています。
seen_place_ids = set()
all_results = []
for point in grid_points:
results = search_by_coords(point['lat'], point['lng'], ...)
for r in results:
place_id = r.get('place_id')
if place_id and place_id not in seen_place_ids:
seen_place_ids.add(place_id)
all_results.append(r)
落とし穴:3万円事件
開発中、Google Cloud から約3万円の請求が来て驚きました。
原因は単純で、テストや調整を繰り返すうちにAPI呼び出し回数が膨れ上がっていたのです。無意識に操作していて、月末近くに請求画面を見てびっくり、というやつです。
教訓:
- Google Cloud Console で予算アラートを必ず設定する
- 1日あたりの上限も設定しておく
- 開発中は本番用とは別のプロジェクトを使う
Places API は1リクエストあたり約$0.032(Text Search の場合)。1,000回呼べば$32です。全国検索を何度かテストすれば、あっという間に数万円になります。
成果
最終的に、以下のようなシステムが完成しました:
| 項目 | 内容 |
|---|---|
| グリッドポイント | 全国約80ポイント |
| API呼び出し | 約240回(80点 × 3ページ) |
| 推定コスト | 1回の全国調査で約$8 |
| 実行環境 | Ubuntu + Flask で自前Webサーバー |
座標を指定して、距離とカテゴリを入れれば検索できるUIも作りました。方向キーで座標を移動しながら調査できるので、「この辺をもう少し細かく調べたい」という場合にも対応できます。
まとめ
Google Places API は便利ですが、「60件の壁」や「検索バイアス」など、全国規模のデータ収集には向いていない面もあります。
今回は、座標ベースのグリッド検索という方法でその限界を超えることができました。
ポイントをまとめると:
- 駅名ベース → 座標ベースで県境問題を解消
- 地域密度に応じた動的グリッドで効率的にカバー
-
grid_step = radius × 2で重複なし・漏れなし - place_id で重複除去
- 課金上限の設定は最初にやる(教訓)
同じような課題を持っている方の参考になれば幸いです。
使用技術:Python, Flask, Google Places API (Text Search)

