- JavaScript 55%
- Python 36.9%
- CSS 5.4%
- HTML 2.7%
- strip_status: (abgesagt), (verlegt), (verschoben) als Suffix entfernen - Stellwerk: "[…]"-Prefix, "Konzert der Band ", "Konzert: " strippen - Zinnschmelze: "WELCOME MUSIC CONCERT: " strippen - Hebebühne: " in Hamburg"-Suffix strippen - Honigfabrik: "X mit Y" → Y |
||
|---|---|---|
| python | ||
| systemd-files | ||
| tests | ||
| web | ||
| .env.example | ||
| .gitignore | ||
| ASSET-VERSIONEN.md | ||
| deploy-backend.py | ||
| deploy.py | ||
| dev-server.py | ||
| discogs-misses.txt | ||
| e2e-ergebnis.txt | ||
| LICENSE | ||
| local-setup.py | ||
| NEUES-VENUE.md | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.js | ||
| pyproject.toml | ||
| README.md | ||
| README_LOCAL.md | ||
| vitest.config.js | ||
konzert.fun
Konzertübersicht für Hamburg – als Vorlage auch für andere Städte nutzbar.
Einmal täglich werden Konzertdaten von Venue-Webseiten gescraped und in eine SQLite-Datenbank geschrieben. Das Frontend lädt diese Datenbank in den Browser (OPFS) und visualisiert alles lokal in Vanilla JavaScript.
Features
- Kalenderansicht mit farblich kodierten Venues
- Drag & Drop zum Sortieren der Venues
- Suchfunktion mit chronologischer Ansicht
- Zeitraum-Slider zum Filtern
- Desktop- und Mobile-Version
- Optionales Email-Benachrichtigungssystem
Stack
- Frontend: Vanilla JS (ES6 Modules), HTML5, CSS3
- Backend: Python3 (aiohttp-Scraper, Flask-API), SQLite
- Browser-DB: SQLite in OPFS via Web Worker
Voraussetzungen
- Python 3.10+
- Scraper (Kernfunktion):
- aiohttp – async HTTP-Requests
- selectolax – HTML-Parsing
- python-dateutil – Datumsberechnung
- Benachrichtigungssystem (optional, für Email-Abos):
- Webserver mit folgenden Headern:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corpCross-Origin-Resource-Policy: same-origin
Quick Start
# 1. Scraper ausführen (erzeugt web/konzerte.db)
python3 -m python.main
# 2. Dev-Server starten
python3 local-setup.py
Dann http://localhost:8000 öffnen. Siehe README_LOCAL.md für weitere Optionen.
Projektstruktur
web/ # Frontend (wird auf den Webserver deployed)
js/ # ES6 Module
css/ # Stylesheets
index.html # Desktop
mobile.html # Mobile
python/ # Backend
locations/ # Ein Scraper pro Venue
api/ # Flask-API (Benachrichtigungen)
worker/ # Benachrichtigungs-Worker
scraper.py # Basis-Klasse für alle Scraper
main.py # Scraper-Entry-Point
tests/ # Python-, JS- und E2E-Tests
Für andere Städte anpassen
Das Projekt lässt sich für andere Städte anpassen. Die vollständige Schritt-für-Schritt-Checkliste (inkl. Farben, Logo, Initial-Position) findest du in NEUES-VENUE.md.
Kurzfassung: Scraper schreiben, registrieren, CSS-Farben für alle drei Farbschemas definieren, SVG-Logo erstellen und die Default-Position festlegen.
Scraper schreiben
Jeder Venue hat eine eigene Scraper-Klasse in python/locations/. Ein Scraper erbt von python.scraper.Scraper und implementiert Methoden zum Extrahieren von Konzertdaten aus der Venue-Webseite.
Minimales Beispiel:
from python.scraper import Scraper
from selectolax.parser import HTMLParser
class MeinVenue(Scraper):
BASIS_LINK = "https://www.mein-venue.de/"
ALLGEMEINER_LINK = "/programm/{programmjahr}/{programmmonat}/"
LOCATION = "Mein Venue"
DATUM_FORMAT = "%d.%m.%Y"
ZEIT_FORMAT = "%H:%M"
def __init__(self):
# True = Scraper ruft Detailseiten auf, False = nur Übersichtsseite
super().__init__(True)
async def hole_allgemeine_seite(self, jahr, monat):
"""Übersichtsseite für einen Monat laden."""
url = self.BASIS_LINK + f"/programm/{jahr}/{monat}/"
return await self.fetch_html(url)
def hole_konzert_elemente(self, allgemeine_seite):
"""Einzelne Konzert-Elemente aus der Übersichtsseite extrahieren."""
if allgemeine_seite is None:
return []
return allgemeine_seite.css("div.event")
def skippe_element(self, element, jahr, monat):
"""True zurückgeben wenn dieses Element übersprungen werden soll."""
return False
def skippe_seite(self, seite, jahr, monat):
"""True zurückgeben wenn diese Detailseite übersprungen werden soll."""
return False
def hole_konzertlink_von_element(self, element):
"""Link zur Detailseite aus dem Element extrahieren."""
return element.css_first("a").attributes.get("href")
# Daten können entweder vom Element (Übersichtsseite) oder von der
# Detailseite extrahiert werden. Nicht benötigte Methoden geben None zurück.
def hole_titel_von_element(self, element):
return None # wird von Detailseite geholt
def hole_titel_von_seite(self, seite):
return seite.css_first("h1").text().strip()
def hole_start_datum_von_element(self, element):
return None
def hole_start_datum_von_seite(self, seite):
return seite.css_first(".datum").text().strip()
def hole_start_zeit_von_element(self, element):
return None
def hole_start_zeit_von_seite(self, seite):
return seite.css_first(".uhrzeit").text().strip()
# Diese Methoden können None zurückgeben:
def hole_unterraum_von_element(self, element): return None
def hole_unterraum_von_seite(self, seite): return None
def hole_start_dt_von_element(self, element): return None
def hole_start_dt_von_seite(self, seite): return None
Detaillierte Notizen zur Scraper-Architektur, Preis-/Einlass-Extraktion und externen Ticketanbietern: python/SCRAPER-NOTIZEN.md
Weitere optionale Methoden (mit Default-Implementierung in Scraper):
hole_einlass_von_seite()– Einlasszeithole_preis_von_seite()– Ticketpreishole_status_von_element/seite()– "ausverkauft", "abgesagt", "verschoben"hole_genres_von_seite()– Genre-Erkennung (automatisch viagenre_matcher)hole_externe_ticket_url_von_seite()– Ticket-URL (automatische Preisextraktion)
2. Scraper registrieren
Den neuen Scraper in python/main.py importieren und zur SCRAPERS-Liste hinzufügen.
3. CSS-Farben definieren
In web/css/colors.css für jede Location eine Klasse anlegen:
.MeinVenue {
--location-bg: #1a1a2e;
--location-color: #e0e0e0;
}
Bei Locations mit hellem/weißem Hintergrund zusätzlich:
--location-separator: black;
Konfiguration
Umgebungsvariablen (siehe .env.example):
| Variable | Beschreibung | Default |
|---|---|---|
DEPLOY_TARGET |
rsync-Ziel für Frontend-Deploy | server:/srv/http/konzert.fun/ |
DB_SOURCE |
rsync-Quelle für DB-Download | server:/srv/http/konzert.fun/konzerte.db |
USERS_DB |
Pfad zur Users-Datenbank | /var/lib/konzertfun/users.db |
KONZERTE_DB |
Pfad zur Konzerte-Datenbank | web/konzerte.db (relativ zum Projekt) |
RESEND_API_KEY |
API-Key für Email-Versand (Resend) | – |
BASE_URL |
Öffentliche URL der Seite | https://konzert.fun |
ALLOWED_ORIGINS |
CORS Origins (kommasepariert) | https://konzert.fun |
NTFY_URL |
ntfy-Server für Scraper-Benachrichtigungen | https://ntfy.sh |
Deploy
# Frontend deployen (inkl. automatischer Asset-Versionierung)
python3 deploy.py
# Nur anzeigen was sich ändern würde
python3 deploy.py --dry
Lizenz
MIT – siehe LICENSE.