From bb7991bbd33a61c6d1b1af7ff7396bb4532b8b0d Mon Sep 17 00:00:00 2001 From: "Till (Cowork)" Date: Sun, 28 Jun 2026 07:39:39 +0000 Subject: [PATCH] =?UTF-8?q?Live-Crawler=20scharf:=20Frontend-Overlay=20(LI?= =?UTF-8?q?VE-gescrapte=20OBI/Bauhaus-Preise=20im=20Monitor)=20+=20404/410?= =?UTF-8?q?-Erkennung=20(Produktseite=20entfernt)=20=E2=80=93=20Edge,=20de?= =?UTF-8?q?n=20braucheklima=20nicht=20hat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- livecrawler/app.py | 11 +++++++++-- src/pages/karte.astro | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/livecrawler/app.py b/livecrawler/app.py index b76b912..21ca21d 100644 --- a/livecrawler/app.py +++ b/livecrawler/app.py @@ -55,7 +55,11 @@ def parse(htmltext): async def fetch_one(client, src): try: r= await client.get(src["url"], headers=UA, timeout=20, follow_redirects=True) - if r.status_code!=200: + if r.status_code in (404,410): + # Produktseite verschwunden -> als entfernt kennzeichnen (das kann braucheklima nicht) + return {"chain":src["chain"],"kind":src["kind"],"productId":src["productId"],"name":src["name"], + "url":src["url"],"price":None,"availability":"discontinued","status":r.status_code} + if r.status_code!=200: return {**src,"price":None,"availability":"unknown","status":r.status_code} price, avail = parse(r.text) return {"chain":src["chain"],"kind":src["kind"],"productId":src["productId"],"name":src["name"], @@ -83,13 +87,16 @@ async def crawl(): p=prev.get((o["chain"],o["productId"])) if p and p.get("availability")=="out_of_stock" and o.get("availability") in ("in_stock","online_available"): hist["events"].append({"chain":o["chain"],"productId":o["productId"],"name":o["name"],"at":now}) + if p and p.get("availability")!="discontinued" and o.get("availability")=="discontinued": + hist["events"].append({"chain":o["chain"],"productId":o["productId"],"name":o["name"],"at":now,"type":"discontinued"}) hist["events"]=hist["events"][-200:] # Preis-Aggregat je Produkt byp={} for o in results: if o.get("price"): byp.setdefault(o["productId"],[]).append(o["price"]) agg={k:{"min":min(v),"max":max(v),"n":len(v)} for k,v in byp.items()} - snap={"generatedAt":now,"source":"kuehlfinder-livecrawler","intervalMin":INTERVAL_MIN, + gone=[o for o in results if o.get("availability")=="discontinued"] + snap={"generatedAt":now,"source":"kuehlfinder-livecrawler","intervalMin":INTERVAL_MIN,"discontinued":len(gone), "offers":results,"priceByProduct":agg,"feed":hist["events"][-20:][::-1]} json.dump(snap, open(LIVE,"w"), ensure_ascii=False) json.dump(hist, open(HIST,"w"), ensure_ascii=False) diff --git a/src/pages/karte.astro b/src/pages/karte.astro index ab64b53..6734f10 100644 --- a/src/pages/karte.astro +++ b/src/pages/karte.astro @@ -123,6 +123,7 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü .osrc b{font-size:.82rem}.osrc .num{font-size:.92rem}.osrc small{font-size:.71rem;color:var(--slate)} .osrc.wucher{border-color:var(--danger);background:var(--danger-bg)}.osrc.wucher .num{color:var(--danger)} .trust{font-size:.68rem;color:var(--slate)} + .livebadge{display:inline-flex;align-items:center;gap:.4rem;font-size:.8rem;font-weight:600;color:var(--ok-dot);background:var(--ok-bg);border:1px solid var(--ok-dot);border-radius:999px;padding:.35rem .7rem;align-self:center} .tablewrap{margin-top:.8rem;padding:0;overflow:hidden} .mtab{width:100%;border-collapse:collapse;font-size:.9rem} .mtab thead th{text-align:left;font-size:.72rem;text-transform:uppercase;letter-spacing:.04em;color:var(--slate);font-weight:600;padding:.7rem .8rem;border-bottom:1px solid var(--line);background:var(--card);white-space:nowrap} @@ -142,6 +143,7 @@ const jsonld={'@context':'https://schema.org','@type':'WebApplication',name:'Kü