A modern static website that finds the best wines from Vivino toplists available at Systembolaget (Swedish alcohol retailer). Features advanced filtering, real-time search, and detailed wine information.
- π Advanced Filtering - Filter by price, rating, country, wine style, food pairings
- π± Mobile Optimized - Perfect experience on all devices
- β‘ Client-side Search - Instant filtering without server requests
- π· Wine Details - Comprehensive wine information pages
- π Direct Purchase - Links to buy at Systembolaget
- π Match Scores - Color-coded match percentage badges
- π₯© Food Pairings - Emoji-based food pairing suggestions
This is a static site - no database required at runtime!
JSON Data β Static Site Generator β HTML Files β Static Server
- Data: Stored in
data/*.jsonfiles - Generator: Builds 150+ HTML pages from templates
- Server: Lightweight FastAPI serving static files
- Docker and Docker Compose
make build # Build Docker images
make update # Full pipeline: scrape β match β generate β restartThen open: http://localhost:8005
make start # Start server
make stop # Stop server
make update # Refresh all data from Vivino
make logs # View server logsmake fix-image ID=toplist_vivino_under_100_21 URL=https://images.vivino.com/thumbs/xxx.png
make generatemake scrape # Scrape default Vivino toplist (interactive)
make scrape-url URL=<url> # Scrape specific Vivino toplist URL
make match # Match scraped wines with Systembolaget
make generate # Generate static HTML site from JSON data
make update # Full pipeline: scrape β match β generate β restartmake fix-images # Fix missing images interactively
make fix-image ID=<id> URL=<url> # Set specific wine imageExample:
make fix-image ID=toplist_vivino_under_100_21 URL=https://images.vivino.com/thumbs/xxx_pb_x300.pngmake build # Build Docker images
make start # Start the web server (http://localhost:8005)
make stop # Stop all containers
make restart # Restart the web server
make logs # View web server logs
make shell # Open shell in container
make clean # Remove generated files and containers
make rebuild # Full rebuild and restartbest_wines/
βββ app/
β βββ static/ # CSS, JS assets
β βββ static_site/ # Generated HTML (output)
β βββ templates/ # Jinja2 templates
β βββ static_server.py # FastAPI static server
β βββ static_site_generator.py # Site generator
β βββ json_storage.py # JSON data helpers
βββ data/
β βββ wines.json # Wine data
β βββ matches.json # Wine-Systembolaget matches
β βββ toplists.json # Toplist metadata
β βββ stats.json # Statistics
βββ docker-compose.yaml
βββ Dockerfile.web
βββ Makefile
Scrape new wines from Vivino and match with Systembolaget:
make update # Full pipeline: scrape β match β generate β restartmake scrape-url URL=https://www.vivino.com/toplists/your-toplist-here
make match
make generate
make restartFinding the Wine ID:
The wine ID format is toplist_<toplist_id>_<rank>. You can find it:
- In the browser URL:
http://localhost:8005/wine/toplist_vivino_under_100_21 - From the wine detail page title
Finding the Image URL:
- Go to the wine page on Vivino.com
- Right-click on the wine bottle image
- Select "Copy image address"
- The URL should look like:
https://images.vivino.com/thumbs/xxxxxx_pb_x300.png
Commands:
# Interactive mode - prompts for each missing image
make fix-images
# Direct mode - set specific wine image
make fix-image ID=toplist_vivino_under_100_21 URL=https://images.vivino.com/thumbs/xxx_pb_x300.png
# Then regenerate the site
make generateExample workflow:
# 1. Find the wine that needs an image
# Visit: http://localhost:8005/wine/toplist_vivino_under_100_21
# Note: Shows "No image available"
# 2. Find the Vivino image URL
# Search Vivino for "Giacosa Fratelli Nebbiolo"
# Right-click bottle image β Copy image address
# 3. Set the image
make fix-image ID=toplist_vivino_under_100_21 URL=https://images.vivino.com/thumbs/abc123_pb_x300.png
# 4. Regenerate site
make generatemake generate # Regenerate HTML
make restart # Restart serverThe static site requires no configuration for basic operation.
Optional environment variables for development:
# Telegram Notifications (for data pipeline)
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
TELEGRAM_CHAT_ID=your_telegram_chat_idModern, clean UI inspired by qui design system:
- Light theme optimized for wine images
- Color-coded match percentage badges
- Emoji-based food pairings
- Responsive mobile design
- Smooth transitions and shadows
cd app
pip install -r requirements_web.txt
python static_site_generator.py # Generate site
python -m uvicorn static_server:app --reload --port 8000Templates are in app/templates/:
base.html- Base layoutindex.html- Homepagefilters.html- Filter page with client-side searchtoplist.html- Individual toplist pageswine_detail.html- Wine detail pages
After template changes:
make generate{
"id": "vivino_123",
"name": "Wine Name",
"rating": 4.2,
"country": "France",
"image_url": "https://...",
"simplified_food_pairings": ["beef", "cheese"]
}{
"vivino_wine_id": "vivino_123",
"systembolaget_product_id": "7727601",
"match_score": 85.5,
"systembolaget_product": {
"full_name": "Wine at Systembolaget",
"price": 149.0
}
}Enjoy discovering amazing wines! π·