Cliquenabend: Cover-Bilder aus den Spieldetailseiten übernehmen
This commit is contained in:
@@ -45,13 +45,9 @@ class CliquenabendQuelle(QuellenAdapter):
|
|||||||
treffer.append(self._zeile(zellen, url))
|
treffer.append(self._zeile(zellen, url))
|
||||||
return [t for t in treffer if t is not None], []
|
return [t for t in treffer if t is not None], []
|
||||||
|
|
||||||
def _release_jahr(self, spiel_url: str) -> str | None:
|
def _detailseite(self, spiel_url: str):
|
||||||
"""Liest das Release-Feld von der Spieldetailseite.
|
"""Lädt eine Spieldetailseite und gibt das BeautifulSoup-Dokument zurück.
|
||||||
|
|
||||||
Die Detailseite (…/spiele/….html) enthält ein Feld
|
|
||||||
`<b>Release:</b><br> JAHR <br>` — 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.
|
Fehler (404, Strukturänderung) führen zu None und blockieren nie.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@@ -61,7 +57,22 @@ class CliquenabendQuelle(QuellenAdapter):
|
|||||||
return None
|
return None
|
||||||
if antwort.status_code != 200:
|
if antwort.status_code != 200:
|
||||||
return None
|
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
|
||||||
|
`<b>Release:</b><br> JAHR <br>` — 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(
|
fett = suppe.find(
|
||||||
"b", string=lambda s: isinstance(s, str) and s.strip() == "Release:"
|
"b", string=lambda s: isinstance(s, str) and s.strip() == "Release:"
|
||||||
)
|
)
|
||||||
@@ -81,6 +92,27 @@ class CliquenabendQuelle(QuellenAdapter):
|
|||||||
wert = text.strip()
|
wert = text.strip()
|
||||||
return wert or None
|
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
|
||||||
|
`<a href="[diese Spielseite]"><img src="/images/db/ID_360x240.jpg">`.
|
||||||
|
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:
|
def _zeile(self, zellen, url: str) -> QuellenTreffer | None:
|
||||||
spiel_zelle = zellen[3]
|
spiel_zelle = zellen[3]
|
||||||
titel_link = spiel_zelle.find("a", href=re.compile(r"^/spiele/"))
|
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
|
autor = zellen[4].get_text(" ", strip=True) or None
|
||||||
|
|
||||||
datum = None
|
datum = None
|
||||||
|
bild_url = bild_aus_element(spiel_zelle, quellen_url)
|
||||||
jahr_text = zellen[0].get_text(strip=True)
|
jahr_text = zellen[0].get_text(strip=True)
|
||||||
fund = DATUM_MUSTER.search(jahr_text)
|
fund = DATUM_MUSTER.search(jahr_text)
|
||||||
if fund:
|
if fund:
|
||||||
# Datum so übernehmen, wie es in der Quelle steht (z. B. „15.10.25“).
|
# Datum so übernehmen, wie es in der Quelle steht (z. B. „15.10.25“).
|
||||||
datum = fund.group(0)
|
datum = fund.group(0)
|
||||||
|
|
||||||
# Release-Feld der Detailseite hat Vorrang: dort steht das echte
|
# Release-Feld und Cover-Bild der Detailseite haben Vorrang: dort
|
||||||
# Erscheinungsjahr/-datum statt des Update-Datums der Mese-Tabelle.
|
# 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:
|
if titel_link is not None:
|
||||||
href = titel_link.get("href")
|
href = titel_link.get("href")
|
||||||
if href:
|
if href:
|
||||||
release = self._release_jahr(urljoin(BASIS_URL, href))
|
detail_url = urljoin(BASIS_URL, href)
|
||||||
if release:
|
suppe_detail = self._detailseite(detail_url)
|
||||||
datum = release
|
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(
|
return QuellenTreffer(
|
||||||
titel=titel,
|
titel=titel,
|
||||||
@@ -120,7 +160,7 @@ class CliquenabendQuelle(QuellenAdapter):
|
|||||||
autor=autor,
|
autor=autor,
|
||||||
erscheinungsdatum_oder_quartal=datum,
|
erscheinungsdatum_oder_quartal=datum,
|
||||||
quellen_url=quellen_url,
|
quellen_url=quellen_url,
|
||||||
bild_url=bild_aus_element(spiel_zelle, quellen_url),
|
bild_url=bild_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -130,10 +130,14 @@ CLIQUENABEND_SEITE = """
|
|||||||
|
|
||||||
CLIQUENABEND_DETAIL_1 = """
|
CLIQUENABEND_DETAIL_1 = """
|
||||||
<html><body>
|
<html><body>
|
||||||
|
<h1>Galactic Cruise</h1>
|
||||||
|
<div class="right_bar"><center>
|
||||||
|
<a href="/spiele/790610-Galactic-Cruise.html"><img src="/images/db/62452_360x240.jpg" border="0"></a><br>
|
||||||
|
</center></div>
|
||||||
<b>Release:</b><br>
|
<b>Release:</b><br>
|
||||||
2025 <br>
|
2025 <br>
|
||||||
<b>Anzahl der Spieler:</b><br>
|
<b>Anzahl der Spieler:</b><br>
|
||||||
1 bis 4 Spieler<br>
|
1 bis 4 Spieler<br>
|
||||||
</body></html>
|
</body></html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -332,6 +336,8 @@ def test_parser_cliquenabend_liest_tabellenspalten():
|
|||||||
# Release-Feld der Detailseite schlägt das Update-Datum der Tabelle:
|
# Release-Feld der Detailseite schlägt das Update-Datum der Tabelle:
|
||||||
assert erster.erscheinungsdatum_oder_quartal == "2025"
|
assert erster.erscheinungsdatum_oder_quartal == "2025"
|
||||||
assert erster.quellen_url == "https://www.cliquenabend.de/spiele/790610-Galactic-Cruise.html"
|
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"
|
assert ergebnis.treffer[1].verlag == "Piatnik"
|
||||||
# Ohne Release-Feld auf der Detailseite fällt der Parser auf das
|
# Ohne Release-Feld auf der Detailseite fällt der Parser auf das
|
||||||
# Update-Datum zurück:
|
# Update-Datum zurück:
|
||||||
|
|||||||
Reference in New Issue
Block a user