import random import re from html import unescape from html.parser import HTMLParser from urllib.parse import urljoin from urllib.request import Request, urlopen CHEFKOCH_TODAY_URL = "https://www.chefkoch.de/rezepte/was-koche-ich-heute/" FALLBACK_RECIPES = [ { "title": "Cremige Pasta mit Tomaten", "url": "https://www.chefkoch.de/", "image_url": "", "meta": "ca. 25 Minuten", }, { "title": "Kartoffel-Brokkoli-Auflauf", "url": "https://www.chefkoch.de/", "image_url": "", "meta": "vegetarisch", }, { "title": "Schnelle Reispfanne", "url": "https://www.chefkoch.de/", "image_url": "", "meta": "einfach", }, { "title": "Wraps mit Hähnchen", "url": "https://www.chefkoch.de/", "image_url": "", "meta": "schnell", }, { "title": "Gemüse-Curry", "url": "https://www.chefkoch.de/", "image_url": "", "meta": "würzig", }, { "title": "Ofenkartoffeln mit Dip", "url": "https://www.chefkoch.de/", "image_url": "", "meta": "günstig", }, { "title": "Gnocchi-Pfanne", "url": "https://www.chefkoch.de/", "image_url": "", "meta": "15 Minuten", }, { "title": "Pizza-Toast", "url": "https://www.chefkoch.de/", "image_url": "", "meta": "MVP-Klassiker", }, ] class ChefkochTodayParser(HTMLParser): def __init__(self): super().__init__() self.recipes = [] self._current_link = None self._current_text = [] self._current_image_url = "" def handle_starttag(self, tag, attrs): attrs = dict(attrs) if tag == "a": href = attrs.get("href", "") if re.search(r"/rezepte/\d+/.+\.html", href): self._current_link = urljoin(CHEFKOCH_TODAY_URL, href) self._current_text = [] self._current_image_url = "" elif tag == "img" and self._current_link: self._current_image_url = attrs.get("src") or attrs.get("data-src") or "" def handle_data(self, data): if self._current_link: self._current_text.append(data) def handle_endtag(self, tag): if tag != "a" or not self._current_link: return title, meta = parse_recipe_text(" ".join(self._current_text)) if title and not any(recipe["url"] == self._current_link for recipe in self.recipes): self.recipes.append( { "title": title, "url": self._current_link, "image_url": self._current_image_url, "meta": meta, } ) self._current_link = None self._current_text = [] self._current_image_url = "" def parse_recipe_text(text): text = unescape(re.sub(r"\s+", " ", text)).strip() text = text.replace("rating_star", "").strip() rating_match = re.match(r"(?P\d+[,.]\d)\s*/?\s*5?\s*\((?P\d+)\)\s*(?P.+)", text) if rating_match: rating = rating_match.group("rating") count = rating_match.group("count") title = clean_recipe_title(rating_match.group("title")) return title, f"{rating}/5 ({count})" return clean_recipe_title(text), "" def clean_recipe_title(title): title = re.sub(r"\s+von\s+essen\s*&\s*trinken.*$", "", title, flags=re.IGNORECASE) title = re.sub(r"[^\w\sÄÖÜäöüß.,&'’:/()\-]", "", title) return re.sub(r"\s+", " ", title).strip() def fetch_chefkoch_today_recipes(): cache_buster = random.randint(100000, 999999) request = Request( f"{CHEFKOCH_TODAY_URL}?rf={cache_buster}", headers={ "User-Agent": "Mozilla/5.0 (compatible; RezeptFinder/1.0)", "Cache-Control": "no-cache", }, ) with urlopen(request, timeout=8) as response: html = response.read().decode("utf-8", errors="replace") parser = ChefkochTodayParser() parser.feed(html) return parser.recipes[:8] def get_recipes(): try: recipes = fetch_chefkoch_today_recipes() except Exception: return FALLBACK_RECIPES return recipes or FALLBACK_RECIPES