144 lines
6.9 KiB
Python
144 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Kombi-Script: Brettspiel-Deals + Multi-Kategorie. Ausführung bei jeder Top-30-Anfrage."""
|
|
import os, time
|
|
from datetime import datetime
|
|
|
|
exec(open('/home/hermes/.hermes/skills/finance/amazon-brettspiel-deals/scripts/search_deals.py').read().split('RESOURCES')[0])
|
|
|
|
from amazon_creatorsapi import AmazonCreatorsApi, Country
|
|
from amazon_creatorsapi import models as m
|
|
|
|
PARTNER_TAG = "60pro05-21"
|
|
COUNTRY = Country.DE
|
|
|
|
R_COMMON = [
|
|
m.SearchItemsResource.ITEM_INFO_DOT_TITLE,
|
|
m.SearchItemsResource.IMAGES_DOT_PRIMARY_DOT_LARGE,
|
|
m.SearchItemsResource.OFFERS_V2_DOT_LISTINGS_DOT_PRICE,
|
|
m.SearchItemsResource.ITEM_INFO_DOT_BY_LINE_INFO,
|
|
]
|
|
|
|
MULTI_CATEGORIES = [
|
|
("Heißluftfritteusen", ["Heißluftfritteuse", "Airfryer", "Air Fryer"], 10, None),
|
|
("Staubsauger & Wischroboter", ["Staubsauger", "Saugroboter", "Wischsauger", "Akkusauger"], 10, None),
|
|
("Laptops & Notebooks", ["Laptop", "Notebook"], 5, None),
|
|
("Apple-Produkte", ["Apple iPhone", "Apple iPad", "Apple Watch", "Apple AirPods"], 5, None),
|
|
("Tablets", ["Tablet"], 5, None),
|
|
("Smartphones", ["Smartphone"], 5, None),
|
|
]
|
|
|
|
def search_deals(api, keywords, count, node=None, with_savings=True):
|
|
deals, seen = [], set()
|
|
for kw in keywords:
|
|
if len(deals) >= count * 3: break
|
|
for page in range(1, 5):
|
|
if len(deals) >= count * 3: break
|
|
try:
|
|
kwargs = dict(keywords=kw, item_count=50, item_page=page, resources=R_COMMON)
|
|
if node:
|
|
kwargs["browse_node_id"] = node
|
|
resp = api.search_items(**kwargs)
|
|
time.sleep(1.1)
|
|
if not resp or not resp.items: continue
|
|
for item in resp.items:
|
|
if len(deals) >= count * 3: break
|
|
try:
|
|
asin = str(item.asin)
|
|
if asin in seen: continue
|
|
seen.add(asin)
|
|
title = str(item.item_info.title.display_value)
|
|
brand = str(item.item_info.by_line_info.brand.display_value) if item.item_info.by_line_info and item.item_info.by_line_info.brand else ""
|
|
if not item.offers_v2 or not item.offers_v2.listings: continue
|
|
li = item.offers_v2.listings[0]
|
|
if not li.price or not li.price.money: continue
|
|
pr = float(li.price.money.amount)
|
|
sp, se, lp = 0, 0, pr
|
|
if li.price.savings and li.price.savings.money:
|
|
se = float(li.price.savings.money.amount)
|
|
sp = int(li.price.savings.percentage)
|
|
if li.price.saving_basis and li.price.saving_basis.money:
|
|
lp = float(li.price.saving_basis.money.amount)
|
|
if not with_savings or sp >= 10:
|
|
img = ""
|
|
if item.images and item.images.primary and item.images.primary.large:
|
|
img = str(item.images.primary.large.url)
|
|
deals.append({"t":title,"b":brand,"img":img,"pr":pr,"lp":lp,"sp":sp,"se":se,
|
|
"url":f"https://www.amazon.de/dp/{asin}?tag={PARTNER_TAG}"})
|
|
except: continue
|
|
except Exception as e:
|
|
print(f" E {kw}: {e}")
|
|
if with_savings:
|
|
deals.sort(key=lambda x: x["sp"], reverse=True)
|
|
return deals[:count]
|
|
|
|
def generate_combined_html(board_deal_html_path, multi_results, output_path):
|
|
# Read board game HTML
|
|
with open(board_deal_html_path) as f:
|
|
board_html = f.read()
|
|
|
|
# Build multi-category sections
|
|
now = datetime.now().strftime("%d.%m.%Y, %H:%M")
|
|
sec = ""
|
|
for cat_name, deals in multi_results.items():
|
|
if not deals: continue
|
|
label = f"{cat_name} ({len(deals)})"
|
|
sec += f'<h2 style="margin-top:2.5rem;color:#c75b3a;border-bottom:2px solid #e8e0d3;padding-bottom:0.3rem;">{label}</h2>\n'
|
|
for d in deals:
|
|
img = f'<img src="{d["img"]}" style="width:100px;height:100px;object-fit:contain;" onerror="this.style.display=\'none\'">' if d["img"] else ""
|
|
br = f'<br><span style="color:#888;font-size:0.85rem;">{d["b"]}</span>' if d["b"] else ""
|
|
pct = f'-{d["sp"]}%' if d["sp"] > 0 else ''
|
|
saved = f'({d["se"]:.2f} €)' if d["se"] > 0 else ''
|
|
has_deal = d["sp"] > 0
|
|
uvp_line = f'<span style="text-decoration:line-through;color:#999;margin-left:0.5rem;">UVP {d["lp"]:.2f} €</span>' if has_deal else ''
|
|
badge_line = f'<span style="background:#b12704;color:#fff;padding:2px 6px;border-radius:3px;margin-left:0.5rem;font-size:0.85rem;">{pct}</span><span style="color:#b12704;margin-left:0.3rem;">{saved}</span>' if has_deal else ''
|
|
sec += f"""<div style="display:flex;gap:1rem;padding:1rem 0;border-bottom:1px solid #eee;">
|
|
{img}<div style="flex:1;"><strong>{d['t'][:120]}</strong>{br}<br>
|
|
<span style="color:#b12704;font-size:1.2rem;font-weight:bold;">{d['pr']:.2f} €</span>
|
|
{uvp_line}
|
|
{badge_line}<br>
|
|
<a href="{d['url']}" target="_blank" style="color:#007185;">Zu Amazon →</a></div></div>\n"""
|
|
|
|
footer_marker = '<p class="meta" style="margin-top:2rem;">'
|
|
combined = board_html.replace(footer_marker, sec + footer_marker)
|
|
|
|
with open(output_path, 'w') as f:
|
|
f.write(combined)
|
|
return output_path
|
|
|
|
def main():
|
|
import argparse
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--board-html', type=str, required=True, help='Path to board game deals HTML')
|
|
parser.add_argument('--output', type=str, default=None)
|
|
args = parser.parse_args()
|
|
|
|
if args.output is None:
|
|
args.output = os.path.expanduser(f"~/workspace/kombi_deals_{datetime.now().strftime('%Y-%m-%d')}.html")
|
|
|
|
api = AmazonCreatorsApi(CREDENTIAL_ID, CREDENTIAL_SECRET, "3.2", PARTNER_TAG, country=COUNTRY, throttling=1.0)
|
|
print("Connected!\n")
|
|
|
|
results = {}
|
|
for cat_name, keywords, count, node in MULTI_CATEGORIES:
|
|
print(f"--- {cat_name} ---")
|
|
deals = search_deals(api, keywords, count, node, with_savings=True)
|
|
results[cat_name] = deals
|
|
print(f" => {len(deals)}/{count}")
|
|
for d in deals:
|
|
print(f" -{d['sp']}% | {d['pr']:.2f}€ | {d['t'][:80]}")
|
|
|
|
# Bücher: Top 5 Bestseller (keine Angebots-Filterung)
|
|
print("--- Bücher-Bestseller ---")
|
|
books = search_deals(api, ["Bestseller Roman", "Bestseller Buch", "Thriller Bestseller"], 5, "541686", with_savings=False)
|
|
results["Bücher-Bestseller (Top 5)"] = books
|
|
print(f" => {len(books)}/5")
|
|
for d in books:
|
|
print(f" {d['pr']:.2f}€ | {d['t'][:80]}")
|
|
|
|
output = generate_combined_html(args.board_html, results, args.output)
|
|
total = sum(len(v) for v in results.values())
|
|
print(f"\n{total} Non-Brettspiel-Deals hinzugefügt → {output}")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|