Initial commit: Hermes Agent Skills collection
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Template: Chefkoch Nightly Scraper (async Playwright)
|
||||
|
||||
Scrapes recipe metadata from Chefkoch.de via headless browser.
|
||||
Intended to be customized per search query and run as background job.
|
||||
|
||||
Prerequisite: playwright install chromium
|
||||
"""
|
||||
import asyncio, json, re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
# ─── Config ─────────────────────────────────────────────────────────────────
|
||||
QUERY = "asiatisch einfach familie"
|
||||
MAX_RESULTS = 10
|
||||
OUTPUT_DIR = Path.home() / "Documents/Essensplanung"
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT = OUTPUT_DIR / f"scrape_{re.sub(r'[^a-z0-9]', '_', QUERY.lower())}_{datetime.now().strftime('%Y%m%d')}.json"
|
||||
|
||||
# ─── Playwright ─────────────────────────────────────────────────────────────
|
||||
|
||||
async def accept_cookies(page):
|
||||
try:
|
||||
await page.click("button[title='Zustimmen']", timeout=2000)
|
||||
except:
|
||||
pass
|
||||
|
||||
async def scrape_page(browser, query, max_results):
|
||||
results = []
|
||||
url = f"https://www.chefkoch.de/rs/s0/{query.replace(' ', '%20')}/Rezepte.html"
|
||||
|
||||
page = await browser.new_page()
|
||||
try:
|
||||
await page.goto(url, wait_until="networkidle", timeout=30000)
|
||||
await accept_cookies(page)
|
||||
|
||||
# Scrolle bis genug Karten geladen
|
||||
for _ in range(15):
|
||||
cards = await page.query_selector_all(".ds-recipe-card")
|
||||
if len(cards) >= max_results * 2:
|
||||
break
|
||||
await page.evaluate("window.scrollBy(0, 800)")
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
# Extrahiere Links
|
||||
links = await page.eval_on_selector_all(
|
||||
"a.ds-recipe-card__title-link",
|
||||
"els => [...new Set(els.map(e => e.href))]"
|
||||
)
|
||||
|
||||
print(f" {len(links)} Links gefunden")
|
||||
|
||||
# Lade Details
|
||||
for link in list(dict.fromkeys(links))[:max_results]:
|
||||
detail = await get_recipe_detail(browser, link)
|
||||
if detail:
|
||||
results.append(detail)
|
||||
print(f" + {detail['name'][:40]}")
|
||||
await asyncio.sleep(0.5)
|
||||
finally:
|
||||
await page.close()
|
||||
|
||||
return results
|
||||
|
||||
async def get_recipe_detail(browser, url):
|
||||
page = await browser.new_page()
|
||||
try:
|
||||
await page.goto(url, wait_until="networkidle", timeout=15000)
|
||||
await accept_cookies(page)
|
||||
|
||||
# Name
|
||||
name = await page.eval_on_selector("h1", "e => e?.innerText.trim() || 'Unbekannt'")
|
||||
|
||||
# Zutaten
|
||||
ings = []
|
||||
rows = await page.query_selector_all("table.ingredients tbody tr")
|
||||
for row in rows[:15]:
|
||||
try:
|
||||
amt = await row.eval_on_selector("td.amount", "e => e.innerText.trim()")
|
||||
item = await row.eval_on_selector("td.name", "e => e.innerText.trim()")
|
||||
if item: ings.append({"amount": amt, "item": item})
|
||||
except: pass
|
||||
|
||||
# Zeit
|
||||
time = await page.eval_on_selector(
|
||||
".ds-recipe-meta__icon-text, .bi-recipe-meta-duration",
|
||||
"e => e?.innerText.trim() || '?'"
|
||||
)
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"url": url,
|
||||
"ingredients": ings,
|
||||
"prep_time_text": time,
|
||||
}
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Fehler: {e}")
|
||||
return None
|
||||
finally:
|
||||
await page.close()
|
||||
|
||||
# ─── Main ───────────────────────────────────────────────────────────────────
|
||||
|
||||
async def main():
|
||||
print(f"🔍 Scrape: '{QUERY}' → {MAX_RESULTS}x")
|
||||
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
results = await scrape_page(browser, QUERY, MAX_RESULTS)
|
||||
await browser.close()
|
||||
|
||||
OUTPUT.write_text(json.dumps(results, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"💾 {len(results)} Rezepte → {OUTPUT}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user