From c5adbb34dbe002b5165ac116c98f808047e3f7c9 Mon Sep 17 00:00:00 2001 From: "Till (Cowork)" Date: Sun, 28 Jun 2026 09:50:50 +0000 Subject: [PATCH] =?UTF-8?q?Crawler-Parser:=20og/product:price-Meta=20(toom?= =?UTF-8?q?=20799=20=E2=82=AC,=20Hornbach)=20+=20Hornbach.ch=20als=20erste?= =?UTF-8?q?=20Ausland-Live-Quelle=20(cc)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- livecrawler/app.py | 20 +++++++++++++++++++- livecrawler/sources.py | 3 +++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/livecrawler/app.py b/livecrawler/app.py index 9bbcf28..16b3ee8 100644 --- a/livecrawler/app.py +++ b/livecrawler/app.py @@ -36,6 +36,22 @@ def _num(s): g=m.groups() return float(g[0]) + (float(g[1])/100 if len(g)>1 and g[1] else 0) +def clean_price(v): + v=(v or "").strip() + if not v: return None + if "," in v: v=v.replace(".","").replace(",",".") + m=re.search(r"\d+(?:\.\d+)?", v) + return float(m.group()) if m else None + +def price_from_meta(h): + for pat in ("product:price:amount","og:price:amount"): + m=re.search(r']+(?:property|name)=["\']'+pat+r'["\'][^>]+content=["\']([^"\']+)', h, re.I) or \ + re.search(r']+content=["\']([^"\']+)["\'][^>]+(?:property|name)=["\']'+pat+r'["\']', h, re.I) + if m: + p=clean_price(m.group(1)) + if p: return p + return None + def parse(htmltext): # 1) JSON-LD Offer price=None; avail=None @@ -56,6 +72,8 @@ def parse(htmltext): low=htmltext.lower() in_store = ("vorrätig" in low or "verfügbar" in low) and not ("nicht vorrätig" in low or "nicht verfügbar" in low) online = ("in den warenkorb" in low) or ("lieferung" in low and "nicht möglich" not in low) or (avail and "instock" in avail.lower()) + if price is None: + price=price_from_meta(htmltext) if price is None: mp=_num(htmltext[:200000]) price=mp @@ -75,7 +93,7 @@ async def fetch_one(client, src): 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"], + return {"chain":src["chain"],"kind":src["kind"],"cc":src.get("cc","DE"),"productId":src["productId"],"name":src["name"], "url":src["url"],"price":price,"availability":avail} except Exception as e: log.warning("fetch fail %s: %s", src["url"], e) diff --git a/livecrawler/sources.py b/livecrawler/sources.py index 952d0e6..5739f3e 100644 --- a/livecrawler/sources.py +++ b/livecrawler/sources.py @@ -15,6 +15,9 @@ SOURCES = [ "url":"https://toom.de/p/mobiles-klimageraet-portasplit-12000-btuh/9350668"}, {"chain":"toom","kind":"baumarkt","productId":"midea-portasplit-cool-8000","name":"Midea PortaSplit Cool 8.000 BTU", "url":"https://toom.de/p/split-klimaanlage-portasplit-cool-8000btuh/10515238"}, + # Ausland (live, Hornbach international – gleiche Plattform wie .de): + {"chain":"Hornbach","cc":"CH","kind":"baumarkt","productId":"midea-portasplit-12000","name":"Midea PortaSplit 12.000 BTU (CH)", + "url":"https://www.hornbach.ch/de/p/mobile-split-klimaanlage-midea-portasplit-ch-12000-btu-42m-weiss/12613899/"}, ] # Elektroketten / Online -> offizielle APIs nötig (mit Keys von Till): # Amazon Product Advertising API 5.0 (Access/Secret/Partner-Tag)