- Créer une arborescence propre (src/, scripts/, config/, data/, docs/, tests/) - Déplacer les modules Python dans src/ - Déplacer les scripts autonomes dans scripts/ - Nettoyer les fichiers temporaires et __pycache__ - Mettre à jour le README.md avec documentation complète - Mettre à jour les imports dans les scripts pour la nouvelle structure - Configurer le .gitignore pour ignorer les données et logs - Organiser les données dans data/ (courses, resultats, clubs, exports) Structure du projet: - src/: Modules principaux (ffa_scraper, ffa_analyzer) - scripts/: Scripts CLI et utilitaires - config/: Configuration (config.env) - data/: Données générées - docs/: Documentation - tests/: Tests unitaires 💘 Generated with Crush Assisted-by: GLM-4.7 via Crush <crush@charm.land>
270 lines
9.9 KiB
Python
Executable File
270 lines
9.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Script pour rechercher une course dans les données FFA
|
|
Peut utiliser les fichiers CSV ou chercher directement depuis l'URL FFA
|
|
"""
|
|
|
|
import pandas as pd
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import logging
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
|
|
from ffa_scraper import FFAScraper
|
|
from datetime import datetime
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
)
|
|
|
|
def search_race_by_name_csv(nom_course, data_dir="data"):
|
|
"""Rechercher une course par nom dans les fichiers CSV"""
|
|
courses_path = os.path.join(data_dir, 'courses', 'courses_list.csv')
|
|
|
|
if not os.path.exists(courses_path):
|
|
logging.error(f"Fichier de courses introuvable: {courses_path}")
|
|
return []
|
|
|
|
try:
|
|
df = pd.read_csv(courses_path, encoding='utf-8-sig')
|
|
mask = df['nom'].str.contains(nom_course, case=False, na=False)
|
|
courses = df[mask].to_dict('records')
|
|
logging.info(f"Trouvé {len(courses)} courses correspondant à '{nom_course}'")
|
|
return courses
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors de la recherche dans les CSV: {e}")
|
|
return []
|
|
|
|
def search_race_by_date_csv(start_date, end_date=None, data_dir="data"):
|
|
"""Rechercher des courses par date dans les fichiers CSV"""
|
|
courses_path = os.path.join(data_dir, 'courses', 'courses_list.csv')
|
|
|
|
if not os.path.exists(courses_path):
|
|
logging.error(f"Fichier de courses introuvable: {courses_path}")
|
|
return []
|
|
|
|
try:
|
|
df = pd.read_csv(courses_path, encoding='utf-8-sig')
|
|
|
|
# Convertir les dates
|
|
df['date'] = pd.to_datetime(df['date'], errors='coerce')
|
|
|
|
if end_date:
|
|
mask = (df['date'] >= pd.to_datetime(start_date)) & (df['date'] <= pd.to_datetime(end_date))
|
|
else:
|
|
mask = df['date'] == pd.to_datetime(start_date)
|
|
|
|
courses = df[mask].sort_values('date').to_dict('records')
|
|
logging.info(f"Trouvé {len(courses)} courses dans la période")
|
|
return courses
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors de la recherche par date: {e}")
|
|
return []
|
|
|
|
def search_race_by_type_csv(type_course, data_dir="data"):
|
|
"""Rechercher des courses par type dans les fichiers CSV"""
|
|
courses_path = os.path.join(data_dir, 'courses', 'courses_list.csv')
|
|
|
|
if not os.path.exists(courses_path):
|
|
logging.error(f"Fichier de courses introuvable: {courses_path}")
|
|
return []
|
|
|
|
try:
|
|
df = pd.read_csv(courses_path, encoding='utf-8-sig')
|
|
mask = df['type'].str.contains(type_course, case=False, na=False)
|
|
courses = df[mask].to_dict('records')
|
|
logging.info(f"Trouvé {len(courses)} courses de type '{type_course}'")
|
|
return courses
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors de la recherche par type: {e}")
|
|
return []
|
|
|
|
def search_race_by_location_csv(lieu, data_dir="data"):
|
|
"""Rechercher des courses par lieu dans les fichiers CSV"""
|
|
courses_path = os.path.join(data_dir, 'courses', 'courses_list.csv')
|
|
|
|
if not os.path.exists(courses_path):
|
|
logging.error(f"Fichier de courses introuvable: {courses_path}")
|
|
return []
|
|
|
|
try:
|
|
df = pd.read_csv(courses_path, encoding='utf-8-sig')
|
|
mask = df['lieu'].str.contains(lieu, case=False, na=False)
|
|
courses = df[mask].to_dict('records')
|
|
logging.info(f"Trouvé {len(courses)} courses à '{lieu}'")
|
|
return courses
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors de la recherche par lieu: {e}")
|
|
return []
|
|
|
|
def search_race_live(search_term, search_type="name", max_pages=5):
|
|
"""Rechercher une course en direct depuis le site FFA"""
|
|
scraper = FFAScraper()
|
|
|
|
logging.info(f"Recherche en direct de courses (type: {search_type})...")
|
|
logging.warning("Note: Cela peut prendre du temps")
|
|
|
|
# Récupérer les courses
|
|
total_pages, total_courses, _ = scraper._detect_pagination_info()
|
|
|
|
if not total_pages:
|
|
logging.error("Impossible de détecter les données")
|
|
return []
|
|
|
|
max_pages = min(max_pages, total_pages)
|
|
logging.info(f"Analyse de {max_pages} pages...")
|
|
|
|
courses = scraper.get_courses_list(max_pages=max_pages, use_multithreading=True)
|
|
|
|
# Filtrer selon le type de recherche
|
|
filtered_courses = []
|
|
|
|
for course in courses:
|
|
match = False
|
|
|
|
if search_type == "name":
|
|
match = search_term.lower() in course.get('nom', '').lower()
|
|
elif search_type == "type":
|
|
match = search_term.lower() in course.get('type', '').lower()
|
|
elif search_type == "location":
|
|
match = search_term.lower() in course.get('lieu', '').lower()
|
|
|
|
if match:
|
|
filtered_courses.append(course)
|
|
|
|
logging.info(f"Trouvé {len(filtered_courses)} courses en direct")
|
|
return filtered_courses
|
|
|
|
def display_race_results(courses, show_details=False, limit=None):
|
|
"""Afficher les résultats de recherche de courses"""
|
|
if not courses:
|
|
print("\n❌ Aucune course trouvée")
|
|
return
|
|
|
|
print(f"\n{'='*80}")
|
|
print(f"📅 RÉSULTATS DE LA RECHERCHE ({len(courses)} courses trouvées)")
|
|
print(f"{'='*80}\n")
|
|
|
|
if limit:
|
|
courses = courses[:limit]
|
|
|
|
for i, course in enumerate(courses, 1):
|
|
print(f"{i}. {course.get('nom', 'Inconnu')}")
|
|
|
|
if course.get('date'):
|
|
print(f" 📅 Date: {course['date']}")
|
|
|
|
if course.get('lieu'):
|
|
print(f" 📍 Lieu: {course['lieu']}")
|
|
|
|
if course.get('type'):
|
|
print(f" 🏷️ Type: {course['type']}")
|
|
|
|
if course.get('niveau'):
|
|
print(f" 📊 Niveau: {course['niveau']}")
|
|
|
|
if show_details:
|
|
if course.get('discipline'):
|
|
print(f" 🎯 Discipline: {course['discipline']}")
|
|
|
|
if course.get('fiche_detail'):
|
|
print(f" 🔗 Détails: {course['fiche_detail']}")
|
|
|
|
if course.get('resultats_url'):
|
|
print(f" 🏆 Résultats: {course['resultats_url']}")
|
|
|
|
if course.get('page'):
|
|
print(f" 📄 Page: {course['page']}")
|
|
|
|
print()
|
|
|
|
print(f"{'='*80}")
|
|
|
|
def export_race_csv(courses, filename, output_dir="data"):
|
|
"""Exporter les résultats de recherche en CSV"""
|
|
os.makedirs(os.path.join(output_dir, 'exports'), exist_ok=True)
|
|
|
|
filepath = os.path.join(output_dir, 'exports', filename.replace(" ", "_"))
|
|
|
|
df = pd.DataFrame(courses)
|
|
df.to_csv(filepath, index=False, encoding='utf-8-sig')
|
|
logging.info(f"Exporté {len(courses)} courses dans {filepath}")
|
|
return filepath
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Rechercher une course dans les données FFA')
|
|
parser.add_argument('--csv', action='store_true', default=True,
|
|
help='Utiliser les fichiers CSV existants (défaut)')
|
|
parser.add_argument('--live', action='store_true',
|
|
help='Récupérer les données en direct depuis le site FFA')
|
|
parser.add_argument('--data-dir', default='data',
|
|
help='Répertoire des données CSV')
|
|
parser.add_argument('--max-pages', type=int, default=5,
|
|
help='Nombre maximum de pages à scraper en mode live (défaut: 5)')
|
|
parser.add_argument('--details', action='store_true',
|
|
help='Afficher les détails complets')
|
|
parser.add_argument('--limit', type=int,
|
|
help='Limiter le nombre de courses affichées')
|
|
parser.add_argument('--export', action='store_true',
|
|
help='Exporter les résultats en CSV')
|
|
parser.add_argument('--export-filename',
|
|
help='Nom du fichier CSV exporté')
|
|
|
|
# Critères de recherche
|
|
parser.add_argument('--name', help='Rechercher par nom de course')
|
|
parser.add_argument('--location', help='Rechercher par lieu')
|
|
parser.add_argument('--type', help='Rechercher par type de course')
|
|
parser.add_argument('--start-date', help='Date de début (format: YYYY-MM-DD)')
|
|
parser.add_argument('--end-date', help='Date de fin (format: YYYY-MM-DD)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Vérifier qu'au moins un critère de recherche est fourni
|
|
if not any([args.name, args.location, args.type, args.start_date]):
|
|
parser.error("Au moins un critère de recherche est requis (--name, --location, --type, --start-date)")
|
|
|
|
# Recherche
|
|
courses = []
|
|
|
|
if args.live:
|
|
# Mode live
|
|
if args.name:
|
|
print(f"\n🔍 Mode live: recherche de courses par nom '{args.name}'...")
|
|
courses = search_race_live(args.name, "name", args.max_pages)
|
|
elif args.type:
|
|
print(f"\n🔍 Mode live: recherche de courses par type '{args.type}'...")
|
|
courses = search_race_live(args.type, "type", args.max_pages)
|
|
elif args.location:
|
|
print(f"\n🔍 Mode live: recherche de courses par lieu '{args.location}'...")
|
|
courses = search_race_live(args.location, "location", args.max_pages)
|
|
|
|
else:
|
|
# Mode CSV
|
|
print(f"\n📂 Mode CSV: recherche dans {args.data_dir}/")
|
|
|
|
if args.name:
|
|
courses = search_race_by_name_csv(args.name, args.data_dir)
|
|
elif args.location:
|
|
courses = search_race_by_location_csv(args.location, args.data_dir)
|
|
elif args.type:
|
|
courses = search_race_by_type_csv(args.type, args.data_dir)
|
|
elif args.start_date:
|
|
courses = search_race_by_date_csv(args.start_date, args.end_date, args.data_dir)
|
|
|
|
# Affichage
|
|
display_race_results(courses, show_details=args.details, limit=args.limit)
|
|
|
|
# Export
|
|
if args.export and courses:
|
|
if args.export_filename:
|
|
filename = args.export_filename
|
|
else:
|
|
filename = f"race_search_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
|
|
|
|
filepath = export_race_csv(courses, filename, args.data_dir)
|
|
print(f"\n💾 Exporté dans: {filepath}")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|