【実験ログ】Google Maps Places APIで店舗の実写真をブログに自動表示する仕組みを作った
旅行ブログを作っていて、一番困ったのが「写真」の問題でした。
カフェやレストラン、観光スポットを紹介する記事には、当然その場所の写真が必要です。でも、自分で全ての場所に行って撮影するのは現実的じゃない。かといって、他のサイトから画像を持ってくるのは著作権の問題があります。
この実験ログでは、Google Maps Places API(ソフトウェア同士がデータをやり取りする窓口)(New) を使って、サイトを作るときに自動でお店の実写真を取得して、HTMLに埋め込む仕組みを作った過程を書いていきます。結果的にランニングコスト$0で、リアルな写真付きの旅行記事が作れるようになりました。
課題: 旅行ブログの画像をどこから持ってくるか
旅行メディア「trip-journal.net」の記事で、お店やスポットの写真を載せたい。でも画像の調達先には、それぞれ制約があります。実際に試してわかった各ソースの特徴を整理します。
画像ソースの比較
| ソース | 使えるか | メリット | デメリット |
|---|---|---|---|
| Unsplash | OK | 高品質、無料、商用利用可 | 特定のお店の写真はない。「東京のカフェ」程度の雰囲気写真のみ |
| Pixabay | 条件付き | 無料 | ホットリンク(他のサイトの画像URLを直接借りて表示すること。多くのサイトでブロックされる)がブロックされることがある。品質にばらつき |
| Wikimedia Commons | 条件付き | ライセンスが明確 | 外部からのホットリンクがよくブロックされる。403エラー(アクセス拒否)になる |
| Google Maps | API経由で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つの問題があります。
- コスト: アクセスのたびにAPI呼び出しが発生して、無料枠をすぐに使い切ってしまう
- 表示速度: ページを開くたびにAPIの返事を待つことになるので、表示が遅くなる
そこで思いついたのがビルド時取得です。AstroのSSG(サイトを事前にHTMLファイルとして作っておく方式。表示が速い)では、npm run buildのタイミングでAPIを呼んで、結果をHTMLに埋め込むことができます。かんたんに言うと、「サイトを作る段階で写真を取ってきて、完成品のHTMLに組み込んでおく」ということです。公開後のサイトは普通のHTMLなので、APIは一切呼ばれません。
全体の流れ
写真取得の全体フロー
ポイントは「サイトを作るときに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プロジェクトの作成
- Google Cloud Console にアクセス
- 新しいプロジェクトを作成(名前は自由、例:
trip-journal) - プロジェクトが作成されたら、そのプロジェクトを選択
GCPのアカウントがない場合は、Googleアカウントで新規登録します。クレジットカードの登録が求められますが、無料枠内の利用なら課金されません。
Step 2: Places API (New) を有効化
- 「APIとサービス」→「ライブラリ」を開く
- 「Places API (New)」 を検索して有効化
ここで注意。「Places API」には旧版(Legacy)と新版(New)があります。新版のほうがデータの形式がわかりやすく、料金体系も良くなっています。必ず**「Places API (New)」**を選んでください。名前に「New」が付いているほうです。
Step 3: APIキーの作成
- 「APIとサービス」→「認証情報」を開く
- 「認証情報を作成」→「APIキー」を選択
- 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つです。
searchTextPlaces(query: string)
getPlacePhotos(photoReference: string)
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段階の保険
手動で指定した画像(imageプロパティ)
Places APIから自動取得(mapQueryプロパティ)
この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を呼ぶ → 返事を待つ → 写真を表示
ビルド時に取得しておく場合(速い): ページ読み込み → HTMLに写真URLがもう入ってる → すぐ表示
ビルド時に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を置くだけで実際の写真が自動表示されます。手動で写真を探してダウンロードして配置する手間がゼロになりました。この「仕組み化」こそが、個人で複数サイトを運用するうえでの生命線だと実感しています。