150 lines
5.3 KiB
Python
150 lines
5.3 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
Parse Fähigkeitsmatrix und extrahiere vollständige Kategorisierung.
|
|||
|
|
"""
|
|||
|
|
import sys
|
|||
|
|
import httpx
|
|||
|
|
from bs4 import BeautifulSoup
|
|||
|
|
|
|||
|
|
def parse_matrix():
|
|||
|
|
"""Parse die Fähigkeitsmatrix und gib CSV-Mapping aus."""
|
|||
|
|
|
|||
|
|
# Wiki-Login
|
|||
|
|
api_url = 'https://karatetrainer.net/api.php'
|
|||
|
|
username = 'Jinkendo'
|
|||
|
|
password = 'Jinkendo6970'
|
|||
|
|
|
|||
|
|
# Synchroner Client
|
|||
|
|
with httpx.Client(timeout=30) as client:
|
|||
|
|
# Schritt 1: Login-Token holen
|
|||
|
|
r1 = client.get(api_url, params={
|
|||
|
|
'action': 'query',
|
|||
|
|
'meta': 'tokens',
|
|||
|
|
'type': 'login',
|
|||
|
|
'format': 'json',
|
|||
|
|
})
|
|||
|
|
r1.raise_for_status()
|
|||
|
|
data1 = r1.json()
|
|||
|
|
token = data1.get('query', {}).get('tokens', {}).get('logintoken', '')
|
|||
|
|
|
|||
|
|
if not token:
|
|||
|
|
print("ERROR: Kein Login-Token erhalten", file=sys.stderr)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# Schritt 2: Login durchführen
|
|||
|
|
r2 = client.post(api_url, data={
|
|||
|
|
'action': 'login',
|
|||
|
|
'lgname': username,
|
|||
|
|
'lgpassword': password,
|
|||
|
|
'lgtoken': token,
|
|||
|
|
'format': 'json',
|
|||
|
|
})
|
|||
|
|
r2.raise_for_status()
|
|||
|
|
data2 = r2.json()
|
|||
|
|
|
|||
|
|
if data2.get('login', {}).get('result') != 'Success':
|
|||
|
|
print(f"ERROR: Login fehlgeschlagen: {data2}", file=sys.stderr)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# Hole Matrix-Seite als HTML
|
|||
|
|
page_url = 'https://karatetrainer.net/index.php?title=Fähigkeitsmatrix'
|
|||
|
|
r3 = client.get(page_url)
|
|||
|
|
r3.raise_for_status()
|
|||
|
|
html = r3.text
|
|||
|
|
|
|||
|
|
if not html:
|
|||
|
|
print("ERROR: Konnte Matrix-HTML nicht abrufen", file=sys.stderr)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# Parse HTML
|
|||
|
|
soup = BeautifulSoup(html, 'html.parser')
|
|||
|
|
table = soup.find('table', {'class': 'wikitable'})
|
|||
|
|
|
|||
|
|
if not table:
|
|||
|
|
print("ERROR: Tabelle nicht gefunden", file=sys.stderr)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
rows = table.find_all('tr')
|
|||
|
|
|
|||
|
|
# Extrahiere Struktur
|
|||
|
|
# Default: KARATE Fähigkeiten (bis Row 49 wo ALLGEMEINE beginnt)
|
|||
|
|
current_main_cat = 'karate'
|
|||
|
|
current_sub_cat = None
|
|||
|
|
skills_data = []
|
|||
|
|
|
|||
|
|
for idx, row in enumerate(rows):
|
|||
|
|
cells = row.find_all(['td', 'th'])
|
|||
|
|
|
|||
|
|
if len(cells) == 1:
|
|||
|
|
# Kategorie-Header (1 Zelle)
|
|||
|
|
text = cells[0].get_text(strip=True)
|
|||
|
|
|
|||
|
|
# Überspringe Inhaltsverzeichnis (Row 4)
|
|||
|
|
if 'Inhaltsverzeichnis' in text:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# Haupt-Kategorie Wechsel erkennen (nur bei "ALLGEMEINE sportliche Fähigkeiten")
|
|||
|
|
if 'ALLGEMEINE' in text and 'sportliche' in text:
|
|||
|
|
current_main_cat = 'allgemeine'
|
|||
|
|
current_sub_cat = None
|
|||
|
|
# print(f"DEBUG: Row {idx} - Wechsel zu allgemeine", file=sys.stderr)
|
|||
|
|
else:
|
|||
|
|
# Alle anderen 1-Zellen-Rows sind Unterkategorien
|
|||
|
|
# Bereinige Text (entferne Sonderzeichen, Leerzeichen)
|
|||
|
|
current_sub_cat = text.replace('', '').strip() # soft hyphen entfernen
|
|||
|
|
# print(f"DEBUG: Row {idx} - Unterkategorie '{current_sub_cat}' (main_cat={current_main_cat})", file=sys.stderr)
|
|||
|
|
|
|||
|
|
elif len(cells) == 6:
|
|||
|
|
# Skill-Zeile (6 Zellen: Name + 5 Level-Beschreibungen)
|
|||
|
|
skill_name = cells[0].get_text(strip=True).replace('', '') # soft hyphen entfernen
|
|||
|
|
|
|||
|
|
# Nur Skills mit Hauptkategorie UND Unterkategorie erfassen
|
|||
|
|
if skill_name and current_main_cat and current_sub_cat:
|
|||
|
|
# Fokusbereich bestimmen
|
|||
|
|
focus = 'karate' if current_main_cat == 'karate' else 'universal'
|
|||
|
|
|
|||
|
|
skills_data.append({
|
|||
|
|
'skill': skill_name,
|
|||
|
|
'sub_cat': current_sub_cat,
|
|||
|
|
'main_cat': current_main_cat,
|
|||
|
|
'focus': focus,
|
|||
|
|
'row': idx
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# Duplikat-Handling: Bevorzuge spezifischere Kategorien
|
|||
|
|
# Bei Duplikaten: Kondition/Koordination > Kumite (für allgemeine Fähigkeiten)
|
|||
|
|
# Kumite > Rest (für Kampf-spezifische wie "Timing", "Antizipation")
|
|||
|
|
|
|||
|
|
duplicates_to_remove = {
|
|||
|
|
('Anaerobe Ausdauer', 'Kumite'), # Behalte Kondition
|
|||
|
|
('Bewegungsschnelligkeit', 'Kumite'), # Behalte Kondition
|
|||
|
|
('Flexibilität', 'Kumite'), # Behalte Kondition
|
|||
|
|
('Reaktionsschnelligkeit', 'Kumite'), # Behalte Kondition
|
|||
|
|
('Schnelligkeitsausdauer', 'Kumite'), # Behalte Kondition
|
|||
|
|
('Antizipation', 'Koordination'), # Behalte Kumite (kampfspezifisch)
|
|||
|
|
('Timing', 'Koordination'), # Behalte Kumite (kampfspezifisch)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Filtere Duplikate
|
|||
|
|
filtered_skills = []
|
|||
|
|
for s in skills_data:
|
|||
|
|
if (s['skill'], s['sub_cat']) not in duplicates_to_remove:
|
|||
|
|
filtered_skills.append(s)
|
|||
|
|
|
|||
|
|
# Ausgabe als CSV (UTF-8)
|
|||
|
|
import sys
|
|||
|
|
# Force UTF-8 output
|
|||
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|||
|
|
|
|||
|
|
print('skill_name,sub_category,main_category,focus_areas')
|
|||
|
|
for s in filtered_skills:
|
|||
|
|
# Escape Kommas in Namen
|
|||
|
|
skill = s['skill'].replace(',', ';')
|
|||
|
|
sub_cat = s['sub_cat'].replace(',', ';')
|
|||
|
|
print(f"{skill},{sub_cat},{s['main_cat']},{s['focus']}")
|
|||
|
|
|
|||
|
|
print(f"\n# Total: {len(filtered_skills)} Skills (von {len(skills_data)} Zeilen, {len(skills_data) - len(filtered_skills)} Duplikate entfernt)", file=sys.stderr)
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
parse_matrix()
|