From 8b38bc6c893c035d492ad8cb5a96546128716875 Mon Sep 17 00:00:00 2001 From: Flo Hartmann Date: Tue, 25 Aug 2026 00:55:07 +0000 Subject: [PATCH] =?UTF-8?q?Cliquenabend:=20Cover-Bilder=20aus=20den=20Spie?= =?UTF-8?q?ldetailseiten=20=C3=BCbernehmen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/neuheiten/quellen/cliquenabend.py | 66 ++++++++++++++++++----- tests/test_neuheiten_quellen.py | 10 +++- 2 files changed, 61 insertions(+), 15 deletions(-) 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: