Files
Eurojackpot-Tipp-Generator/scripts/utils/update_from_api.py
T
cbazzaandClaude Sonnet 4.5 70e0638dee Initial commit: Eurojackpot analysis and prediction system
This repository contains a comprehensive Eurojackpot lottery analysis and prediction system including:
- Historical data analysis and processing
- ML-based prediction models
- Automated weekly tip generation
- Position and range analysis tools
- Notification system for results

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-16 15:53:01 +01:00

557 lines
17 KiB
Python

#!/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()