#!/usr/bin/env python3 """ Eurojackpot API Data Updater Lädt Eurojackpot-Ziehungen von verschiedenen APIs: 1. lottoAPI (https://lottoapi.herokuapp.com) 2. Lottoland API 3. Sazka.cz API (Fallback) Features: - Automatisches Laden von APIs - Fallback auf andere APIs wenn primäre fehlschlägt - Duplikate-Vermeidung - Automatisches Backup """ import requests import pandas as pd from datetime import datetime import shutil import os from typing import List, Dict, Optional import time class EurojackpotAPIUpdater: """Updater für Eurojackpot über APIs.""" def __init__(self, data_file: str): self.data_file = data_file self.backup_file = None self.df_existing = None # API-Endpoints self.apis = { 'lottoapi': { 'url': 'https://lottoapi.herokuapp.com/eurojackpot-results/100', 'name': 'lottoAPI', 'parser': self._parse_lottoapi }, 'lottoland': { 'url': 'https://media.lottoland.com/api/drawings/euroJackpot', 'name': 'Lottoland API', 'parser': self._parse_lottoland }, 'sazka': { 'url': 'https://www.sazka.cz/api/draw-info/past-draws/eurojackpot', 'name': 'Sazka.cz API', 'parser': self._parse_sazka } } print("🔄 EUROJACKPOT API UPDATER") print("=" * 70) print(f"📁 Datei: {os.path.basename(data_file)}") print("=" * 70) def load_existing_data(self) -> bool: """Lädt existierende Daten.""" print("\n📂 Lade existierende Daten...") try: if not os.path.exists(self.data_file): print(f" ⚠️ Datei existiert nicht - erstelle neue") self.df_existing = pd.DataFrame( columns=['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2'] ) return True self.df_existing = pd.read_csv(self.data_file, sep=';') # Konvertiere Datum if 'datum' in self.df_existing.columns: self.df_existing['datum'] = pd.to_datetime( self.df_existing['datum'], format='%Y-%m-%d', errors='coerce' ) print(f" ✅ {len(self.df_existing)} Ziehungen geladen") if len(self.df_existing) > 0: latest = self.df_existing['datum'].max() oldest = self.df_existing['datum'].min() print(f" 📅 Zeitraum: {oldest.strftime('%Y-%m-%d')} bis {latest.strftime('%Y-%m-%d')}") return True except Exception as e: print(f" ❌ Fehler: {e}") return False def create_backup(self) -> bool: """Erstellt Backup.""" if not os.path.exists(self.data_file): return True try: print("\n💾 Erstelle Backup...") timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_dir = os.path.join(os.path.dirname(self.data_file), "backups") os.makedirs(backup_dir, exist_ok=True) filename = os.path.basename(self.data_file) self.backup_file = os.path.join(backup_dir, f"{filename}.backup_{timestamp}") shutil.copy2(self.data_file, self.backup_file) print(f" ✅ Backup: {os.path.basename(self.backup_file)}") return True except Exception as e: print(f" ⚠️ Backup-Fehler: {e}") return False def fetch_from_api(self, api_name: str) -> List[Dict]: """ Holt Daten von spezifischer API. Args: api_name: Name der API ('lottoapi', 'lottoland', 'sazka') Returns: Liste von Ziehungen """ if api_name not in self.apis: print(f" ❌ Unbekannte API: {api_name}") return [] api = self.apis[api_name] print(f"\n🌐 Versuche {api['name']}...") print(f" URL: {api['url']}") try: headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' } response = requests.get(api['url'], headers=headers, timeout=15) response.raise_for_status() print(f" ✅ Antwort erhalten ({len(response.content)} bytes)") # Parse mit spezifischem Parser draws = api['parser'](response.json()) if draws: print(f" ✅ {len(draws)} Ziehungen extrahiert") else: print(f" ⚠️ Keine Ziehungen extrahiert") return draws except requests.RequestException as e: print(f" ❌ Netzwerkfehler: {e}") return [] except Exception as e: print(f" ❌ Fehler: {e}") return [] def _parse_lottoapi(self, data: List[Dict]) -> List[Dict]: """ Parst lottoAPI Response. Expected format: [ { "date": "2025-01-17", "numbers": [3, 9, 12, 24, 39], "euroNumbers": [5, 10] }, ... ] """ draws = [] try: for item in data: # Datum date_str = item.get('date') date_obj = datetime.strptime(date_str, '%Y-%m-%d') # Hauptzahlen numbers = sorted(item.get('numbers', [])) if len(numbers) != 5: continue # Eurozahlen euro_numbers = sorted(item.get('euroNumbers', [])) if len(euro_numbers) != 2: continue draw = { 'datum': date_obj, 'Z1': numbers[0], 'Z2': numbers[1], 'Z3': numbers[2], 'Z4': numbers[3], 'Z5': numbers[4], 'SZ1': euro_numbers[0], 'SZ2': euro_numbers[1] } if self._validate_draw(draw): draws.append(draw) except Exception as e: print(f" ⚠️ Parse-Fehler: {e}") return draws def _parse_lottoland(self, data: Dict) -> List[Dict]: """ Parst Lottoland API Response. Expected format: {'last': {...}, 'next': {...}} """ draws = [] try: # Lottoland gibt nur die letzte Ziehung zurück if 'last' in data: item = data['last'] # Datum extrahieren aus verschachteltem 'date' Objekt date_info = item.get('date', {}) day = date_info.get('day') month = date_info.get('month') year = date_info.get('year') if day and month and year: date_obj = datetime(year, month, day) else: return draws # Hauptzahlen numbers = sorted(item.get('numbers', [])) if len(numbers) != 5: return draws # Eurozahlen euro_numbers = sorted(item.get('euroNumbers', [])) if len(euro_numbers) != 2: return draws draw = { 'datum': date_obj, 'Z1': numbers[0], 'Z2': numbers[1], 'Z3': numbers[2], 'Z4': numbers[3], 'Z5': numbers[4], 'SZ1': euro_numbers[0], 'SZ2': euro_numbers[1] } if self._validate_draw(draw): draws.append(draw) print(f" ✅ Ziehung vom {date_obj.strftime('%Y-%m-%d')} extrahiert") except Exception as e: print(f" ⚠️ Parse-Fehler: {e}") return draws def _parse_sazka(self, data: Dict) -> List[Dict]: """ Parst Sazka.cz API Response. Sazka hat zwei Steps: 1. Liste von Draw IDs holen 2. Für jede Draw ID Details holen """ draws = [] try: # Erste Response enthält Liste von Ziehungen if isinstance(data, list): past_draws = data elif isinstance(data, dict) and 'draws' in data: past_draws = data['draws'] else: return draws print(f" 📊 {len(past_draws)} Ziehungen gefunden, hole Details...") # Begrenzen auf letzte 10 Ziehungen (um Requests zu sparen) for i, draw_item in enumerate(past_draws[:10]): draw_id = draw_item.get('id') # Nicht 'drawId' sondern 'id'! if not draw_id: continue # Hole Details für diese Ziehung detail_url = f"https://www.sazka.cz/api/draw-info/draws/universal/eurojackpot/{draw_id}" try: response = requests.get(detail_url, timeout=10) detail_data = response.json() # Parse Detail date_str = detail_data.get('date') # Nicht 'drawDate' sondern 'date' date_obj = datetime.strptime(date_str, '%Y-%m-%d') # Zahlen sind direkt in 'numbers' und 'euroNumbers' numbers = sorted(detail_data.get('numbers', [])) euro_numbers = sorted(detail_data.get('euroNumbers', [])) if len(numbers) == 5 and len(euro_numbers) == 2: draw = { 'datum': date_obj, 'Z1': numbers[0], 'Z2': numbers[1], 'Z3': numbers[2], 'Z4': numbers[3], 'Z5': numbers[4], 'SZ1': euro_numbers[0], 'SZ2': euro_numbers[1] } if self._validate_draw(draw): draws.append(draw) if (i + 1) % 5 == 0: print(f" ⏳ {i + 1}/10 Details geladen...") time.sleep(0.3) # Rate limiting except Exception as e: print(f" ⚠️ Fehler bei Draw ID {draw_id}: {e}") continue print(f" ✅ {len(draws)} gültige Ziehungen extrahiert") except Exception as e: print(f" ⚠️ Parse-Fehler: {e}") return draws def _validate_draw(self, draw: Dict) -> bool: """Validiert eine Ziehung.""" try: required_fields = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2'] if not all(field in draw for field in required_fields): return False main_numbers = [draw[f'Z{i}'] for i in range(1, 6)] if not all(isinstance(n, int) and 1 <= n <= 50 for n in main_numbers): return False if len(set(main_numbers)) != 5: return False euro_numbers = [draw['SZ1'], draw['SZ2']] if not all(isinstance(n, int) and 1 <= n <= 12 for n in euro_numbers): return False if len(set(euro_numbers)) != 2: return False if not isinstance(draw['datum'], datetime): return False return True except Exception: return False def fetch_from_all_apis(self) -> List[Dict]: """ Versucht alle APIs nacheinander. Returns: Liste von Ziehungen """ print("\n🔍 SUCHE NACH DATEN VON APIs") print("=" * 70) all_draws = [] # Versuche alle APIs for api_name in ['lottoapi', 'lottoland', 'sazka']: draws = self.fetch_from_api(api_name) if draws: all_draws.extend(draws) print(f" ✅ {len(draws)} Ziehungen von {self.apis[api_name]['name']}") break # Erste erfolgreiche API nutzen else: print(f" ⏭️ Weiter zur nächsten API...") time.sleep(1) # Pause zwischen APIs if not all_draws: print("\n ❌ Keine Daten von APIs erhalten") return all_draws def merge_with_existing(self, new_draws: List[Dict]) -> pd.DataFrame: """Merged neue Ziehungen mit existierenden Daten.""" print(f"\n🔀 Merge mit existierenden Daten...") if not new_draws: print(" ⚠️ Keine neuen Ziehungen zum Mergen") return self.df_existing df_new = pd.DataFrame(new_draws) if self.df_existing is None or len(self.df_existing) == 0: df_combined = df_new print(f" ✅ Neue Datei erstellt mit {len(df_combined)} Ziehungen") else: df_combined = pd.concat([self.df_existing, df_new], ignore_index=True) before_dedup = len(df_combined) df_combined = df_combined.drop_duplicates(subset=['datum'], keep='first') after_dedup = len(df_combined) duplicates = before_dedup - after_dedup if duplicates > 0: print(f" 🗑️ {duplicates} Duplikat(e) entfernt") df_combined = df_combined.sort_values('datum', ascending=True) df_combined = df_combined.reset_index(drop=True) new_entries = len(df_combined) - (len(self.df_existing) if self.df_existing is not None else 0) print(f" ✅ Merge abgeschlossen:") print(f" Vorher: {len(self.df_existing) if self.df_existing is not None else 0} Ziehungen") print(f" Neue: {new_entries} Ziehungen") print(f" Gesamt: {len(df_combined)} Ziehungen") return df_combined def save_data(self, df: pd.DataFrame) -> bool: """Speichert Daten.""" try: print(f"\n💾 Speichere Daten...") df_export = df.copy() df_export['datum'] = df_export['datum'].dt.strftime('%Y-%m-%d') column_order = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2'] df_export = df_export[column_order] df_export.to_csv(self.data_file, sep=';', index=False) print(f" ✅ Gespeichert: {self.data_file}") print(f" 📊 {len(df)} Ziehungen total") if len(df) > 0: latest = df['datum'].max() oldest = df['datum'].min() print(f" 📅 Zeitraum: {oldest.strftime('%Y-%m-%d')} bis {latest.strftime('%Y-%m-%d')}") return True except Exception as e: print(f" ❌ Speicherfehler: {e}") if self.backup_file and os.path.exists(self.backup_file): print(" 🔄 Stelle Backup wieder her...") shutil.copy2(self.backup_file, self.data_file) print(" ✅ Backup wiederhergestellt") return False def update(self, api_name: str = 'all', create_backup: bool = True) -> bool: """ Führt komplettes Update durch. Args: api_name: 'all', 'lottoapi', 'lottoland', 'sazka' create_backup: Backup erstellen Returns: True bei Erfolg """ print("\n🚀 STARTE UPDATE") print("=" * 70) if not self.load_existing_data(): return False if create_backup: self.create_backup() if api_name == 'all': new_draws = self.fetch_from_all_apis() else: new_draws = self.fetch_from_api(api_name) if not new_draws: print("\n❌ Keine Daten von APIs geladen") return False df_updated = self.merge_with_existing(new_draws) success = self.save_data(df_updated) print("\n" + "=" * 70) if success: print("✅ UPDATE ERFOLGREICH ABGESCHLOSSEN") else: print("❌ UPDATE FEHLGESCHLAGEN") print("=" * 70) return success def main(): """Hauptfunktion.""" import sys print() print("=" * 70) print(" EUROJACKPOT API UPDATER") print(" Unterstützt: lottoAPI, Lottoland, Sazka.cz") print("=" * 70) print() default_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv" if len(sys.argv) > 1: data_file = sys.argv[1] api_name = sys.argv[2] if len(sys.argv) > 2 else 'all' else: print(f"Standard-Datei: {default_file}") use_default = input("\nStandard verwenden? (j/n): ").strip().lower() if use_default in ['j', 'ja', 'y', 'yes', '']: data_file = default_file else: data_file = input("Pfad zur Datei: ").strip() print("\n🌐 API WÄHLEN") print("=" * 60) print("1. Alle APIs versuchen (empfohlen)") print("2. lottoAPI") print("3. Lottoland API") print("4. Sazka.cz API") print() api_choice = input("Wahl (1-4): ").strip() api_map = {'1': 'all', '2': 'lottoapi', '3': 'lottoland', '4': 'sazka'} api_name = api_map.get(api_choice, 'all') print() updater = EurojackpotAPIUpdater(data_file) success = updater.update(api_name=api_name) sys.exit(0 if success else 1) if __name__ == "__main__": main()