日本酒データベースサイト開発秘話|全国の地酒を一元管理する技術
はじめに:「あの日本酒、どこの酒蔵?」という疑問から
「この日本酒、美味しいな。どこで造られているんだろう?」
居酒屋で地酒を飲むたびに、そんな疑問を抱いていました。スマホで調べても、情報がバラバラ。公式サイトがない酒蔵も多く、詳細な情報にたどり着くのに苦労します。
「だったら、全国の日本酒を一元管理できるデータベースを作ろう」
そう思い立ったのが、このプロジェクトの始まりでした。
完成したサイト: 日本酒データベース 参考記事: Note記事も合わせてご覧ください。
プロジェクト概要
作ったもの
日本酒データベースサイト
- 47都道府県の地酒データを収録
- 都道府県別の検索機能
- 銘柄名・酒蔵名・精米歩合・アルコール度数などの詳細情報
- レスポンシブ対応(スマホでも快適)
技術スタック
- フロントエンド: HTML, CSS, JavaScript(Vanilla JS)
- データ収集: Python(BeautifulSoup, Requests)
- データ形式: JSON
- ホスティング: GitHub Pages
- ドメイン: お名前.com(サブドメイン設定)
開発期間
- Phase 1: データ収集(2週間)
- Phase 2: DB設計(3日)
- Phase 3: フロントエンド開発(1週間)
- Phase 4: デバッグ・公開(3日)
合計: 約3週間
Phase 1: データ収集の苦労
最初の壁:データがない
日本酒のデータベースを作ろうとして、最初にぶつかった壁が「データがない」ということでした。
想定していたこと:
- どこかに日本酒の一覧データがある
- APIで取得できる
- CSVでダウンロードできる
現実:
- 統一されたデータベースは存在しない
- 各酒蔵のWebサイトに情報が散在
- サイトがない酒蔵も多数
データ収集戦略
結局、以下の方法でデータを集めました:
方法1: 公的機関のWebサイトからスクレイピング
import requests
from bs4 import BeautifulSoup
import json
# 日本酒の銘柄情報を取得
def scrape_sake_info(url):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
sake_list = []
# テーブルから情報を抽出
for row in soup.find_all('tr'):
cells = row.find_all('td')
if len(cells) >= 4:
sake = {
'銘柄名': cells[0].text.strip(),
'酒蔵名': cells[1].text.strip(),
'所在地': cells[2].text.strip(),
'種類': cells[3].text.strip()
}
sake_list.append(sake)
return sake_list
方法2: 酒造組合のリストを参照
各都道府県の酒造組合が公開している酒蔵リストを手動で収集。
方法3: 手動入力
どうしても自動取得できない情報(精米歩合、アルコール度数など)は、個別に調査して手動入力。
データ収集で学んだこと
- 完璧を求めない: 100%のデータを集めるのは不可能。70%でリリース。
- 段階的に拡充: 最初は主要銘柄のみ。徐々に追加。
- ユーザーからのフィードバック: 公開後に修正依頼を受け付ける仕組み。
収集したデータ量
- 酒蔵数: 約1,200蔵
- 銘柄数: 約3,500銘柄
- データ項目: 銘柄名、酒蔵名、都道府県、住所、種類、精米歩合、アルコール度数、特徴
Phase 2: データベース設計
JSON形式を採用した理由
最初はMySQLなどのRDBMSを検討しましたが、以下の理由でJSON形式を採用:
JSON形式のメリット:
- サーバー不要(静的サイトで完結)
- 軽量(ファイルサイズが小さい)
- JavaScriptから直接読み込める
- 無料ホスティング(GitHub Pages)で運用可能
デメリット:
- 大量データには不向き(数万件以上)
- 複雑な検索は苦手
今回は3,500銘柄程度なので、JSON形式で十分でした。
データ構造の設計
{
"北海道": [
{
"id": "hokkaido-001",
"銘柄名": "国士無双",
"酒蔵名": "高砂酒造",
"住所": "旭川市宮下通17丁目",
"種類": "純米大吟醸",
"精米歩合": "40%",
"アルコール度数": "16度",
"特徴": "淡麗辛口、フルーティーな香り"
},
...
],
"青森県": [...],
...
}
正規化の工夫
問題: 同じ酒蔵が複数の銘柄を持つ場合、データが重複する
解決策: 酒蔵情報と銘柄情報を分離
{
"brewery": {
"hokkaido-takasago": {
"name": "高砂酒造",
"address": "旭川市宮下通17丁目",
"established": "1899年",
"website": "https://..."
}
},
"sake": {
"hokkaido-001": {
"name": "国士無双",
"brewery_id": "hokkaido-takasago",
"type": "純米大吟醸",
"rice_polishing": "40%",
"alcohol": "16%"
}
}
}
これにより、データサイズが約30%削減できました。
Phase 3: フロントエンド開発
検索機能の実装
最も重要な機能が「検索」です。以下の検索方法を実装:
1. 都道府県別検索
function filterByPrefecture(prefecture) {
const sakeList = sakeData[prefecture];
displayResults(sakeList);
}
2. キーワード検索
function searchByKeyword(keyword) {
const results = [];
for (const prefecture in sakeData) {
sakeData[prefecture].forEach(sake => {
if (
sake.銘柄名.includes(keyword) ||
sake.酒蔵名.includes(keyword) ||
sake.特徴.includes(keyword)
) {
results.push(sake);
}
});
}
displayResults(results);
}
3. フィルター機能
- 種類(純米、吟醸、大吟醸など)
- 精米歩合(50%以下、50〜60%など)
- アルコール度数(15度以下、15〜17度など)
function filterResults(filters) {
let results = getAllSake();
if (filters.type) {
results = results.filter(s => s.種類 === filters.type);
}
if (filters.polishing) {
results = results.filter(s => {
const polishing = parseInt(s.精米歩合);
return polishing <= filters.polishing;
});
}
displayResults(results);
}
パフォーマンス最適化
3,500件のデータを扱うため、パフォーマンスが課題でした。
最適化1: 遅延読み込み
// 最初は100件だけ表示
let displayedCount = 100;
function loadMore() {
displayedCount += 100;
renderResults(results.slice(0, displayedCount));
}
// スクロール時に自動読み込み
window.addEventListener('scroll', () => {
if (isNearBottom()) {
loadMore();
}
});
最適化2: デバウンス処理
// 検索入力のデバウンス(連続入力時の負荷軽減)
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchByKeyword(e.target.value);
}, 300);
});
これにより、検索のレスポンスが劇的に改善しました。
UI/UXの工夫
1. カード型デザイン
各銘柄をカード形式で表示。視覚的に見やすい。
<div class="sake-card">
<h3 class="sake-name">国士無双</h3>
<p class="brewery-name">高砂酒造</p>
<div class="sake-details">
<span class="tag">純米大吟醸</span>
<span class="tag">精米歩合40%</span>
</div>
<p class="sake-description">淡麗辛口、フルーティーな香り</p>
</div>
2. 都道府県マップ
日本地図をクリックすると、その都道府県の日本酒が表示される。
const prefectureMap = document.querySelector('.japan-map');
prefectureMap.addEventListener('click', (e) => {
const prefecture = e.target.dataset.prefecture;
if (prefecture) {
filterByPrefecture(prefecture);
highlightPrefecture(prefecture);
}
});
3. レスポンシブ対応
スマホでの利用を想定し、完全レスポンシブ化。
@media (max-width: 768px) {
.sake-grid {
grid-template-columns: 1fr;
}
.sake-card {
width: 100%;
}
}
Phase 4: デバッグと公開
デバッグで見つかった問題
問題1: 検索結果が0件になる
原因: 全角・半角の違い
解決:
function normalizeText(text) {
return text
.replace(/[A-Za-z0-9]/g, s =>
String.fromCharCode(s.charCodeAt(0) - 0xFEE0)
)
.toLowerCase();
}
問題2: 表示が遅い
原因: 全データを一度にレンダリング
解決: 仮想スクロール実装(前述の遅延読み込み)
公開後の反応
Twitter(X)での反応:
- 「これ便利!」
- 「旅行先の日本酒探しに使える」
- 「データ間違ってるよ」(修正依頼)
アクセス数:
- 公開1週間: 500PV
- 公開1ヶ月: 3,000PV
- 公開3ヶ月: 10,000PV
技術的な学び
1. データが全て
どんなに優れたUIでも、データが貧弱では価値がない。データ収集に最も時間をかけるべき。
2. 完璧主義は敵
70%の完成度でリリースし、ユーザーのフィードバックで改善する方が早い。
3. パフォーマンスは重要
3,500件のデータでも、最適化しないと遅い。ユーザー体験を損なわない工夫が必須。
4. 静的サイトの可能性
サーバーレスでも、十分に高機能なWebアプリが作れる。コストを抑えられるメリットは大きい。
今後の展望
追加予定の機能
- ユーザー登録: お気に入り機能、レビュー投稿
- AI推薦: 好みに合った日本酒をレコメンド
- 購入リンク: Amazonや楽天へのリンク
- イベント情報: 酒蔵イベント、試飲会の情報
データ拡充
- 現在3,500銘柄 → 目標10,000銘柄
- ユーザーからの情報提供機能
- クラウドソーシングでデータ収集
まとめ
日本酒データベースサイトの開発を通じて、以下を学びました:
- データ収集の重要性: 良質なデータがサービスの価値を決める
- シンプルな技術選択: 複雑な技術は不要。Vanilla JSで十分
- ユーザー視点: 自分が欲しいものを作る
- 継続的改善: リリースしてからが本番
個人開発は楽しい。自分の「あったらいいな」を形にできる喜びは、何物にも代えがたいものです。
関連記事
参考: この記事は筆者のNote記事を基に、技術的な詳細を追加したものです。
完成したサイト: 日本酒データベース
この記事をシェアする
✍️ この記事を書いた人
MechaTora
社会保険労務士 × Web開発エンジニア × データサイエンティスト。
人事労務の専門知識とプログラミングスキルを活かして、実務に役立つツールやコンテンツを開発・発信しています。