Konzertscraper und Websitedarstellung für Hamburg https://konzert.fun
  • JavaScript 55%
  • Python 36.9%
  • CSS 5.4%
  • HTML 2.7%
Find a file
Tobias Hahn 5d310eebb4 Künstlernamen-Extraktion: weitere Reihen-Prefixe und Status-Suffixe
- 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
2026-05-10 19:00:20 +02:00
python Künstlernamen-Extraktion: weitere Reihen-Prefixe und Status-Suffixe 2026-05-10 19:00:20 +02:00
systemd-files Scraper: Detailseiten sauber ueber Basisklasse laden, Timer auf 06:00 2026-03-16 09:13:24 +01:00
tests Künstlernamen-Extraktion: weitere Reihen-Prefixe und Status-Suffixe 2026-05-10 19:00:20 +02:00
web Frontend: F+K-Logo-Sandwich, Mobile-Desktop-Switch, Fehler-Overlay-Cleanup 2026-05-08 10:44:11 +02:00
.env.example Repo fuer Veroeffentlichung vorbereiten 2026-03-07 19:52:56 +01:00
.gitignore Komet: ntfy-Notifications für Karaoke-Veranstaltungen 2026-04-21 16:45:42 +02:00
ASSET-VERSIONEN.md Frontend: F+K-Logo-Sandwich, Mobile-Desktop-Switch, Fehler-Overlay-Cleanup 2026-05-08 10:44:11 +02:00
deploy-backend.py Stadtpark-Scraper Datumsformat-Fix + Backend-Deploy-Skript 2026-03-08 15:47:46 +01:00
deploy.py Repo fuer Veroeffentlichung vorbereiten 2026-03-07 19:52:56 +01:00
dev-server.py Neue-Konzerte: Goldener Glow, Dachboden-Markierung, Drag&Drop-Fix 2026-03-08 12:34:48 +01:00
discogs-misses.txt alles 2026-04-01 13:03:51 +02:00
e2e-ergebnis.txt test 2026-04-18 15:11:42 +02:00
LICENSE Repo fuer Veroeffentlichung vorbereiten 2026-03-07 19:52:56 +01:00
local-setup.py Repo fuer Veroeffentlichung vorbereiten 2026-03-07 19:52:56 +01:00
NEUES-VENUE.md Stadtpark Border entfernt, Border-Regel in Venue-Guide dokumentiert 2026-03-11 10:37:39 +01:00
package-lock.json Testabdeckung: 290 Tests (Python, vitest, Playwright) 2026-03-02 20:49:51 +01:00
package.json Accessibility + Testverbesserungen 2026-03-02 21:14:23 +01:00
playwright.config.js Testabdeckung: 290 Tests (Python, vitest, Playwright) 2026-03-02 20:49:51 +01:00
pyproject.toml Testumgebung: waitForTimeout durch zustandsbasierte Waits ersetzen, Fixtures haerten 2026-03-02 21:49:09 +01:00
README.md scraper notizen 2026-03-07 23:35:01 +01:00
README_LOCAL.md Repo fuer Veroeffentlichung vorbereiten 2026-03-07 19:52:56 +01:00
vitest.config.js Testabdeckung: 290 Tests (Python, vitest, Playwright) 2026-03-02 20:49:51 +01:00

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):
  • Benachrichtigungssystem (optional, für Email-Abos):
  • Webserver mit folgenden Headern:
    • Cross-Origin-Opener-Policy: same-origin
    • Cross-Origin-Embedder-Policy: require-corp
    • Cross-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() Einlasszeit
  • hole_preis_von_seite() Ticketpreis
  • hole_status_von_element/seite() "ausverkauft", "abgesagt", "verschoben"
  • hole_genres_von_seite() Genre-Erkennung (automatisch via genre_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.