- 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>
182 lines
6.6 KiB
Python
Executable File
182 lines
6.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Script pour lister tous les clubs présents dans les résultats FFA
|
|
Utilise les fichiers CSV générés par le scraper ou les données live 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 collections import defaultdict
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
)
|
|
|
|
def list_clubs_from_csv(data_dir="data"):
|
|
"""Lister tous les clubs à partir des fichiers CSV"""
|
|
results_path = os.path.join(data_dir, 'resultats', 'results.csv')
|
|
|
|
if not os.path.exists(results_path):
|
|
logging.error(f"Fichier de résultats introuvable: {results_path}")
|
|
print("\n💡 Pour générer les résultats, utilisez:")
|
|
print(" python ffa_cli.py scrape --fetch-details")
|
|
return []
|
|
|
|
try:
|
|
df = pd.read_csv(results_path, encoding='utf-8-sig')
|
|
logging.info(f"Chargé {len(df)} résultats")
|
|
|
|
# Extraire les clubs uniques
|
|
clubs_info = df.groupby('club').agg({
|
|
'nom': lambda x: x.nunique(), # Nombre d'athlètes uniques
|
|
'dept': lambda x: x.mode()[0] if len(x.mode()) > 0 else '',
|
|
'ligue': lambda x: x.mode()[0] if len(x.mode()) > 0 else ''
|
|
}).reset_index()
|
|
|
|
clubs_info.columns = ['club', 'athletes_count', 'departement', 'ligue']
|
|
clubs_info = clubs_info.sort_values('athletes_count', ascending=False)
|
|
|
|
return clubs_info.to_dict('records')
|
|
except Exception as e:
|
|
logging.error(f"Erreur lors de la lecture des CSV: {e}")
|
|
return []
|
|
|
|
def list_clubs_live():
|
|
"""Lister les clubs depuis l'URL FFA (besoin de scraping live)"""
|
|
scraper = FFAScraper()
|
|
|
|
logging.info("Récupération des données en direct depuis le site FFA...")
|
|
logging.warning("Note: Cette méthode nécessite un scraping complet, ce qui peut prendre du temps")
|
|
|
|
# Récupérer les résultats depuis le site
|
|
# Pour simplifier, nous récupérons quelques courses et extrayons les clubs
|
|
total_pages, total_courses, _ = scraper._detect_pagination_info()
|
|
|
|
if not total_pages:
|
|
logging.error("Impossible de détecter les données")
|
|
return []
|
|
|
|
# Limiter à quelques pages pour éviter trop de temps
|
|
max_pages = min(5, total_pages)
|
|
logging.info(f"Analyse de {max_pages} pages pour extraire les clubs...")
|
|
|
|
clubs = defaultdict(lambda: {
|
|
'count': 0,
|
|
'athletes': set(),
|
|
'dept': '',
|
|
'ligue': ''
|
|
})
|
|
|
|
# Scraper les courses et récupérer les résultats
|
|
courses = scraper.get_courses_list(max_pages=max_pages, use_multithreading=True)
|
|
|
|
for course in courses:
|
|
if course.get('resultats_url'):
|
|
results = scraper.get_course_results(course['resultats_url'])
|
|
for result in results:
|
|
club = result.get('club', 'Inconnu')
|
|
clubs[club]['count'] += 1
|
|
clubs[club]['athletes'].add(f"{result.get('prenom', '')} {result.get('nom', '')}")
|
|
if not clubs[club]['dept'] and result.get('dept'):
|
|
clubs[club]['dept'] = result['dept']
|
|
if not clubs[club]['ligue'] and result.get('ligue'):
|
|
clubs[club]['ligue'] = result['ligue']
|
|
|
|
# Convertir en liste et trier
|
|
clubs_list = []
|
|
for club, info in clubs.items():
|
|
clubs_list.append({
|
|
'club': club,
|
|
'athletes_count': len(info['athletes']),
|
|
'results_count': info['count'],
|
|
'departement': info['dept'],
|
|
'ligue': info['ligue']
|
|
})
|
|
|
|
clubs_list.sort(key=lambda x: x['athletes_count'], ascending=False)
|
|
|
|
return clubs_list
|
|
|
|
def display_clubs(clubs, limit=None, show_details=False):
|
|
"""Afficher la liste des clubs"""
|
|
if not clubs:
|
|
print("\n❌ Aucun club trouvé")
|
|
return
|
|
|
|
print(f"\n{'='*80}")
|
|
print(f"📊 LISTE DES CLUBS ({len(clubs)} clubs trouvés)")
|
|
print(f"{'='*80}\n")
|
|
|
|
if limit:
|
|
clubs = clubs[:limit]
|
|
|
|
for i, club in enumerate(clubs, 1):
|
|
print(f"{i:3d}. {club['club']}")
|
|
print(f" Athlètes: {club['athletes_count']}")
|
|
|
|
if show_details:
|
|
if 'results_count' in club:
|
|
print(f" Résultats totaux: {club['results_count']}")
|
|
if club.get('departement'):
|
|
print(f" Département: {club['departement']}")
|
|
if club.get('ligue'):
|
|
print(f" Ligue: {club['ligue']}")
|
|
|
|
print()
|
|
|
|
print(f"{'='*80}")
|
|
|
|
def export_clubs_csv(clubs, filename="clubs_list_export.csv", output_dir="data"):
|
|
"""Exporter la liste des clubs en CSV"""
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
filepath = os.path.join(output_dir, filename)
|
|
|
|
df = pd.DataFrame(clubs)
|
|
df.to_csv(filepath, index=False, encoding='utf-8-sig')
|
|
logging.info(f"Exporté {len(clubs)} clubs dans {filepath}")
|
|
return filepath
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Lister tous les clubs des résultats 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('--limit', type=int,
|
|
help='Limiter le nombre de clubs affichés')
|
|
parser.add_argument('--details', action='store_true',
|
|
help='Afficher les détails (dpt, ligue, résultats)')
|
|
parser.add_argument('--export', action='store_true',
|
|
help='Exporter la liste en CSV')
|
|
parser.add_argument('--output', default='data',
|
|
help='Répertoire de sortie pour les données CSV')
|
|
parser.add_argument('--export-filename', default='clubs_list_export.csv',
|
|
help='Nom du fichier CSV exporté')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Choisir la source des données
|
|
if args.live:
|
|
print("\n⚠️ Mode live: récupération des données depuis le site FFA...")
|
|
clubs = list_clubs_live()
|
|
else:
|
|
print(f"\n📂 Mode CSV: utilisation des fichiers dans {args.output}/")
|
|
clubs = list_clubs_from_csv(args.output)
|
|
|
|
# Afficher les résultats
|
|
display_clubs(clubs, limit=args.limit, show_details=args.details)
|
|
|
|
# Exporter si demandé
|
|
if args.export and clubs:
|
|
filepath = export_clubs_csv(clubs, args.export_filename, args.output)
|
|
print(f"\n💾 Exporté dans: {filepath}")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|