Brettspiel-Angebote auf Amazon
{''.join(cards) if cards else 'Keine reduzierten Brettspiele gefunden.
'}#!/usr/bin/env python3 """Amazon CreatorAPI 3.2 — Brettspiel-Deals auf amazon.de finden (verbessert). Änderungen: - item_count=50 (max), MAX_PAGES=10 (max) - 15 Keywords statt 5 — deckt mehr Spieltypen ab - Ohne min_saving_percent (Client-seitiger Filter, zuverlässiger) - Zusätzlich: keyword-lose Browse-Node-Suche für alle Deals """ import argparse, os, re, time from datetime import datetime from amazon_creatorsapi import AmazonCreatorsApi, Country from amazon_creatorsapi import models as m # Configuration CREDENTIAL_ID = "amzn1.application-oa2-client.91799968d0744e66950ea413659197d4" 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 = 10 ITEMS_PER_PAGE = 50 # Breite Keyword-Abdeckung — 15 verschiedene Suchbegriffe KEYWORDS = [ "Brettspiel", "Kartenspiel", "Familienspiel", "Strategiespiel", "Partyspiel", "Kennerspiel", "Kinderspiel", "Würfelspiel", "Legespiel", "kooperativ", "Roll & Write", "Deckbuilding", "Worker Placement", "Drafting", "Puzzle", ] RESOURCES = [ m.SearchItemsResource.ITEM_INFO_DOT_TITLE, m.SearchItemsResource.IMAGES_DOT_PRIMARY_DOT_LARGE, m.SearchItemsResource.OFFERS_V2_DOT_LISTINGS_DOT_PRICE, ] def search_amazon(api, keyword=None, 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 or 'BROWSE'}' p{page}: {e}", flush=True) return None 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) 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) # Client-seitiger Filter — zuverlässiger als min_saving_percent if savings_pct >= 10: deals.append({ "title": title, "image": image, "asin": asin, "price": price, "list_price": list_price or 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 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) # === QUALITÄTSKONTROLLE: Nicht-Spiele herausfiltern === # Blacklist: Produkte, die definitiv KEINE Brettspiele sind NON_GAME_PATTERNS = [ # Elektronik & Gadgets r'\bAir\s*Tag\b', r'\bAirtag\b', r'\bGPS[-\s]Tracker\b', r'\bBluetooth[-\s]Tracker\b', r'\bKopfhörer\b', r'\bSmartwatch\b', # Software, digital, Steuer (NUR als eigenständiges Produkt) r'\bWISO\s+Steuer\b', r'\bSteuer\s*\d{4}\b', r'\bSteuererklärung\b', r'\bSteuersoftware\b', r'\bDigitaler\s+Download\b', r'\bAntivirus\b', r'\bAntiviren\b', # Bücher (explizit, nicht im Spielekontext) r'\bTaschenbuch\b', r'\bGebundene\s+Ausgabe\b', r'\bKindle\b', r'\bHörbuch\b', r'\bSchulbuch\b', r'\bLernheft\b', r'\bÜbungsbuch\b', r'\bVokabelheft\b', r'\bVokabeltrainer\b', # Medien (keine Spiele) r'\bBlu-ray\b', # Haushalt & Sonstiges r'\bNetzteil\b', r'\bLadegerät\b', # Sonstige Nicht-Spiele r'\bPolydraw\b', r'\bDrafting\s+Film\b', r'\bDrafting\s+Pad\b', r'\bBasic\s+Drafting\b', # Jigsaw-Puzzles (keine Brettspiele) r'\bPuzzle\s+\d+\s*Teile\b', r'\b\d+\s*Teile\s+Puzzle\b', r'\bJigsaw\s+Puzzle\b', r'\bPuzzle\s+\d+[.,]\d+\s*cm\b', ] filtered = [] removed = [] for d in unique: title_lower = d["title"].lower() is_non_game = False for pattern in NON_GAME_PATTERNS: if re.search(pattern, title_lower, re.IGNORECASE): is_non_game = True break if is_non_game: removed.append(d) else: filtered.append(d) if removed: print(f" QC: {len(removed)} Nicht-Spiele entfernt:") for r in removed: print(f" ✗ {r['title'][:80]}") unique = filtered # === ENDE QUALITÄTSKONTROLLE === unique.sort(key=lambda x: x["savings_pct"], reverse=True) top = unique[:50] # Show top 50 instead of 30 now = datetime.now().strftime("%d.%m.%Y, %H:%M") cards = [] for d in top: cards.append(f"""
Keine reduzierten Brettspiele gefunden.
'}