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>
This commit is contained in:
Muyue
2026-01-01 18:05:14 +01:00
commit a5406a4e89
16 changed files with 3920 additions and 0 deletions

181
scripts/list_clubs.py Executable file
View File

@@ -0,0 +1,181 @@
#!/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()