diff --git a/plugins/neuheiten/quellen/cliquenabend.py b/plugins/neuheiten/quellen/cliquenabend.py
index f6ef03f..616bd79 100644
--- a/plugins/neuheiten/quellen/cliquenabend.py
+++ b/plugins/neuheiten/quellen/cliquenabend.py
@@ -45,13 +45,9 @@ class CliquenabendQuelle(QuellenAdapter):
treffer.append(self._zeile(zellen, url))
return [t for t in treffer if t is not None], []
- def _release_jahr(self, spiel_url: str) -> str | None:
- """Liest das Release-Feld von der Spieldetailseite.
+ def _detailseite(self, spiel_url: str):
+ """Lädt eine Spieldetailseite und gibt das BeautifulSoup-Dokument zurück.
- Die Detailseite (…/spiele/….html) enthält ein Feld
- `Release:
JAHR
` — meist nur das Jahr (z. B.
- „2025“), gelegentlich ein genaues Datum. Liefert die rohe Angabe
- zurück; `jahr_aus_datumsangabe` leitet daraus später das Jahr ab.
Fehler (404, Strukturänderung) führen zu None und blockieren nie.
"""
try:
@@ -61,7 +57,22 @@ class CliquenabendQuelle(QuellenAdapter):
return None
if antwort.status_code != 200:
return None
- suppe = BeautifulSoup(antwort.content, "html.parser")
+ return BeautifulSoup(antwort.content, "html.parser")
+
+ def _release_jahr(self, spiel_url: str) -> str | None:
+ """Liest das Release-Feld von der Spieldetailseite.
+
+ Die Detailseite (…/spiele/….html) enthält ein Feld
+ `Release:
JAHR
` — meist nur das Jahr (z. B.
+ „2025“), gelegentlich ein genaues Datum. Liefert die rohe Angabe
+ zurück; `jahr_aus_datumsangabe` leitet daraus später das Jahr ab.
+ """
+ suppe = self._detailseite(spiel_url)
+ if suppe is None:
+ return None
+ return self._release_jahr_aus_suppe(suppe)
+
+ def _release_jahr_aus_suppe(self, suppe) -> str | None:
fett = suppe.find(
"b", string=lambda s: isinstance(s, str) and s.strip() == "Release:"
)
@@ -81,6 +92,27 @@ class CliquenabendQuelle(QuellenAdapter):
wert = text.strip()
return wert or None
+ def _cover_bild(self, spiel_url: str) -> str | None:
+ """Liest das Cover-Bild von der Spieldetailseite."""
+ suppe = self._detailseite(spiel_url)
+ if suppe is None:
+ return None
+ return self._cover_bild_aus_suppe(suppe)
+
+ def _cover_bild_aus_suppe(self, suppe) -> str | None:
+ """Die Detailseite zeigt das Cover in der rechten Leiste als
+ `
`.
+ Liefert die absolute Bild-URL oder None.
+ """
+ for bild in suppe.find_all("img", src=True):
+ src = str(bild["src"])
+ if "/images/db/" not in src:
+ continue # Template-Grafiken (Logo, Pfeile etc.) auslassen
+ eltern_link = bild.find_parent("a", href=re.compile(r"^/spiele/"))
+ if eltern_link is not None:
+ return urljoin(BASIS_URL, src)
+ return None
+
def _zeile(self, zellen, url: str) -> QuellenTreffer | None:
spiel_zelle = zellen[3]
titel_link = spiel_zelle.find("a", href=re.compile(r"^/spiele/"))
@@ -99,20 +131,28 @@ class CliquenabendQuelle(QuellenAdapter):
autor = zellen[4].get_text(" ", strip=True) or None
datum = None
+ bild_url = bild_aus_element(spiel_zelle, quellen_url)
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)
- # Release-Feld der Detailseite hat Vorrang: dort steht das echte
- # Erscheinungsjahr/-datum statt des Update-Datums der Mese-Tabelle.
+ # Release-Feld und Cover-Bild der Detailseite haben Vorrang: dort
+ # steht das echte Erscheinungsjahr/-datum bzw. das Cover statt des
+ # Update-Datums bzw. des Messe-Fotos aus der Tabelle.
if titel_link is not None:
href = titel_link.get("href")
if href:
- release = self._release_jahr(urljoin(BASIS_URL, href))
- if release:
- datum = release
+ detail_url = urljoin(BASIS_URL, href)
+ suppe_detail = self._detailseite(detail_url)
+ if suppe_detail is not None:
+ release = self._release_jahr_aus_suppe(suppe_detail)
+ if release:
+ datum = release
+ cover = self._cover_bild_aus_suppe(suppe_detail)
+ if cover:
+ bild_url = cover
return QuellenTreffer(
titel=titel,
@@ -120,7 +160,7 @@ class CliquenabendQuelle(QuellenAdapter):
autor=autor,
erscheinungsdatum_oder_quartal=datum,
quellen_url=quellen_url,
- bild_url=bild_aus_element(spiel_zelle, quellen_url),
+ bild_url=bild_url,
)
diff --git a/tests/test_neuheiten_quellen.py b/tests/test_neuheiten_quellen.py
index f576a89..7e440b2 100644
--- a/tests/test_neuheiten_quellen.py
+++ b/tests/test_neuheiten_quellen.py
@@ -130,10 +130,14 @@ CLIQUENABEND_SEITE = """
CLIQUENABEND_DETAIL_1 = """
+ Galactic Cruise
+
+ 
+
Release:
- 2025
+ 2025
Anzahl der Spieler:
- 1 bis 4 Spieler
+ 1 bis 4 Spieler
"""
@@ -332,6 +336,8 @@ def test_parser_cliquenabend_liest_tabellenspalten():
# Release-Feld der Detailseite schlägt das Update-Datum der Tabelle:
assert erster.erscheinungsdatum_oder_quartal == "2025"
assert erster.quellen_url == "https://www.cliquenabend.de/spiele/790610-Galactic-Cruise.html"
+ # Cover-Bild aus der Detailseite (statt Messe-Foto):
+ assert erster.bild_url == "https://www.cliquenabend.de/images/db/62452_360x240.jpg"
assert ergebnis.treffer[1].verlag == "Piatnik"
# Ohne Release-Feld auf der Detailseite fällt der Parser auf das
# Update-Datum zurück: