公的機関に散在する登山道情報を集約するWebサービスを作った
開発の経緯
趣味の登山関連で何か公共的な意義があり、ポートフォリオにも出来そうなモノを作りたいと思っている中で閃いたアイデアを具現化しました。
まずこちらのサイトを見ていただければ幸いです。
これは各公的機関に散在したまま放置されている登山道の最新通行止め情報などを集約・表示するWebアプリケーションで、毎晩各サイトの情報を取得し、最新情報を常に取得できるようになっています。
世の中の登山道状況を調査し公表している機関のサイトは更新の有無がすぐには分かりづらく、また更新頻度も多くはないので、登山者(私)は思い出したときにたまに見に行くくらいでした。
それらは確かに貴重な情報であるにも関わらず、世の中でうまく有効活用がなされていないのではないかと考えています。
そんな中でこのサイトは、ニッチですが刺さる層にはかなり刺さるんじゃないかなと思っています。
技術スタック
- バックエンド: Django 6.0, PostgreSQL
- フロントエンド: Tailwind CSS 4.x, Vite
- データ収集: httpx, trafilatura
- AI処理: Gemini API, Pydantic (+ DeepSeek API, OpenAI API)
- テキストマッチング: RapidFuzz, SudachiPy
- インフラ: Cloud Run, Supabase, Cloudflare, Docker
データフロー
- trafilaturaを使って情報源ごとに登山道状況をスクレイピング
- SHA256ハッシュ値により前回巡回時とのサイト変更有無を判定(無ければ以降のLLM処理をスキップ)
- それをLLM(Gemini)に投げ、サイトデータを私が用意したスキーマ(登山道名、山名、他各種情報のリスト)へ構造化
- LLM出力と既存データとのあいだで類似度判定で名寄せをし、データ新規作成/更新処理
- その際に利用したプロンプト関連の情報やLLM利用記録等メタデータもDBへ永続化
工夫/苦労した点
プロジェクトを開始した当初の時点で、各サイト異なる記述形式のデータに対してLLMを利用して構造化するという方針だけは決まっていました。ただそれだけで情報をうまく処理できるわけではないことはすぐにわかってきました。
いくつかの問題がありましたが、そのうちで主要なものを以下に挙げていきます。
LLMが出力した各項目自体にも揺らぎがある
出力のJSONスキーマへの構造化自体はLLMのAPIのコードに数行足せばよいだけなのですが、課題は名寄せのために必要な各項目ごとの表記もある程度統一し、既存データとの同定を行う必要があることでした。
その解決のために浮かぶ方法として、まずLLM設定の温度Temperatureを低い値にするというものがあります。この値を0にすれば同じ入力に対しては基本的に毎回同じ出力を吐き出してくれます。
しかし今回の場合、
- 入力となるサイトのデータ自体が更新のあるたびに変わること
- 情報源によっては山名の具体表記がなく、マイナーな登山道名だけで山名を推測する必要があり、少しの入力の違いでも出力のブレが生じうること
などの問題で、温度の設定だけでは不十分でした。
しかしこの問題は、ある方法を導入したところすぐに解決することになりました。
その結果だけを先に言うと、Geminiによる出力とGPTによる出力、つまり全く異なるAIの間で同定テストを行っても、なんと99%以上の精度で既存情報と正確に名寄せをすることが可能になりました。
グラウンディングツール
その方法はシンプルであり、つまり検索ツールをLLMのAPI呼び出しで用いることです。私はコスト削減の観点で当初はそれ無しで試行錯誤をしていました。
ところで、Googleのドキュメントでは検索ツールは「グラウンディングツール」と紹介されています。
- Google 検索によるグラウンディング
https://ai.google.dev/gemini-api/docs/google-search?hl=ja
「グラウンディング」という言葉は日本語として馴染みが薄いかもしれませんが、それを聞いて私が唯一思い出すのは、AIに関わる哲学の問題である記号接地問題です。
- シンボルグラウンディング問題 - Wikipedia
https://ja.wikipedia.org/wiki/シンボルグラウンディング問題
今回のプロジェクトで、その検索ツールがグラウンディングツールと呼ばれる理由が個人的に明確になりました。
OpenAIとGoogleの両者のアルゴリズムが異なっていたとしても、検索ツールはおそらく同じGoogle検索であり、この検索結果/検索「空間」を参照することを通してこの二つのAIは同じ出力を担保できていたのだと思います。
本題に戻ります。すると逆に言えば、今回のような目的で検索ツールを使用する場合、その両AIの違いはそんなにないことになります。
しかしGoogleの場合検索ツールが月5000件の無料枠がある(26/01/24現在、Gemini-3-flash-previewの場合)ようなので、このプロジェクトでは基本的にGeminiを利用していくことにしました。
- 上述Geminiモデルの検索ツール料金:
https://ai.google.dev/gemini-api/docs/pricing?hl=ja#gemini-3-flash-preview
付記しておくと、一方のOpenAIでは無料枠がなく、初めから検索1000呼び出しあたり10$の料金がかかることになります。
- OpenAIモデル共通の検索ツール料金
https://platform.openai.com/docs/pricing#built-in-tools
Gemini-3-flashモデルに対応するOpenAIのモデルはおそらくGPT-5-miniであり、私の使い方の場合その一度のAPI呼び出しで2~3円ほどで、Gemini 3 flashよりも割安です。しかしサーバーの裏側で一度のリクエストで何度呼び出されたのかを調べずとも1$/検索回数は明らかにコストは高くつき、また推論能力的にも今回の場合両者は同程度と見えたため、Geminiの利用を最終的に決めることになりました。
と、少し分量が長くなってきたためここで一旦切って、以降はまた次回書きたいと思います。
しかし続きを実際に書くかどうかも不明であるため(多分書くけど)、このプロジェクトのGitHubリポジトリを最後に掲載して今回の記事を終わりにしたいと思います。
もし関東在住の登山趣味の人がいましたら、今回立ち上げたサイトを利用していただけたら嬉しいです。
機能やUIの改善案等ありましたらぜひ仰っていただけると助かります。
ここまでお読みいただきありがとうございました。

Discussion