Plugin neuheiten: Web-Quellen-Crawler (4 Adapter, Rate-Limit, Dedup-Gate, Sync-UI)
This commit is contained in:
97
plugins/neuheiten/quellen/cliquenabend.py
Normal file
97
plugins/neuheiten/quellen/cliquenabend.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Adapter für cliquenabend.de Messe-Eventseiten (z. B. SPIEL 2025 Essen).
|
||||
|
||||
Seite: …/events/130000-SPIEL-2025-Essen.html (große Einzelseite, ~4 MB).
|
||||
|
||||
Struktur (Stand Analyse August 2026): Eine einzige Tabelle mit der
|
||||
Kopfzeile Update | Verlag | Stand | Spiel | Autor; eine Zeile pro Spiel.
|
||||
Titel steht in `a[href^="/spiele/"] > b`, Verlage in fett formatierten
|
||||
`div`-Blöcken der Verlagsspalte, das Update-Datum in der Form TT.MM.JJ.
|
||||
Die Seite ist nicht paginiert.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from .basis import QuellenAdapter, QuellenTreffer
|
||||
|
||||
DATUM_MUSTER = re.compile(r"\d{2}\.\d{2}\.(\d{2})\b")
|
||||
BASIS_URL = "https://www.cliquenabend.de"
|
||||
|
||||
|
||||
class CliquenabendQuelle(QuellenAdapter):
|
||||
name = "cliquenabend"
|
||||
anzeigename = "Cliquenabend Messe-Übersicht"
|
||||
start_urls = ("https://www.cliquenabend.de/events/130000-SPIEL-2025-Essen.html",)
|
||||
|
||||
def seite_verarbeiten(self, inhalt: bytes, url: str):
|
||||
suppe = BeautifulSoup(inhalt, "html.parser")
|
||||
treffer: list[QuellenTreffer] = []
|
||||
tabelle = suppe.find("table")
|
||||
if tabelle is None:
|
||||
return [], [] # Strukturänderung: nichts tun, kein Crash
|
||||
for zeile in tabelle.find_all("tr"):
|
||||
zellen = zeile.find_all("td")
|
||||
if len(zellen) < 5:
|
||||
continue
|
||||
if zellen[0].get_text(strip=True).lower() == "update":
|
||||
continue # Kopfzeile
|
||||
treffer.append(self._zeile(zellen, url))
|
||||
return [t for t in treffer if t is not None], []
|
||||
|
||||
def _zeile(self, zellen, url: str) -> QuellenTreffer | None:
|
||||
spiel_zelle = zellen[3]
|
||||
titel_link = spiel_zelle.find("a", href=re.compile(r"^/spiele/"))
|
||||
titel = None
|
||||
quellen_url = url
|
||||
if titel_link is not None:
|
||||
fett = titel_link.find("b")
|
||||
titel = (fett or titel_link).get_text(" ", strip=True)
|
||||
href = titel_link.get("href")
|
||||
if href:
|
||||
from urllib.parse import urljoin
|
||||
|
||||
quellen_url = urljoin(BASIS_URL, href)
|
||||
if not titel:
|
||||
return None
|
||||
|
||||
verlag = _verlage(zellen[1])
|
||||
autor = zellen[4].get_text(" ", strip=True) or None
|
||||
|
||||
datum = None
|
||||
jahr_text = zellen[0].get_text(strip=True)
|
||||
fund = DATUM_MUSTER.search(jahr_text)
|
||||
if fund:
|
||||
# Datum so übernehmen, wie es in der Quelle steht (z. B. „15.10.25“).
|
||||
datum = fund.group(0)
|
||||
|
||||
return QuellenTreffer(
|
||||
titel=titel,
|
||||
verlag=verlag,
|
||||
autor=autor,
|
||||
erscheinungsdatum_oder_quartal=datum,
|
||||
quellen_url=quellen_url,
|
||||
)
|
||||
|
||||
|
||||
def _verlage(zelle) -> str | None:
|
||||
"""Verlagsspalte: fett formatierte `div`-Blöcke enthalten die Namen."""
|
||||
namen = [
|
||||
div.get_text(" ", strip=True)
|
||||
for div in zelle.find_all("div")
|
||||
if div.get_text(" ", strip=True)
|
||||
]
|
||||
if not namen:
|
||||
roh = zelle.get_text(" ", strip=True)
|
||||
namen = [teil for teil in (roh.split(",") if roh else [])
|
||||
if teil.strip() and teil.strip().lower() != "profil"]
|
||||
bereinigt = []
|
||||
for name in namen:
|
||||
name = name.strip().rstrip(",").strip()
|
||||
# „Piatnik Profil ,“ → Profil-Link-Text hängt am Namen; abschneiden.
|
||||
if name.lower().endswith(" profil"):
|
||||
name = name[: -len(" Profil")].strip()
|
||||
if name and name.lower() != "profil":
|
||||
bereinigt.append(name)
|
||||
return ", ".join(bereinigt) if bereinigt else None
|
||||
Reference in New Issue
Block a user