#!/usr/bin/env python3 """Amazon CreatorAPI 3.2 — Kinderspiele-Deals auf amazon.de finden. NUR Kinderspiele behalten, Non-Games entfernen.""" import argparse, os, re, time from datetime import datetime from amazon_creatorsapi import AmazonCreatorsApi, Country from amazon_creatorsapi import models as m CREDENTIAL_ID = "amzn1.application-oa2-client.91799968d0744e66950ea413659197d4" CREDENTIAL_SECRET = "amzn1.oa2-cs.v1.4c41154ecfd1835e00879ad5c8060ef5cd3b209b06077c0af1d868b343cab955" PARTNER_TAG = "60pro05-21" BROWSE_NODE = "1290250031" # Gesellschaftsspiele COUNTRY = Country.DE MAX_PAGES = 8 ITEMS_PER_PAGE = 50 KINDERSPIEL_KEYWORDS = [ "Kinderspiel Brettspiel", "Kinderspiel ab 3 Jahre", "Kinderspiel ab 4 Jahre", "Kinderspiel ab 5 Jahre", "Kinderspiel ab 6 Jahre", "Kinderspiel ab 7 Jahre", "Mitbringspiel", "Vorschulspiel", "Lernspiel Kinder", "Gedächtnisspiel Kinder", "Würfelspiel Kinder", "Kinderkartenspiel", "Bluey Spiel", "Paw Patrol Spiel", "Peppa Pig Spiel", "Sendung mit der Maus Spiel", "Dobble", "Uno Junior", "Lotti Karotti", "Obstgarten Spiel", ] RESOURCES = [ m.SearchItemsResource.ITEM_INFO_DOT_TITLE, m.SearchItemsResource.IMAGES_DOT_PRIMARY_DOT_LARGE, m.SearchItemsResource.OFFERS_V2_DOT_LISTINGS_DOT_PRICE, ] # Patterns für NON-Games (entfernen) NON_GAME_PATTERNS = [ r'\btragetasche\b', r'\bau(fbewahr|fbewahrung)\b', r'\bpuzzle\b', r'\bjigsaw\b', r'\bairtag\b', r'\bwiso\s+steuer\b', r'\bblu-?ray\b', r'\bdvd\b', r'\binsert\b', r'\borgani[sz]er\b', r'\bwellness\b', r'\bclean\s+slate\b', r'\btaschenbuch\b', r'\bcd\b', r'\bbuch\b', r'\bhandgepäck\b', r'\beurowings\b', r'\bryanair\b', r'\bsoaker\b', r'\bnerf\b', r'\bkrang\b', r'\bdr\.?\s*rahm\b', r'\d{2,4}\s*teilig', # Puzzles mit Stückzahl r'\d{3,4}\s*pc\b', # Jigsaw piece count ] NON_GAME_SIMPLE = [ 'tragetasche', 'aufbewahrung', 'airtag', 'wiso', 'blu-ray', 'bluray', 'wellness', 'clean slate', 'dr. rahm', 'krang', 'handgepäck', 'eurowings', 'ryanair', 'soaker', 'nerf', 'taschenbuch', 'insert', 'organizer', 'upgrade kit', ] def search_amazon(api, keyword, page=1): try: kwargs = dict( browse_node_id=BROWSE_NODE, item_count=ITEMS_PER_PAGE, item_page=page, resources=RESOURCES, ) if keyword: kwargs["keywords"] = keyword resp = api.search_items(**kwargs) time.sleep(1.1) return resp except Exception as e: print(f" Error '{keyword}' p{page}: {e}", flush=True) return None def is_nongame(title): """Check if title is a non-game product.""" tl = title.lower() # Simple check for bad in NON_GAME_SIMPLE: if bad in tl: return True # Stückzahl-Puzzle check: "1000 Teile", "500-teilig" etc. if re.search(r'\b(500|1000|1500|2000)\s*[tT]eil', tl): return True return False def is_kinderspiel(title): """Check if title is a genuine children's game.""" tl = title.lower() # Age markers if re.search(r'ab\s+[3-8]\s+jahr', tl): return True # Children's game markers kind_markers = [ 'kinderspiel', 'mitbringspiel', 'vorschul', 'lernspiel', 'gedächtnisspiel', 'kinderkartenspiel', 'würfelspiel', 'junior', 'kids', 'kinder', 'bluey', 'paw patrol', 'peppa pig', 'sendung mit der maus', 'monsterjäger', 'pupsparade', 'sagaland', 'verrückte labyrinth', 'paletti spaghetti', 'tempo kleine schnecke', 'dobble', 'uno junior', 'lotti karotti', 'obstgarten', 'halli galli', 'zicke zacke', 'leo muss zum friseur', 'make n break', 'make \'n\' break', 'memo', 'memory', ] for marker in kind_markers: if marker in tl: return True return False def extract_deals(response): deals = [] if not response or not response.items: return deals for item in response.items: try: title = str(item.item_info.title.display_value) # QC: Non-Games entfernen if is_nongame(title): continue # QC: Nur Kinderspiele behalten if not is_kinderspiel(title): continue image = "" if item.images and item.images.primary and item.images.primary.large: image = str(item.images.primary.large.url) asin = str(item.asin) if not item.offers_v2 or not item.offers_v2.listings: continue listing = item.offers_v2.listings[0] if not listing.price or not listing.price.money: continue price = float(listing.price.money.amount) list_price = None savings_pct = 0 savings_eur = 0 if listing.price.savings and listing.price.savings.money: savings_eur = float(listing.price.savings.money.amount) savings_pct = int(listing.price.savings.percentage) if listing.price.saving_basis and listing.price.saving_basis.money: list_price = float(listing.price.saving_basis.money.amount) # Niedrigere Schwelle für Kinderspiele (auch kleine Rabatte) if savings_pct > 0: if list_price is None: list_price = price deals.append({ "title": title, "image": image, "asin": asin, "price": price, "list_price": list_price, "savings_pct": savings_pct, "savings_eur": savings_eur, "url": f"https://www.amazon.de/dp/{asin}?tag={PARTNER_TAG}", }) except Exception: continue return deals def guess_age_group(title): """Guess age group from title.""" tl = title.lower() if re.search(r'ab\s+3\s+jahr', tl): return "3+" if re.search(r'ab\s+4\s+jahr', tl): return "4+" if re.search(r'ab\s+5\s+jahr', tl): return "5+" if re.search(r'ab\s+6\s+jahr', tl): return "6+" if re.search(r'ab\s+7\s+jahr', tl): return "7+" if 'mitbringspiel' in tl: return "Mitbringspiele" if re.search(r'ab\s+8\s+jahr', tl): return "8+" # Fallback: check for junior/kids markers if 'junior' in tl or 'kids' in tl: return "Mitbringspiele" return "Allgemein" AGE_ORDER = ["3+", "4+", "5+", "6+", "7+", "8+", "Mitbringspiele", "Allgemein"] def generate_html(deals, output_path): seen = set() unique = [] for d in deals: if d["asin"] not in seen: seen.add(d["asin"]) unique.append(d) unique.sort(key=lambda x: x["savings_pct"], reverse=True) top = unique[:20] # Group by age groups = {} for d in top: age = guess_age_group(d["title"]) if age not in groups: groups[age] = [] groups[age].append(d) now = datetime.now().strftime("%d.%m.%Y, %H:%M") sections = [] for age in AGE_ORDER: if age not in groups: continue items = groups[age] age_labels = { "3+": "Für die Kleinsten (ab 3 Jahre)", "4+": "Ab 4 Jahre", "5+": "Ab 5 Jahre", "6+": "Ab 6 Jahre", "7+": "Ab 7 Jahre", "8+": "Ab 8 Jahre", "Mitbringspiele": "Mitbringspiele", "Allgemein": "Weitere Kinderspiele", } label = age_labels.get(age, age) cards = [] for d in items: cards.append(f"""
{d['title'][:120]}
{d['price']:.2f} € UVP {d['list_price']:.2f} € -{d['savings_pct']}% ({d['savings_eur']:.2f} € gespart)
Zu Amazon →
""") sections.append(f"

{label}

\n{''.join(cards)}") count_info = f"{len(top)} Spiele in {len(groups)} Altersgruppen" html = f""" Kinderspiele-Angebote auf Amazon

Kinderspiele-Angebote auf Amazon

Stand: {now} · {count_info} · Preise inkl. MwSt.

{''.join(sections) if sections else '

Keine reduzierten Kinderspiele gefunden.

'}

Preise und Verfügbarkeit können sich schnell ändern. Über die Links ({PARTNER_TAG}) kauft ihr zum gleichen Preis — Amazon zahlt eine kleine Provision.

Brettspiel-News.de · (C) liegt beim jeweiligen Rechteinhaber

""" with open(output_path, 'w') as f: f.write(html) return len(top), groups def main(): parser = argparse.ArgumentParser() parser.add_argument('--limit', type=int, default=20) parser.add_argument('--output', type=str, default=None) args = parser.parse_args() if args.output is None: args.output = os.path.expanduser(f"~/workspace/kinderspiele_deals_{datetime.now().strftime('%Y-%m-%d')}.html") api = AmazonCreatorsApi( CREDENTIAL_ID, CREDENTIAL_SECRET, "3.2", PARTNER_TAG, country=COUNTRY, throttling=1.0 ) print(f"Connected to CreatorAPI (DE) — Kinderspiele-Suche") print(f"Keywords: {len(KINDERSPIEL_KEYWORDS)} · {MAX_PAGES} Seiten à {ITEMS_PER_PAGE}\n") all_deals = [] for kw in KINDERSPIEL_KEYWORDS: for page in range(1, MAX_PAGES + 1): resp = search_amazon(api, kw, page) if resp: deals = extract_deals(resp) all_deals.extend(deals) total = resp.total_result_count if hasattr(resp, 'total_result_count') else '?' if deals: names = [d['title'][:50] for d in deals] print(f" {kw} p{page}: {len(deals)} Kinderspiele (total: {total})") for n in names: print(f" → {n}") else: print(f" {kw} p{page}: 0 Kinderspiele (total: {total})") # Early exit if we have enough if len(all_deals) >= args.limit * 2: break count, groups = generate_html(all_deals, args.output) print(f"\n{count} reduzierte Kinderspiele → {args.output}") print(f"Link: http://185.162.249.159:8890/{os.path.basename(args.output)}?key=br3ttsp1el-n3ws-2026") # Print summary for Joomla article building print(f"\n=== SUMMARY ===") print(f"Total: {count} Spiele") for age in AGE_ORDER: if age in groups: print(f" {age}: {len(groups[age])} Spiele") for d in groups[age]: print(f" {d['savings_pct']}% | {d['price']:.2f}€ | {d['title'][:80]} | ASIN:{d['asin']}") if __name__ == "__main__": main()