🤖

「水族館って全国にいくつあるの?」を解決するために、Gemini APIとNext.jsで自動データ収集基盤を作った話

に公開

https://adventar.org/calendars/11678

1. はじめに:動機は「水族館に行きたい」

ふと「水族館に行きたいな」と思ったのが全ての始まりでした。

Googleマップで検索しても、公式サイトを一つひとつ開いて比較するのは大変です。「どこにあるのか」「どんな魚がいるのか」「どうやってたどり着くのか」等、すぐに知りたい情報を見やすくしたいと思い、AIを活用して形にしたいと考えました。

「既存の検索結果では満足できない。ならば自分でデータベースを作ってみよう」

そう思い立ったものの、自分で調べた限り日本全国にある水族館はおよそ300箇所。これら全ての情報を手作業でスプレッドシートにまとめるのは、人ひとりの手でやるものではないと思いました。

そこで、Next.js × Supabase × Gemini API を組み合わせ、データ収集からDB構築までを半自動化する仕組みを作ってみました。

2. 課題:APIがないなら作ればいい

現状の課題

  • 水族館の公式データAPIなんてものは存在しない。
  • スクレイピングはサイトごとの構造がバラバラすぎてメンテナンスコストが高い。
  • 欲しいのは「名前」だけでなく、「魅力(Overview)」や「主要な生き物(Highlight Fish)」といった定性的な情報。

解決策

「Geminiに公式サイトのテキスト情報を構造化データ(JSON)に変換させればいいのでは?」

今回は、最終的には人の目で判断する必要がありますが、Supabaseに空のレコードを用意し、Gemini APIを使ってリッチな情報を埋めていくバッチ処理を構築しました。

この記事を執筆しているときに、こういった人間が最終確認を行うプロセスを含むバッチ処理が「Human-in-the-loop」と言われているのを初めて知りました(笑)

3. 技術スタックとアーキテクチャ

シンプルな構成ですが、モダンな技術選定を行いました。

Category Tech
Runtime Node.js (Next.js scripts)
Database Supabase (PostgreSQL)
LLM Google Gemini 2.0 Flash
SDK @google/generative-ai

4. 実装のポイント:Geminiへの指示出し(プロンプト)

ここが肝です。Geminiに対して曖昧な指示を出すと、扱いづらいテキストが返ってきます。
プログラムで扱いやすくするために、以下の工夫をしました。

① JSONモードの強制

Gemini APIの responseMimeType: "application/json" を活用し、確実にパース可能なJSONを受け取ります。

// 実際のコードイメージ
const model = genAI.getGenerativeModel({
  model: 'gemini-2.0-flash',
  generationConfig: { responseMimeType: "application/json" }
});

② スキーマの定義

プロンプト内で「どんなキーが必要か」を明確に定義します。
特に「生き物リスト」は配列で、「説明文」は文字数を指定することで、最終的にサイトのUIにそのまま流し込む想定で必要な情報を抽出するように定義しました。

(プロンプト例)
日本の水族館「{name}」の情報を以下のJSON形式で出力してください。
{
  "overview": "100文字以内の魅力的な紹介文",
  "highlight_fish": ["生き物A", "生き物B", "生き物C"],
  "access_info": "最寄り駅からのアクセス(簡潔に)"
}

5. 躓いたポイント

Geminiを活用しながら作っていましたが、個人的に躓いた点がいくつかありました。

APIバージョンの罠

gemini-1.5-flash を指定したら 404 Not Found に遭遇しました。APIキーで使用可能なモデル一覧を取得するスクリプトを書き、自分の環境では gemini-2.0-flash が使えることを突き止めて解決しました。

レスポンスの揺らぎ

JSONモードを指定しても、たまにオブジェクトではなく [{...}] という配列形式で返ってくることが判明しました。
これについては、コード側で型判定を行い、柔軟に受け取るロジックを追加することで対応しました。

6. 結果:150件のデータ収集にかかった時間

もちろんすべての水族館を網羅しているわけではなく、既に閉館している水族館もあったり、情報の正確性は担保しておりませんが、手作業なら恐らく1日で終わらないような作業が、スクリプトを実行して待つだけの数秒〜5分に短縮されました。

7. まとめ

「水族館に行きたい」という単純な欲求から、Gemini APIを活用したデータパイプライン構築までを行うことができました!

今後はこのデータを活用して、自分にとって使いやすい水族館マップサイトを作っていく予定です。
全国の水族館を回りたい野望をそのうち叶えます、そのうち。
実際に作ってみたサイト

Discussion