Files
ffa-calendar/scripts/search_race.py
Muyue a5406a4e89 Initial commit: Reorganiser le projet FFA Calendar Scraper
- 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>
2026-01-01 18:05:14 +01:00

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()