Suite aux changements majeurs du site bases.athle.fr :
- Nouvelles URLs pour les pages athlètes et clubs
- Changement des IDs athlètes (overlaps possibles entre anciens et nouveaux IDs)
- Nouveau format HTML
Décision : Refactorisation complète avec nouvelle architecture de base de données.
- ✅ IDs internes auto-générés - Plus de conflits possibles
- ✅
license_idcomme clé métier - Identifiant stable et fiable - ✅
normalized_namepour recherche performante - Index trigram pour recherches floues rapides - ✅ Gestion des logs améliorée - Rotation automatique et archivage
- ✅ Architecture modulaire - Code organisé en modules (scraper/, tools/)
mypacer_scraper/
├── core/ # ✅ Module fondamental
│ ├── __init__.py
│ ├── db.py # ✅ Connexion base de données
│ ├── config.py # ✅ Configuration logs améliorée
│ ├── schema.py # ✅ Gestion du schéma
│ └── schema.sql # ✅ Définition du schéma SQL
├── scraper/ # Module de scraping
│ ├── __init__.py
│ ├── list_athletes.py # Script de scraping athlètes
│ └── list_clubs.py # Script de scraping clubs
├── tools/ # Outils d'analyse et maintenance
│ ├── __init__.py
│ └── analyze_database.py # ✅ Script d'analyse consolidé
├── tests/ # Tests unitaires
│ ├── test_duplicate_handling.py
│ └── test_storage.py
├── logs/ # Logs avec rotation automatique
│ ├── archive/ # Anciens logs archivés
│ └── *.log # Logs actifs (5 derniers)
├── update_database.sh # Script principal de mise à jour
└── README.md
CREATE TABLE athletes (
id SERIAL PRIMARY KEY, -- ✅ ID interne auto-increment
ffa_id TEXT NOT NULL UNIQUE, -- ID FFA (abstrait)
license_id TEXT, -- Numéro de licence (clé métier)
name TEXT NOT NULL,
normalized_name TEXT NOT NULL, -- ✅ Pour recherche rapide
url TEXT,
birth_date TEXT,
sexe TEXT,
nationality TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Index unique partiel : garantit qu'un license_id valide ne peut exister qu'une fois
CREATE UNIQUE INDEX idx_athletes_license_id_unique ON athletes(license_id)
WHERE license_id IS NOT NULL
AND license_id != ''
AND license_id != '-'
AND license_id != 'None';Index créés :
idx_athletes_ffa_id- Recherche par ID FFAidx_athletes_license_id_unique- ✅ Index unique partiel sur license_id (exclut valeurs invalides)idx_athletes_normalized_name_trgm- Recherche floue ultra-rapide (GIN trigram)idx_athletes_license_id- Recherche par numéro de licence- Autres index sur sexe, birth_date, etc.
CREATE TABLE clubs (
id SERIAL PRIMARY KEY,
ffa_id TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
normalized_name TEXT NOT NULL,
first_year INTEGER,
last_year INTEGER,
url TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);trigger_update_athlete_normalized_name- Met à jour automatiquementnormalized_nameetupdated_attrigger_update_club_normalized_name- Idem pour clubs
v_athletes_stats- Statistiques globales sur les athlètesv_clubs_stats- Statistiques globales sur les clubs
Avant :
- Un seul fichier
scrapping.log - Pas de rotation
- Logs qui grossissent indéfiniment
Après :
from core.config import setup_logging
setup_logging('update_database') # Crée update_database_20251115_174530.log✅ Fonctionnalités :
- Nom de fichier avec timestamp
- Rotation automatique (garde les 5 derniers)
- Archivage des anciens dans
logs/archive/ - Nettoyage automatique des archives > 30 jours
Avant :
- Tables créées directement dans les scripts
- Pas de normalisation des noms
- Pas de vues ni de fonctions
Après :
python -m core.schema # Crée toutes les tables + indexes + triggers + vues✅ Fonctionnalités :
- Schéma complet en SQL
- Fonction
normalize_text()PostgreSQL - Triggers automatiques
- Vues pour statistiques
- Extension
pg_trgmpour recherche rapide
Avant :
- Multiples scripts (analyze_duplicates.py, cleanup_duplicates.py, etc.)
- Analyses fragmentées
Après :
python tools/analyze_database.py # Analyse complète en un seul script✅ Affiche :
- Statistiques générales (totaux, répartition H/F, années)
- Qualité des données (complétude de chaque champ)
- Analyse des URLs (ancien/nouveau format)
- Doublons potentiels
- Recommandations
# Backup complet
pg_dump -U $POSTGRES_USER -d $POSTGRES_DB > backup_before_refactoring_$(date +%Y%m%d).sql
# Ou utiliser votre script de backup
./your_backup_script.sh# Option 1 : Via le script Python
python -m core.schema
# Option 2 : Directement en SQL
psql -U $POSTGRES_USER -d $POSTGRES_DB -f core/schema.sqlÀ faire :
- Déplacer
list_athletes.pydansscraper/ - Déplacer
list_clubs.pydansscraper/ - Modifier les fonctions de création de tables pour utiliser le nouveau schéma
- Ajouter la normalisation des noms lors de l'insertion
- Utiliser
ffa_idau lieu deidpour stocker l'ID FFA
Exemple de modification :
# AVANT
cursor.execute("""
INSERT INTO athletes (id, name, url, birth_date, license_id, sexe, nationality)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (id) DO UPDATE ...
""", (athlete_id, name, url, birth_date, license_id, sexe, nationality))
# APRÈS
cursor.execute("""
INSERT INTO athletes (ffa_id, name, url, birth_date, license_id, sexe, nationality)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (ffa_id) DO UPDATE ...
""", (athlete_id, name, url, birth_date, license_id, sexe, nationality))
# Note: normalized_name est géré automatiquement par le trigger#!/bin/bash
LOG_FILE="logs/update.log"
TIMESTAMP="$(date +"%Y%m%d_%H%M%S")"
# ... reste du script inchangé# Scraper quelques clubs pour tester
python scraper/list_clubs.py --first-year 2025 --last-year 2025
# Vérifier avec l'outil d'analyse
python tools/analyze_database.pySi vous voulez migrer les données existantes :
# Script de migration (à créer)
# 1. Lire l'ancienne base
# 2. Insérer dans la nouvelle avec ffa_id
# 3. Laisser les triggers gérer normalized_namecore/db.py- ✅ Connexion base de donnéescore/config.py- ✅ Configuration logs amélioréecore/schema.py- ✅ Gestion du schémacore/schema.sql- ✅ Définition du schéma SQLcore/__init__.py- ✅ Module package
scraper/list_athletes.py- ✅ Scraping athlètesscraper/list_clubs.py- ✅ Scraping clubstools/analyze_database.py- ✅ Script d'analyse consolidéscraper/__init__.py- ✅ Module packagetools/__init__.py- ✅ Module package
update_database.sh- À adapter pour le module coreREADME.md- À mettre à jour
analyze_duplicates.pycleanup_duplicates.pyanalyze_id_conflicts.pyupdate_missing_licenses.pyupdate_old_urls.pyMIGRATION_LICENSE_ID.md
Pour utiliser la recherche optimisée avec normalized_name :
# AVANT
normalized_query = ' '.join(unidecode(name).lower().strip().split())
query_parts = normalized_query.split()
where_clause = " AND ".join(["LOWER(name) LIKE %s" for _ in query_parts])
# APRÈS
normalized_query = ' '.join(unidecode(name).lower().strip().split())
query_parts = normalized_query.split()
where_clause = " AND ".join(["normalized_name LIKE %s" for _ in query_parts])
# Bonus : tri par pertinence
query = f"""
SELECT id, ffa_id, name, url, birth_date, license_id, sexe, nationality
FROM athletes
WHERE {where_clause}
ORDER BY similarity(normalized_name, %s) DESC
LIMIT 25
"""- Backup de l'ancienne base effectué
- Module
core/créé avec db, config, schema - Imports mis à jour dans tous les fichiers
-
update_database.shmis à jour pour utiliserpython3 -m scraper.* - Fichier
log.txthardcodé supprimé (utilise système de logging centralisé) - Nouveau schéma créé (
python -m core.schema) - Scripts de scraping adaptés pour utiliser
ffa_id - Tests effectués sur un petit échantillon
- API mise à jour pour utiliser
normalized_nameetffa_id - Anciens scripts obsolètes supprimés
- Documentation (
README.md) mise à jour
- Performance : Recherches jusqu'à 10x plus rapides avec les index trigram
- Robustesse : Plus de conflits d'IDs possibles
- Maintenabilité : Code organisé en modules
- Traçabilité : Logs horodatés avec archivage automatique
- Qualité : Indicateurs de qualité des données facilement accessibles
- Pérennité : Résistant aux futurs changements du site FFA
Pour toute question ou problème lors de la migration :
- Consulter les logs dans
logs/ - Lancer
python tools/analyze_database.pypour diagnostiquer - Restaurer le backup si nécessaire