mcpField
G
gmaps

gmaps

github.com/monotykamary生産性454 回インストール2 日前に更新
リポジトリ

gmaps について

gmapsは、CDP経由でGoogle Mapsを検索し、ルートを取得するコマンドラインスキルです。複数の移動手段に対応し、直線距離で開始地点を固定した訪問順を推定した後、実際のルートを取得します。

主な機能

  1. 01Google Mapsで周辺のビジネスを検索
  2. 02複数の移動手段でルートを取得
  3. 03開始地点を固定して訪問順を推定
  4. 04テキストまたはJSONで結果を出力
  5. 05ブラウザータブで並行実行

活用シーン

  1. 01場所の近くにある店舗を検索
  2. 02ルートや移動手段を比較
  3. 03複数地点の訪問順を計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/monotykamary/browser-harness-js --skill gmaps

インストール後、Claude Code で/gmapsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

scripts/
gmaps
setup
test

詳細

gmaps — CDP 経由の Google Maps(検索、ルート案内、ベストエフォートの TSP)

CDP を通じて、Google Maps に無料で API キーなしでアクセスできます。従量課金制の Google Places API と Directions API が提供するものと同じデータを、レンダリングされたページから直接取得します。API キーもクォータも jq も不要です。3 つのモードがあります。

  • search(デフォルト)— クエリに対する地域のビジネス検索結果です。
  • --route — 順序を指定した場所のリストに対する、実際の車のルート案内(合計時間と距離、現在の交通状況)です。
  • --optimize — 最速となるベストエフォートの 訪問順序(始点を最初の場所に固定した開路 TSP)です。

呼び出しごとに専用のバックグラウンドタブと WebSocket セッションが開くため、並列実行しても安全です。

コマンド

検索(デフォルト)
gmaps "coffee shops in Austin TX"            # up to 10 results, pretty
gmaps "sushi near Times Square" 5            # 5 results
gmaps "pharmacy open now Berlin" 20          # 20 (scrolls the feed)
gmaps --json "coffee shops in Austin TX" 5   # raw JSON
引数意味
--json整形済みテキストの代わりに、生の JSON を出力します
<query>Google Maps の検索クエリです。例:coffee shops in Austin TX
[count]検索結果の数です(デフォルトは 10、上限は 30 — さらに読み込むためにフィードをスクロールします)
ルート案内(--route)
gmaps --route "Austin, TX" "Houston, TX"                       # real time + distance (driving)
gmaps --route --mode transit "London, UK" "Paris, France"     # by travel mode
gmaps --route "Austin, TX" "Houston, TX" "Dallas, TX"           # ordered multi-stop, total
gmaps --route --json --mode cycling "Austin, TX" "Houston, TX" # raw JSON

移動手段 --mode(デフォルトは driving)で、指定された順序の実際のルートを取得します。合計時間と距離(現在の交通状況)、解決された経由地名を読み取ります。最初の場所が出発地です。場所は最大 25 件です。折りたたまれたルート概要には区間ごとの所要時間は表示されません(Maps が表示するのは合計のみです)。詳しいターンバイターン案内を確認するには、url を開いてください。

ベストエフォートの順序(--optimize)
gmaps --optimize "Austin, TX" "Houston, TX" "Dallas, TX" "San Antonio, TX"
gmaps --optimize --mode transit "Austin, TX" "Houston, TX" "Dallas, TX"
gmaps --optimize --json "Austin, TX" "Houston, TX" "Dallas, TX"

始点(最初の場所)を固定した開路 TSPとして、最速の 訪問順序 を求めます。最初の場所を出発地とし、残りの場所は直線距離の合計が最小になるよう並べ替えます。その後、その順序で(選択した --mode、デフォルトは driving による)実際のルート案内呼び出しを 1 回行います。そのため、順序 は直線距離による推定ですが、報告される時間と距離は実際の値です。

  • 各場所の正確な緯度・経度を並列に解決します(バックグラウンドタブで、解決された場所の URL に含まれる !3d!4d 座標をポーリングします)。必要な検索は N 回であり、N² 回ではありません。
  • 区間は直線距離(ハバーサイン距離)として仮想化され、各区間の方位も報告されます。移動手段 --mode が影響するのは最後の実際のルート案内呼び出しのみで、TSP の順序には影響しません。
  • 最大 12 か所までの TSP を厳密に解きます(Held-Karp 法)。
  • ブラウザー呼び出しの合計は N + 1 回です(場所の解決 N 回 + ルート案内のレンダリング 1 回)。
移動手段(--mode)
--mode返される内容
driving (デフォルト)時間 + 距離 + 経由地 + 有料道路(通常の Maps ルート)
transit最適なバス/電車/フェリー/地下鉄の所要時間(距離と経由地は含まれません)
walking時間 + 距離(所要時間は時間単位で表示されます。例:62 hr)
cycling時間 + 距離(フェリー/車両輸送に関する注記が含まれる場合があります)
flights直行便の所要時間のみ(ベストエフォート — フライトカードはルートパネルの外にあります)
bestMaps の「Best」タブが選択する内容(ほとんどのルートでは車、より速い場合は公共交通機関/飛行機)
  • motorcycle モードと ferry モードはありません。 Maps のトップレベルのタブは上記の 6 種類です。バイクのルートは driving で案内され、フェリーは driving/transit のルート内の区間として表示されます。(ベトナムや米国では、専用の二輪車タブは Maps に表示されません。表示されるのは一部の地域/国のみです。)
  • ページは best で読み込まれます。それ以外のモードでは、gmaps がその移動手段のタブをクリックします。利用できないモード(例:島へのルートでの cycling)では、わかりやすいエラーが返されます。
  • --route と --optimize の両方に適用されます。
並列実行

呼び出しごとに専用のバックグラウンドタブが開くため、並列実行しても安全です。

gmaps "coffee Austin" 5 &  gmaps "coffee Seattle" 5 &  wait
gmaps --route "Austin, TX" "Houston, TX" &  gmaps --route "Dallas, TX" "San Antonio, TX" &  wait

結果の形式

検索 — 整形済み(デフォルト)
coffee shops in Austin TX  ·  5 results

1. Jo's Coffee – South Congress   ★4.4 (1972)   $1–10   Coffee shop
   1300 S Congress Ave   ·   Open · Closes 7 PM
   30.2510458, -97.7493717   ·   ChIJ1S_Bov20RIYRe7MiR8
   https://www.google.com/maps/place/Jo%27s+Coffee...
検索 — --json
{
  "query": "coffee shops in Austin TX",
  "count": 5,
  "results": [
    {
      "name": "Jo's Coffee – South Congress",
      "rating": 4.4,
      "review_count": 1972,
      "price": "$1–10",
      "category": "Coffee shop",
      "address": "1300 S Congress Ave",
      "hours": "Open · Closes 7 PM",
      "lat": 30.2510458,
      "lng": -97.7493717,
      "place_id": "ChIJ1S_Bov20RIYRe7MiR8",
      "url": "https://www.google.com/maps/place/Jo%27s+Coffee…"
    }
  ]
}

各結果には name, rating, review_count, price, category, address, hours, lat, lng, place_id, url が含まれます。存在しないフィールドは null(整形モードでは -)になります。たとえば、価格帯や番地がない場所などが該当します。

ルート案内 — 整形済み
Route (driving): Austin, TX  →  Houston, TX

  2 hr 31 min   165 miles   via State Hwy 71 E and I-10 E
  Fastest route, the usual traffic  ·  tolls

  1. Austin, Texas, USA
  2. Houston, Texas, USA

  https://www.google.com/maps/dir/Austin,+TX/Houston,+TX/

(driving) は --mode の値です(デフォルト)。非常に短いルートや、transit/flights(距離なし)では、distance(および via)が null になる場合があります。tolls は、ルートに有料道路が含まれる場合のみ表示されます。

ルート — --json
{
  "mode": "route",
  "travel_mode": "driving",
  "places": ["Austin, TX", "Houston, TX"],
  "route": {
    "duration": "2 hr 31 min",
    "distance": "165 miles",
    "via": "via State Hwy 71 E and I-10 E",
    "label": "Fastest route, the usual traffic",
    "tolls": true,
    "waypoints": ["Austin, Texas, USA", "Houston, Texas, USA"],
    "url": "https://www.google.com/maps/dir/Austin,+TX/Houston,+TX/"
  }
}
最適化 — pretty
Optimized route  ·  best-effort (straight-line TSP)  ·  driving  ·  fixed start: Austin, TX, USA

  1. Austin, TX, USA  (30.267153, -97.7430608)   start
  2. San Antonio, TX, USA  (29.4251905, -98.4945922)   ← 118.4 km SW (218°)
  3. Houston, TX, USA  (29.7600771, -95.3701108)   ← 304.4 km E (82°)
  4. Dallas, TX, USA  (32.7766642, -96.7969879)   ← 361.8 km N (338°)

  Straight-line total: 784.6 km

Real driving for this order:
  7 hr 50 min   516 miles   via I-35 S
  Fastest route now due to traffic conditions  ·  tolls
  Austin, TX, USA  →  San Antonio, TX, USA  →  Houston, TX, USA  →  Dallas, TX, USA

  https://www.google.com/maps/dir/Austin,+TX/San+Antonio,+TX/Houston,+TX/Dallas,+TX/

Note: order is a best-effort estimate from straight-line (haversine) distances;
the driving time above is the real route for that order.
最適化 — --json
{
  "mode": "optimize",
  "travel_mode": "driving",
  "places": [{ "query": "Austin, TX", "name": "Austin, TX, USA", "lat": 30.267153, "lng": -97.7430608, "url": "…" }, …],
  "order": ["Austin, TX, USA", "San Antonio, TX, USA", "Houston, TX, USA", "Dallas, TX, USA"],
  "order_indices": [0, 3, 1, 2],
  "legs": [{ "from": "Austin, TX, USA", "to": "San Antonio, TX, USA", "km": 118.4, "bearing": 218, "dir": "SW" }, …],
  "straight_line_total_km": 784.6,
  "route": { "duration": "7 hr 50 min", "distance": "516 miles", "via": "via I-35 S", "label": "…", "tolls": true, "waypoints": […], "url": "…" },
  "note": "Order is a best-effort estimate from straight-line (haversine) distances; the driving time/distance is the real route for that order."
}

仕組み

検索
手順内容
1session.connect()(1回)— ブラウザーと共有する WebSocket
2Target.createTarget({ background: true }) + attachToTarget — 分離されたバックグラウンドタブ + 呼び出しごとの sessionId
3Page.navigate で https://www.google.com/maps/search/<query> に移動します(URI エンコード済み。スペースは +)。
4a[href*='/maps/place/'] の数が 0 より大きくなるまでポーリングします — これが実際の準備完了を示すシグナルです。Maps では Chrome の networkIdle は発火しません(継続的な XHR ポーリングにより、500ms の静止時間が一度も発生しないため)。そのため、ライフサイクルイベントを待つとタイムアウトします。フィードをポーリングする方が高速で、確実です。
5フィードをスクロールして、最大 count 件の結果を読み込みます。フィードには最初に約 7 件のカードが表示され、フィードの最下部までスクロールするとさらに読み込まれます。count 件に達したとき、スクロールを 2 回連続しても結果が増えないとき(結果の末尾)、または 25 回のスクロール上限に達したときに停止します。
61 回の Runtime.evaluate で各結果カードを解析し、JSON 配列を返します。
7finally で closeTab をファイア・アンド・フォーゲットで実行し、応答をブロックせずにクリーンアップします。
経路案内(--route / --optimize)
手順内容
1Page.navigate で https://www.google.com/maps/dir/<p0>/<p1>/…/ に移動します(パス形式。スペースには + を使用)。?query= 形式ではページが表示されません。ページは Maps の「Best」モードで読み込まれます。
2経路案内パネルに経路の所要時間が表示されるまでポーリングします。 フィードと同様に、networkIdle は発火しません。経路リストの所要時間は min/hr で終わります。一方、移動モードのタブは min を含まない短縮形式(2h 30m、16 hr)を使用します。そのため、[aria-label="Directions"] パネル内で /\d+\s*min\b/ をポーリングすれば、実際の経路を待ちつつ、移動モードのタブを無視できます。距離のリーフ(miles/km)も受け付けるため、ちょうど 1 時間の経路(2 hr のように min を含まない場合)でも検出されます。地図の縮尺バーにある 50 km はパネルの外にあります。
3移動手段の --mode を選択します(best の場合を除く)。移動モードのタブは、[aria-label] アイコンを含む button[role=radio] で、順序は安定して [Best, Driving, Transit, Walking, Cycling, Flights] です(地図タイプの Default/Satellite ラジオにはアイコンの aria-label がないため、対象外です)。タブの aria-label はローカライズされる(「Driving」→「Lái xe」)ため、タブはラベルではなくインデックスで選択します。タブの表示を待機してから選択します(初期描画より遅れる場合があります)。また、利用できないタブを検出します(DOM の disabled プロパティを使用し、フォールバックとしてクリック後に経路が表示されない場合は「利用不可」と判定します)。実際にクリックすると、パネルはクリアされて(所要時間 → null)、その後新しいモードの所要時間が再表示されます。そのため、null → 非 null のポーリングで再描画の完了を待ち、前のモードの所要時間との競合を防ぎます。
41 回の Runtime.evaluate でメインの経路を抽出します。移動モードの BUTTON[role=radio] 内にない最初の完全形式の所要時間リーフ(このボタン内には各モードの最短所要時間が短縮形式で表示されます)、最も近い祖先要素にある距離リーフ、および一意の via …/Fastest route …/This route has tolls. リーフを取得します(RedCoach/FlixBus などの公共交通機関の代替経路にはこれらがないため、結果に混入しません)。解決された経由地名は input[aria-label](Starting point …/Destination …)から取得します。flights の場合、所要時間は body 全体から読み取り、距離/経由地/有料道路情報は null になります。
5finally で closeTab をファイア・アンド・フォーゲットで実行します。
追加の最適化
手順処理内容
Aバックグラウンドタブで各場所を並列に 解決 します。場所名で検索してもフィードは表示されません。検索結果は単一の場所ページに解決され、URL は(約 3~7 秒後に)/maps/place/<name>/@<view>/data=…!8m2!3d<lat>!4d<lng>… に更新されます。各タブは !3d!4d トークン(場所の正確な座標。@lat,lng はビューポートのみを示します)が現れるまで location.href を確認します。場所名は /maps/place/<name>/ のパスセグメントから読み取ります。
Bハーバーサイン距離行列を作成し、開始地点を place[0] に固定したオープンパス TSP を解きます(正確な Held-Karp アルゴリズムを使用。場所は最大 12 件)。
C区間ごとの直線距離(km)と方位を計算します。
D選択した順序に対して(選択した --mode で、デフォルトは driving)実際の --route 呼び出しを 1 回だけ実行します。報告される時間と距離は実際の値で、直線距離による推定なのは 順序 のみです。

呼び出しごとの所要時間:検索は約 2~3 秒(フィードの表示とスクロール)、ルート検索は約 2~3 秒、最適化は約 5~9 秒(場所の解決を N 件並列で行い、経路案内を 1 回表示)。バックグラウンドタブで動作するため、邪魔になりません(ytdl とは異なり、Maps では再生や poToken は不要です)。

Maps で実際のブラウザーを使う理由

  • Google Places API と Directions API は従量課金制で、利用には制限があります — API キーと請求先アカウントが必要で、呼び出しごとに費用がかかります。Maps のウェブページなら同じデータを無料で表示できます。自分のブラウザーで操作すれば、キーも利用枠も必要ありません。有料の Directions API が提供する機能のうち、無料のページでは本当に利用できないのは optimizeWaypoints(停留地点が 9 件以下の TSP)だけです。--optimize はこれを、ベストエフォートの直線距離による TSP で代替します。
  • 実際のブラウザーでフィードとルートパネルを表示します。 Maps は大規模な SPA です。スクロールに応じて検索結果が div[role=feed] に順次読み込まれ、経路案内パネルには難読化され、変更されるクラス名を使って所要時間が表示されます。1 回の CDP 呼び出しで Runtime.evaluate を実行すれば、すべてのカード/主要なルートを抽出できます。HTML の取得もプロキシも Cloudflare の壁もありません(ページはユーザー自身として、ユーザー自身のブラウザーで表示されます)。
  • 用途はクエリ単位であり、大量収集ではありません。 これはエージェント呼び出しの形式です。クエリ 1 件(またはルート 1 件、最大 25 件/場所最大 12 件の TSP 1 件)を、構造化 JSON またはテキストで返します。大量収集用のスクレイパーではありません(その場合はプロキシのローテーションとレート管理が必要で、呼び出しごとに実際のブラウザーを使う方法では規模を拡大できません)。

注意点

検索
  • Maps では networkIdle が発火しません。 XHR ポーリングが継続してネットワークがビジー状態になるため、gsearch/findata が使用するライフサイクルイベント待機は毎回タイムアウトします。gmaps は代わりに a[href*='/maps/place/'] の件数をポーリングします。これが実際の準備完了シグナルです。
  • クラス名セレクターは使用しません。 Google のクラス名は難読化されており(.Nv2PK、.bfdHYd、.MW4etd、…)、リリースごとに変わります。パーサーが依存するのは安定したシグナルのみです。結果リンクの aria-label(名前)、カード内のリーフテキストの DOM 順序(評価/レビュー/価格/カテゴリ/住所/営業時間)、および href(座標には !3d<lat>!4d<lng>、場所 ID には !19sChIJ…/!1s0x..:0x..)。Maps がカードのテキスト順序を変更した場合、最初に見直すべきなのは住所と営業時間のヒューリスティックです。
  • ?query= 形式の URL ではフィードが表示されません(結果のないリダイレクト用シェルが返されます)。フィードが表示されるのはパス形式の /maps/search/<query> のみです。スペースは + として送信されます。
  • 住所はヒューリスティックです。 番地で始まる最初のリーフ(^\d{1,5}[-A-Za-z]*\s)を使います。番地のない場所(地域の検索結果や名称のある建物など)では、推測せず address: null を返します。
  • 営業時間はヒューリスティックです。 Open/Closed/Open 24 hours/Temporarily closed のリーフと、存在する場合はその次の · Closes/Opens … リーフを使います。Open 24 hours が 2 つのリーフ(Open + 24 hours)として表示される場合、Open のみとして読み取られます。
  • 電話番号、ウェブサイト、週間営業時間全体はフィードカードには含まれません。 場所の詳細ページにあり、読み取るには場所ごとに移動する必要があります。未実装です(--details モードとして実装する必要があります)。現時点では url を使って場所を開いてください。
  • スポンサー付きの検索結果が表示される場合があります(Maps には広告が 1 件表示されることがあり、これも /maps/place/ リンクです)。除外されないため、重要な場合はデータを確認してください。
  • 同意画面/Cookie の壁(すでにブラウザーが準備済みの場合はまれ)では、結果が 0 件になります。結果が空の場合は、ブラウザーで検索 URL を一度開いて解除してください。
経路
  • 折りたたまれた経路の概要には区間ごとの所要時間がありません。 経由地を複数指定した順序付きルートでは、Maps には合計時間のみが表示されます。区間ごとの所要時間は「詳細」の展開後に表示されます(未実装)。ターンごとの案内には url を使用してください。
  • ごく短いルートでは distance が null になる場合があります。 Maps が距離のリーフを表示しないことがあります。
  • 主要ルートは「移動手段のラジオボタン内にない、完全な形式の所要時間の最初のもの」です。 モードのタブ(BUTTON[role=radio])には簡略形式の最短時間(2h 30m)が含まれるため、除外する必要があります。経路リストでは完全な形式(2 hr 31 min)が使われます。公共交通機関の別ルート(RedCoach/FlixBus)は同じパネルに表示されますが、via/Fastest route/距離のリーフがないため、主要ルートの抽出には影響しません。
  • 所要時間は現在の交通状況(「今」)を反映しています。 ページに表示されるのは「今」の情報であり、将来の出発時刻を予測した情報ではありません(有料 API の departure_time/traffic_model にあたる情報はページにありません)。ラベルは「Fastest route, the usual traffic」と「Fastest route now due to traffic conditions」のように異なりますが、どちらも ^Fastest route に一致します。
  • 場所名で検索してもフィードは表示されません。 そのため --optimize はフィードカードの解析ではなく、場所ページの URL を !3d!4d についてポーリングして場所を解決します。バックグラウンドタブで URL が更新されるまでに約 3~7 秒かかります。resolvePlace は最大 20 秒間ポーリングします。
移動手段(--mode)
  • best は Maps のデフォルトですが、車とは限りません。 公共交通機関や飛行機のほうが速いルート(例:ロンドン→パリ。Best はユーロスター)の場合、best は公共交通機関での所要時間を返します。車でのルートを指定するには、--mode driving を渡してください。このため、このスキルのデフォルトは best ではなく driving です。
  • 移動手段のタブは aria-label ではなく INDEX で選択します — ラベルはローカライズされています(vi では「Driving」→「Lái xe」)。[Best, Driving, Transit, Walking, Cycling, Flights] の順序はロケール間で安定していますが、Maps がタブを並べ替えたり削除したりすると、インデックスの対応関係が崩れます。
  • 利用できない移動手段は 2 つの方法で検出します — タブの DOM disabled プロパティ(レンダリングに反映されるまで遅れる場合があります)を使い、フォールバックとして、クリック後にルートが表示されない場合(パネルがクリアされるものの、再表示されない場合)は、その移動手段を「このルートでは利用できません」と報告します。
  • flights はベストエフォートです。 フライトカードは経路案内パネルの外側に表示されるため、直行便の所要時間はページ全体から読み取ります。距離、経由地、通行料金はありません。乗り継ぎ便と予約の詳細は解析されません。
  • motorcycle と ferry の移動手段はありません。 Maps に表示されるタブは上記の 6 つのみです。バイクのルートは driving で、フェリーは driving/transit のルート内の区間として表示されます。(ベトナムまたは米国の Maps には、二輪車用のタブは表示されません。)
  • 徒歩/自転車の所要時間は、移動手段のタブに表示される「日数」ではなく、ルート一覧に時間単位で表示されます(例:62 hr)。どちらの表記も照合されます。
最適化
  • 順序は直線距離による推定であり、実際の運転で最適な順序ではありません。 直線距離(ハバースイン距離)が「仮想化された」辺のコストとして使われます。道路網での迂回により、実際の最適な順序は異なる場合があります。報告される運転時間/距離は実際の値です(選択した順序に対する経路案内の呼び出し 1 回)。ベストエフォートなのは順序のみです。
  • 開始地点は固定で、経路は開いたままです。 最初の場所が開始地点として固定され、経路はすべての場所を 1 回ずつ訪れ、どこかで終了します(戻りません)。往復ではありません。
  • 12 か所以下では厳密に計算します。 Held-Karp は厳密ですが、計算量は指数関数的です。12 か所を超えると、推測せずにコマンドがエラーになります。N か所の解決は並列で実行され(N 個のバックグラウンドタブ)、それに経路案内の呼び出し 1 回を加えて、ブラウザー呼び出しは合計 N+1 回です。
全般
  • jq への依存はありません — 解析、TSP、整形はページ内/JS で行います。
  • 入力はプレースホルダー置換によって heredoc に渡されます(node の JSON.stringify と関数置換)。場所名に含まれる &/$/\ の影響を受けません(例:AT&T Stadium、Café du Monde)。検索クエリでも同じ仕組みを使います。

コメント