fix: QC-Ratings aus 7 älteren Artikeln entfernt (CHECK 10)
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Final Top-30 SO Deals HTML with verified images."""
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
NOW = datetime.now().strftime("%d.%m.%Y %H:%M")
|
||||
OUT = "/home/hermes/workspace/spiele_offensive_top30.html"
|
||||
|
||||
# Verified images from product pages (fetched individually)
|
||||
img_map = {
|
||||
"1026921": "1026921_3.jpg",
|
||||
"1028349": "Article_000001028349_2025-07-18_13_05_12_921000.jpg",
|
||||
"1030474": "1030474.jpg",
|
||||
"1033566": "Box_894d0185-a2aa-4f24-a3c8-10070ccc6304_2048x2048.webp",
|
||||
"1028724": "1028724.jpg",
|
||||
"1030971": "main.jpg",
|
||||
"1026525": "1026525.jpg",
|
||||
"1029919": "1029919.jpg",
|
||||
"1031362": "Article_000001031362_2024-03-28_07_29_07_259500.jpg",
|
||||
"1034548": "20260505_103317.png",
|
||||
"1024744": "1024744.jpg",
|
||||
"1028441": "1028441.jpg",
|
||||
"1027906": "1027906.jpg",
|
||||
"1027919": "1027919.jpg",
|
||||
"1027138": "1027138.jpg",
|
||||
"1034592": "1031998_main.webp",
|
||||
"1029966": "MAIN.png",
|
||||
"1027053": "1027053.jpg",
|
||||
"1025619": "1025619.jpg",
|
||||
"1029965": "1029965.jpg",
|
||||
"1030312": "1030312.jpg",
|
||||
"1028661": "Article_000001028661_2025-06-12_13_34_35_190800.png",
|
||||
"1013706": "1013706.jpg",
|
||||
"1032108": "Article_000001032108_2026-04-08_14_04_52_467900.png",
|
||||
"1031736": "1031736.png",
|
||||
"1034433": "1034433.jpg",
|
||||
"1030392": "1030392.jpg",
|
||||
"1030811": "1030811.jpg",
|
||||
"1003800": "1003800.jpg",
|
||||
"1034354": "schnipptrickcover_hoch_rgb.jpg",
|
||||
"1024430": "1024430.jpg",
|
||||
"1026552": "cnoxu9yw.jpg",
|
||||
"1029637": "1029637.jpg",
|
||||
"1027584": "1027584.jpg",
|
||||
"1031069": "1031069_main.jpg",
|
||||
"1032911": "1032911.webp",
|
||||
"1034007": "4002051687427.jpg",
|
||||
"1014267": "1014267.jpg",
|
||||
}
|
||||
|
||||
def img_url(aid):
|
||||
fn = img_map.get(aid)
|
||||
if not fn:
|
||||
return None
|
||||
return "https://mediaservice.happyshops.com/ANY/Article/{}/width=200/{}".format(
|
||||
int(aid).__format__("012d"), fn)
|
||||
|
||||
with open("/home/hermes/workspace/spiele_offensive_deals.html") as f:
|
||||
dhtml = f.read()
|
||||
|
||||
deals = []
|
||||
for m in re.finditer(
|
||||
r'<div class="card-name">([^<]+)</div>.*?'
|
||||
r'<div class="card-badge"[^>]*>([^<]+)</div>.*?'
|
||||
r'<span class="card-old">([^<]*)</span>.*?'
|
||||
r'<span class="card-new">([^<]+)</span>.*?'
|
||||
r'<a href="[^"]*aid=(\d+)',
|
||||
dhtml, re.DOTALL
|
||||
):
|
||||
rabatt = int(re.search(r'(\d+)', m.group(2)).group(1))
|
||||
deals.append({
|
||||
"name": m.group(1).strip(),
|
||||
"rabatt": rabatt,
|
||||
"old": m.group(3).strip().replace(" EUR", "").replace(" ", ""),
|
||||
"new": m.group(4).strip().replace(" EUR", "").replace(" ", ""),
|
||||
"aid": m.group(5).strip()
|
||||
})
|
||||
|
||||
# Filter: no puzzles, jigsaws, surprise bags, bags
|
||||
SKIP = ['puzzle', 'jigsaw', 'wundertute', 'ratselpuzzle', 'tragetasche', 'jubilaumswundertute']
|
||||
deals = [d for d in deals if not any(w in d["name"].lower() for w in SKIP)]
|
||||
seen = set()
|
||||
uniq = []
|
||||
for d in deals:
|
||||
if d["aid"] not in seen:
|
||||
seen.add(d["aid"])
|
||||
uniq.append(d)
|
||||
uniq.sort(key=lambda d: -d["rabatt"])
|
||||
top30 = uniq[:30]
|
||||
|
||||
# Generate cards HTML
|
||||
cards = []
|
||||
for d in top30:
|
||||
name, aid, old, new, rabatt = d["name"], d["aid"], d["old"], d["new"], d["rabatt"]
|
||||
|
||||
saved = ""
|
||||
try:
|
||||
s = float(old.replace(",",".")) - float(new.replace(",","."))
|
||||
saved = "({:.2f} EUR gespart)".format(s).replace(".", ",")
|
||||
except:
|
||||
pass
|
||||
|
||||
bc = "#b12704" if rabatt >= 80 else ("#e63946" if rabatt >= 60 else "#cc0c39")
|
||||
|
||||
src = img_url(aid)
|
||||
if src:
|
||||
img_tag = '<img src="{}" alt="" class="dimg" loading="lazy">'.format(src)
|
||||
else:
|
||||
img_tag = '<div class="dimg noimg">?</div>'
|
||||
|
||||
cards.append("""<div class="d">
|
||||
{}
|
||||
<div class="dinfo">
|
||||
<span class="dname">{}</span>
|
||||
<span class="dprice">{} EUR</span><span class="cold">UVP {} EUR</span>
|
||||
<span class="dbadge" style="background:{}">-{}%</span>
|
||||
<span class="dsave">{}</span>
|
||||
<a href="https://www.spiele-offensive.de/index.php?cmd=artikel_anzeigen&aid={}&pid=505" target="_blank" class="dlink">Zu Spiele-Offensive →</a>
|
||||
</div></div>""".format(img_tag, name, new, old, bc, rabatt, saved, aid))
|
||||
|
||||
html = """<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Top 30 Brettspiel-Angebote — Spiele-Offensive.de</title>
|
||||
<style>
|
||||
body{margin:0;padding:0;background:#f9f7f4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:#2c2416;line-height:1.6}
|
||||
article{max-width:720px;margin:0 auto;padding:2rem 1.5rem;background:#fff;box-shadow:0 2px 12px rgba(0,0,0,.06)}
|
||||
h1{font-size:1.5rem;color:#1a1a1a}.meta{color:#999;font-size:.85rem;margin-bottom:1.5rem}
|
||||
a{color:#007185;text-decoration:none}a:hover{text-decoration:underline}
|
||||
.d{display:flex;gap:1rem;padding:1rem 0;border-bottom:1px solid #eee;align-items:flex-start}
|
||||
.dimg{width:100px;height:100px;object-fit:contain;flex-shrink:0;border-radius:4px;background:#f5f5f5}
|
||||
.noimg{display:flex;align-items:center;justify-content:center;color:#ccc;font-size:2rem}
|
||||
.dinfo{flex:1;min-width:0}
|
||||
.dname{font-weight:600;color:#1a1a1a;font-size:.95rem;display:block;line-height:1.3;margin-bottom:3px}
|
||||
.dprice{color:#b12704;font-size:1.2rem;font-weight:bold}
|
||||
.cold{text-decoration:line-through;color:#999;margin-left:.5rem;font-size:.9rem}
|
||||
.dbadge{color:#fff;padding:2px 6px;border-radius:3px;margin-left:.5rem;font-size:.85rem;font-weight:600}
|
||||
.dsave{color:#b12704;margin-left:.3rem;font-size:.9rem}
|
||||
.dlink{display:inline-block;margin-top:4px;color:#e63946;font-size:.9rem;font-weight:500}
|
||||
.dlink:hover{text-decoration:underline}
|
||||
.footer{margin-top:2rem;padding-top:1rem;border-top:1px solid #eee;font-size:.85rem;color:#888}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h1>Top 30 Brettspiel-Angebote auf Spiele-Offensive.de</h1>
|
||||
<p class="meta">Stand: """ + NOW + """ · Sortiert nach Rabatt · Preise inkl. MwSt. · Affiliate-Links (PID 505) · Bilder: Spiele-Offensive</p>
|
||||
|
||||
""" + "\n".join(cards) + """
|
||||
|
||||
<div class="footer">
|
||||
<p>Alle Preise und Verf\u00fcgbarkeiten zum Zeitpunkt der Erhebung. Zwischenverkauf und Preis\u00e4nderungen vorbehalten.</p>
|
||||
<p>Dieser Beitrag enth\u00e4lt Affiliate-Links. Bei einem Kauf \u00fcber diese Links erhalten wir eine kleine Provision \u2014 f\u00fcr dich entstehen keine Mehrkosten.</p>
|
||||
</div>
|
||||
</article></body></html>"""
|
||||
|
||||
with open(OUT, "w") as f:
|
||||
f.write(html)
|
||||
|
||||
img_count = html.count('<img src=')
|
||||
noimg = html.count('noimg')
|
||||
print("Done: {} deals, {} images, {} no-image, {} bytes".format(len(top30), img_count, noimg, len(html)))
|
||||
print("FILE:{}".format(OUT))
|
||||
Reference in New Issue
Block a user