AI Cowork Lab
【実験ログ】Google Maps Places APIで店舗の実写真をブログに自動表示する仕組みを作った
実験ログ 約27分で読めます

【実験ログ】Google Maps Places APIで店舗の実写真をブログに自動表示する仕組みを作った

旅行ブログを作っていて、一番困ったのが「写真」の問題でした。

カフェやレストラン、観光スポットを紹介する記事には、当然その場所の写真が必要です。でも、自分で全ての場所に行って撮影するのは現実的じゃない。かといって、他のサイトから画像を持ってくるのは著作権の問題があります。

この実験ログでは、Google Maps Places API(ソフトウェア同士がデータをやり取りする窓口)(New) を使って、サイトを作るときに自動でお店の実写真を取得して、HTMLに埋め込む仕組みを作った過程を書いていきます。結果的にランニングコスト$0で、リアルな写真付きの旅行記事が作れるようになりました。


課題: 旅行ブログの画像をどこから持ってくるか

旅行メディア「trip-journal.net」の記事で、お店やスポットの写真を載せたい。でも画像の調達先には、それぞれ制約があります。実際に試してわかった各ソースの特徴を整理します。

画像ソースの比較

ソース使えるかメリットデメリット
UnsplashOK高品質、無料、商用利用可特定のお店の写真はない。「東京のカフェ」程度の雰囲気写真のみ
Pixabay条件付き無料ホットリンク(他のサイトの画像URLを直接借りて表示すること。多くのサイトでブロックされる)がブロックされることがある。品質にばらつき
Wikimedia Commons条件付きライセンスが明確外部からのホットリンクがよくブロックされる。403エラー(アクセス拒否)になる
Google MapsAPI経由でOK実際のお店の写真がたくさんある。ユーザー投稿で常に更新されるAPIの設定が必要。表示ルールに沿う必要がある
食べログ等のレビューサイトNG-著作権・利用規約でスクレイピング(自動でデータを取ってくること)・転載は禁止

Unsplashは雰囲気写真としては優秀ですが、「渋谷の○○カフェ」の写真は存在しません。Wikimedia Commonsは試しましたが、Astro(Webサイトを作るための道具。表示が速いのが特徴)のビルド中にアクセスすると拒否されて使えませんでした。食べログやRettyの写真は論外 ── 著作権的にアウトです。

消去法で残ったのがGoogle Maps。Google Mapsには、ユーザーが投稿したお店の写真が大量にあります。しかも公式のAPIが提供されている。つまり、正規のルートで、ルールに沿って写真を取得できるということです。「これだ」と思いました。

ℹ️

Google Maps Photos APIは「正規ルート」

Places API経由で取得した写真は、Google Maps Platformの利用規約に基づいて使えます。ただし、写真を撮った人のクレジット表示が必要です。勝手にスクレイピングするのとは違って、ちゃんとした正規のAPI利用です。


解決策: サイトを作るときにAPIを呼ぶという発想

最初に考えたのは「ページを見るたびにAPIを呼ぶ」方式でした。でもこれには2つの問題があります。

  1. コスト: アクセスのたびにAPI呼び出しが発生して、無料枠をすぐに使い切ってしまう
  2. 表示速度: ページを開くたびにAPIの返事を待つことになるので、表示が遅くなる

そこで思いついたのがビルド時取得です。AstroのSSG(サイトを事前にHTMLファイルとして作っておく方式。表示が速い)では、npm run buildのタイミングでAPIを呼んで、結果をHTMLに埋め込むことができます。かんたんに言うと、「サイトを作る段階で写真を取ってきて、完成品のHTMLに組み込んでおく」ということです。公開後のサイトは普通のHTMLなので、APIは一切呼ばれません。

全体の流れ

写真取得の全体フロー

MDX記事ファイル
mapQuery=“渋谷 カフェ ABC”
|
SpotCard パーツ
画像がなければAPIに問い合わせ
|
places.ts
お店を検索 → 写真を取得
|
Places API (New)
Google Cloud Platform
|
完成したHTML
imgタグに写真URLが埋め込まれた状態で公開

ポイントは「サイトを作るときに1回だけAPIを呼ぶ」こと。 読者がページを見るときにはAPIは呼ばれません。だからアクセスがどれだけ増えても、APIのコストはゼロのまま。これがSSG + API連携の最大のメリットです。

💡

ビルド時取得のメリット3つ

サイトを作るときにAPIを呼んでHTMLに埋め込む方式なら、(1) 公開後のAPIコストがゼロ、(2) ページ表示が速い(API待ちなし)、(3) APIキー(サービスを使うための鍵のようなもの)がサイトを見ている人に見えない。この3つが同時に実現できます。


GCPのセットアップ ── 4ステップ

Google Maps Places API (New) を使うために、Google Cloud Platform(GCP。Googleが提供するクラウドサービスの管理画面)でプロジェクトを作って、APIキーを発行します。手順は4ステップです。

Step 1: GCPプロジェクトの作成

  1. Google Cloud Console にアクセス
  2. 新しいプロジェクトを作成(名前は自由、例: trip-journal
  3. プロジェクトが作成されたら、そのプロジェクトを選択

GCPのアカウントがない場合は、Googleアカウントで新規登録します。クレジットカードの登録が求められますが、無料枠内の利用なら課金されません。

Step 2: Places API (New) を有効化

  1. 「APIとサービス」→「ライブラリ」を開く
  2. 「Places API (New)」 を検索して有効化

ここで注意。「Places API」には旧版(Legacy)と新版(New)があります。新版のほうがデータの形式がわかりやすく、料金体系も良くなっています。必ず**「Places API (New)」**を選んでください。名前に「New」が付いているほうです。

Step 3: APIキーの作成

  1. 「APIとサービス」→「認証情報」を開く
  2. 「認証情報を作成」→「APIキー」を選択
  3. APIキーが生成される ── これをコピーして保管

Step 4: APIキーの制限設定 ── ここに罠がある

APIキーを作ったら、セキュリティのために制限を設定します。ここが最大のハマりポイントでした。

ウェブサイト制限の罠

やりがちな間違い: 「アプリケーションの制限」で「ウェブサイト」を選んで、自分のドメイン(trip-journal.net)を登録する。

結果: Cloudflare Pages(GitHubと連携してサイトを無料で公開できるサービス)のビルドサーバーからの呼び出しがブロックされて、ビルドが失敗する。

理由: ビルドはCloudflareのサーバー上で実行されます。つまり、APIを呼んでいるのは自分のサイトじゃなくて、Cloudflareのサーバー。ウェブサイト制限は「どのサイトから呼んでいるか」をチェックするので、ビルドサーバーからのリクエストは拒否されてしまうんです。

⚠️

正しいAPI制限の設定

「アプリケーションの制限」は設定しない(または「なし」を選択)。代わりに「APIの制限」でPlaces API (New) だけに限定します。かんたんに言うと、「誰が使えるか」ではなく「何に使えるか」で制限をかけるということです。こうすれば、万が一キーが漏れても、Places API以外は呼べないので被害を最小限にできます。

正しい設定をまとめるとこうなります。

設定項目理由
アプリケーションの制限なしビルドサーバーからの呼び出しを許可するため
APIの制限Places API (New) のみこのキーで他のAPIを呼べないようにする

この設定に辿り着くまでに、ビルドエラー → ログ確認 → API制限の見直しで約30分かかりました。「セキュリティを高めようとして、逆にビルドが壊れる」という皮肉な状況でした。


実装の概要: places.ts と SpotCard

places.ts ── APIを呼ぶ関数

Places APIを呼び出す処理をplaces.tsにまとめました。主な関数は2つです。

// places.ts の構造(ざっくり)

searchTextPlaces(query: string)

→ お店の名前で検索して、場所IDと写真の情報を取得
→ 例: “渋谷 カフェ ABC” → そのお店のIDと写真データ

getPlacePhotos(photoReference: string)

→ 写真の参照コードから実際の写真URLを作る
→ 横幅800pxで取得(表示サイズにちょうどいい大きさ)

Places API (New) の呼び出し先はhttps://places.googleapis.com/v1/places:searchTextです。リクエストにはAPIキーに加えて、X-Goog-FieldMaskというヘッダーで「どの情報がほしいか」を指定します。写真だけが必要ならplaces.photos,places.displayNameのように絞ることで、データ量と料金を最小限にできます。

写真URLの作り方も、旧APIとは形式が違います。新APIではplaces/{placeId}/photos/{photoReference}/media?maxWidthPx=800&key={apiKey}のようなURLになります。このURLをimgタグのsrcに設定すれば、Google Mapsの実際の写真が表示されます。

SpotCard パーツ ── 写真が取れなかったときの保険

SpotCardは、旅行記事内でスポットを紹介するためのカードパーツです。写真の取得には**3段階のフォールバック(代替手段)**を作りました。

SpotCardの写真取得 ── 3段階の保険

優先1

手動で指定した画像(imageプロパティ)

記事を書く人が明示的に画像URLを指定した場合、それを使います。一番確実な方法です。

優先2

Places APIから自動取得(mapQueryプロパティ)

mapQueryに書いたお店の名前で検索して、写真を自動で取ってきます。ほとんどの場合はこれでOK。

優先3
Google Mapsの地図を埋め込み
写真が見つからなかった場合、Google Mapsの地図を表示します。場所はわかります。

この3段階の保険のおかげで、どんな状況でもSpotCardが「何も表示されない」ということは起きません。Places APIで写真が見つからないお店(登録が新しい、写真が投稿されていないなど)でも、最低限Googleマップの地図が表示されます。

実際に使ってみると、Places APIから写真が取れる確率は体感で80%以上。飲食店や観光地はユーザー投稿の写真が豊富なので、ほぼ確実に取得できます。逆に、知名度の低い公園や個人商店は写真がないことがあります。


Cloudflare Pagesへの環境変数設定

APIキーをCloudflare Pagesに設定する手順はかんたんです。

Cloudflare Dashboard → Pages Project → Settings → Environment variables
→ Production に GOOGLE_PLACES_API_KEY を追加

この環境変数(APIキーなどの秘密情報を安全に保存する場所)はビルド時にimport.meta.env.GOOGLE_PLACES_API_KEYとして読み込まれます。ビルドサーバー上でPlaces APIが呼ばれて、結果がHTMLに埋め込まれて、公開されます。公開後のサイトにはAPIキーは一切含まれません。

ここが「ウェブサイト制限」の罠に関係する部分です。ビルドはCloudflareのサーバーで実行されるので、APIキーに「ウェブサイト制限」をかけると、ビルドサーバーからの呼び出しがブロックされます。前のStep 4で説明した通り、「アプリケーションの制限」は設定せず、「APIの制限」だけを使うのが正解です。


料金: 月額$0の仕組み

Google Maps Platformには、毎月**$200の無料クレジット**がもらえます。Places API (New) の料金を実際の利用量と照らし合わせてみましょう。

API操作1回あたり料金$200で呼べる回数用途
Text Search約$0.032約6,250回お店の名前で検索
Place Photos約$0.007約28,571回写真URLの取得
合計(1スポット分)約$0.039-検索+写真取得

1回のビルドで呼ぶAPIの回数は、記事内のSpotCardの数によります。1記事あたり5〜10スポットとして、記事が20本でも100〜200回のAPI呼び出し。金額にして約$4〜8。毎月$200の無料クレジットがあるので、実質コストはゼロです。

実際のコスト計算

項目数値
記事数約20本
1記事あたりのSpotCard数5〜10個
1ビルドあたりのAPI呼び出し100〜200回
月間のビルド回数約20回(記事の更新・修正時)
月間の総API呼び出し約2,000〜4,000回
月間コスト約$78〜156
無料クレジット$200/月
実際の支払い$0

仮に記事数が10倍になっても、ビルド時にしかAPIを呼ばないので、月間のAPI呼び出しはせいぜい数万回。$200の無料クレジット内に収まる可能性が高いです。

💡

ビルド時に呼ぶのがコスト最適化のカギ

もし読者がページを見るたびにAPIを呼ぶ方式だったら、1日1,000PVのサイトで月間30,000回のAPI呼び出しが発生します。ビルド時に1回だけ呼ぶ方式なら、同じコンテンツでも呼び出し回数は100分の1以下。つまり、サイトが大きくなればなるほど、ビルド時取得の方式が有利になるということです。


写真のクレジット表示

Places APIから取得した写真には、authorAttributionsという情報が付いています。これは写真を投稿したユーザーの情報で、表示が求められています。

SpotCardでは、写真の下に小さくクレジットを表示するようにしました。

<!-- SpotCard内のクレジット表示例 -->
<div style="font-size: 0.7em; color: #9ca3af; text-align: right; margin-top: 4px;">
  Photo by Google Maps User
</div>

authorAttributionsには投稿者名、プロフィールURL、写真URLが含まれます。Google Maps Platformの利用規約に沿って、きちんとクレジットを表示することが必要です。細かいことですが、正規のAPI利用を続けるために欠かせません。


ページ表示が速くなるメリット

この仕組みのうれしい副作用として、ページの表示がとても速くなるということがあります。

普通、外部APIから写真を取得して表示するには、こんなステップが必要です。

ページを見るたびにAPIを呼ぶ場合(遅い): ページ読み込み → JavaScriptを読み込み → APIを呼ぶ → 返事を待つ → 写真を表示

所要時間: 1〜3秒(APIの返事待ち)。読者は写真が出るまでローディング画面を見ることになります。

ビルド時に取得しておく場合(速い): ページ読み込み → HTMLに写真URLがもう入ってる → すぐ表示

所要時間: 普通のHTML読み込みと同じ。JavaScriptもAPI待ちも不要です。

ビルド時にAPIを呼んでいるので、公開後のHTMLには写真のimgタグがすでに入っています。ブラウザはHTMLを読み込んだ瞬間に写真のダウンロードを開始できます。かんたんに言うと、「料理が出来上がった状態でテーブルに並んでいる」のと、「注文してから作り始める」の違いです。体感で1〜2秒速いです。

さらに、CloudflareのCDN(世界中にコピーを置いて、近い場所から配信する仕組み。表示が速くなる)がHTMLをキャッシュするので、2回目以降のアクセスはさらに速くなります。写真自体はGoogleのCDNから配信されるので、どちらも高速です。


ハマったポイントの詳細

実装の過程で遭遇した問題を、ほかにもいくつか書いておきます。

Places API (New) vs Places API (Legacy)

GCPのAPIライブラリには「Places API」が2つあります。名前に「(New)」が付いているものと、付いていないもの。APIのURL、リクエストの書き方、返ってくるデータの形が全部違います。間違ったほうを有効にすると、ドキュメント通りに書いてもエラーになります。

検索クエリの書き方で結果が変わる

searchTextPlacesに渡す検索文字の書き方で、結果が大きく変わります。

検索文字結果
"カフェ"近くのカフェがランダムに出てくる(使えない)
"渋谷 カフェ ABC"特定のお店が高い確率でヒット
"ABC 渋谷区"住所を含めるとさらに精度が上がる

SpotCardのmapQueryには、「エリア名 + お店の名前」を指定するのがベストです。住所まで含めれば確実ですが、記事を書くときの手間が増えるので、エリア名 + お店の名前で十分な精度が得られました。

写真がないお店もある

あまり有名でない場所や新しくオープンしたお店では、Google Mapsに写真が投稿されていないことがあります。この場合、APIのレスポンスにphotosの情報が含まれません。SpotCardの保険機能(Google Maps地図の埋め込み)が活躍しますが、写真がないカードは見栄えが劣ります。

将来的には、Unsplashからエリアの雰囲気写真を保険として使うことも考えています。


教訓まとめ ── 5つの学び

Google Maps Places APIで学んだこと

1. ビルド時に取得するのがSSGの最大の武器 外部APIをサイトを作るときに呼んで、結果を完成品のHTMLに埋め込む。これだけで公開後のAPIコストがゼロになり、表示速度も上がり、APIキーも外に出ない。つまり、一石三鳥ということです。

2. API制限は「何の制限か」をちゃんと理解する 「ウェブサイト制限」と「API制限」は別物です。ビルドサーバーから呼ぶ場合、ウェブサイト制限をかけると自分のビルドがブロックされます。かんたんに言うと、「誰が使えるか」じゃなくて「何に使えるか」で制限するのが正解ということです。

3. 保険は3段階以上あると安心 手動画像 → API取得 → 地図埋め込み。「何も表示されない」という最悪のケースを防ぐために、保険は複数用意しておく。80%の確率で成功するAPIでも、20%の失敗に備えることが信頼性を生みます。

4. 無料枠は「設計しだい」で活かせる $200/月の無料クレジットは、読者が見るたびにAPIを呼ぶ設計だとすぐ使い切ります。でもビルド時に1回だけ呼ぶ設計にすれば、事実上無制限に使えます。つまり、コスト削減は技術選びじゃなくて設計の問題ということです。

5. 本物の写真の力はすごい Unsplashの雰囲気写真と、Google Mapsの実際のお店の写真では、読者への説得力がまるで違います。「あ、このお店本当にあるんだ」と思ってもらえるかどうか。リアルな写真が記事の信頼性を底上げしてくれます。


今後やってみたいこと

この仕組みにはまだ改善の余地があります。今後試したいことを書いておきます。

写真のキャッシュ

今はビルドするたびにPlaces APIを呼んでいますが、同じお店の写真は変わらないことが多いです。ビルド時に取得した写真URLをローカルに保存しておいて、2回目以降のビルドではその保存データを使えば、API呼び出し回数をさらに減らせます。

画像のダウンロード保存

Places APIの写真URLはGoogleのサーバーを指しています。将来的にURLが変わるリスクを考えると、ビルド時に画像をダウンロードして自分のサイトに含めるほうが安全かもしれません。ただし、Cloudflare Pagesの容量制限(25MB)に注意が必要です。

複数写真のスライドショー

今はSpotCardに1枚の写真しか表示していませんが、Places APIは複数の写真を返してくれます。スライドショーのように複数写真を表示すれば、お店の雰囲気をもっと伝えられるはずです。

ℹ️

この仕組みは旅行ブログ以外にも使える

Google Maps Places APIのビルド時取得パターンは、旅行ブログに限った話じゃありません。飲食店まとめサイト、不動産の周辺情報、イベント会場の紹介など、「場所に関係する情報を自動で取ってきたい」場面ならどこでも使えます。AstroやNext.jsなどのSSGフレームワークとの相性もバッチリです。

Google Maps Places APIは、最初のセットアップこそ面倒ですが、一度仕組みを作ってしまえば、あとは記事にSpotCardを置くだけで実際の写真が自動表示されます。手動で写真を探してダウンロードして配置する手間がゼロになりました。この「仕組み化」こそが、個人で複数サイトを運用するうえでの生命線だと実感しています。

他のカテゴリの記事