CODEXMONITEUR / src /extract_codex.py
MMOON's picture
Create extract_codex.py
997d370 verified
# extract_codex.py
import requests
from bs4 import BeautifulSoup
import json
import time
from datetime import datetime
import os
# URLs des sections du Codex Alimentarius
URLS = {
"guidelines": "https://www.fao.org/fao-who-codexalimentarius/codex-texts/guidelines/fr/",
"standards": "https://www.fao.org/fao-who-codexalimentarius/codex-texts/list-standards/fr/",
"codes_of_practice": "https://www.fao.org/fao-who-codexalimentarius/codex-texts/codes-of-practice/fr/",
"miscellaneous": "https://www.fao.org/fao-who-codexalimentarius/codex-texts/miscellaneous/fr/"
}
# Mapping des catégories pour les noms de fichiers ou l'identification
CATEGORY_MAP = {
"guidelines": "Directives (CXG)",
"standards": "Normes (CXS)",
"codes_of_practice": "Codes de Pratique (CXC)",
"miscellaneous": "Divers (CXM)"
}
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36'
}
def extract_table_data(url, category_key):
"""
Extrait les données d'un tableau spécifique d'une page Codex.
"""
print(f"Extraction depuis: {url}")
try:
response = requests.get(url, headers=HEADERS, timeout=15) # Ajout d'un timeout
response.raise_for_status()
soup = BeautifulSoup(response.content, 'html.parser')
data_lines = []
# Trouver tous les tableaux
tables = soup.find_all('table')
target_table = None
# Heuristique : trouver le tableau avec le plus de lignes significatives
max_rows = 0
for table in tables:
rows = table.find_all('tr')
# Compter les lignes avec des cellules de données (td), pas seulement des en-têtes (th)
data_rows_count = sum(1 for row in rows if row.find('td'))
if data_rows_count > max_rows:
max_rows = data_rows_count
target_table = table
if not target_table:
print(f" Avertissement: Aucun tableau principal trouvé sur {url}")
return []
# Extraire les lignes du tableau trouvé
rows = target_table.find_all('tr')
# Identifier les en-têtes si nécessaire (optionnel ici car la structure est connue)
# Nous supposons que la première ligne est l'en-tête et les suivantes sont les données
# Parcourir les lignes de données
for row in rows:
cells = row.find_all(['td', 'th']) # Inclure th au cas où l'en-tête serait dans le tbody
if len(cells) >= 4: # S'assurer qu'il y a suffisamment de colonnes
# Extraire le texte de chaque cellule et le nettoyer
# La structure semble être: Code | Titre | Comité | Année | Status
code_raw = cells[0].get_text(strip=True) if cells[0] else ""
title_raw = cells[1].get_text(strip=True) if cells[1] else ""
committee_raw = cells[2].get_text(strip=True) if cells[2] else ""
year_raw = cells[3].get_text(strip=True) if cells[3] else ""
# status_raw = cells[4].get_text(strip=True) if len(cells) > 4 else "" # Optionnel
# Nettoyer les données extraites
# Parfois, le code contient aussi le titre, essayons de les séparer si c'est le cas
# Mais souvent, ils sont dans des cellules séparées. Vérifions simplement.
code = code_raw
title = title_raw
committee = committee_raw
year = year_raw
# Vérifier si les données de base sont présentes
if code and title and committee and year:
# Déterminer le préfixe pour la catégorie (CXG, CXS, etc.)
prefix_map = {
"guidelines": "CXG",
"standards": "CXS",
"codes_of_practice": "CXC",
"miscellaneous": "CXM"
}
expected_prefix = prefix_map.get(category_key, "CX")
# Vérifier si le code commence par le bon préfixe (optionnel mais bon filtre)
# Parfois les pages peuvent avoir des données supplémentaires, donc on filtre
if code.startswith(expected_prefix) or category_key == "miscellaneous": # Divers peut avoir différents codes
data_lines.append({
"code": code,
"title": title,
"committee": committee,
"year": year,
# "status": status_raw # Ajouter si pertinent
})
# else:
# print(f" Debug: Code '{code}' ne commence pas par '{expected_prefix}', ignoré sur {url}.") # Pour debug
# else:
# print(f" Debug: Ligne ignorée en raison de données manquantes: {code_raw}, {title_raw}, {committee_raw}, {year_raw} sur {url}") # Pour debug
print(f" {len(data_lines)} documents extraits de {category_key}.")
return data_lines
except requests.exceptions.Timeout:
print(f" Erreur: Timeout lors de la requête HTTP pour {url}")
return []
except requests.exceptions.RequestException as e:
print(f" Erreur lors de la requête HTTP pour {url}: {e}")
return []
except Exception as e:
print(f" Erreur inattendue lors du parsing de {url}: {e}")
import traceback
traceback.print_exc() # Affiche la pile d'appel pour le débogage
return []
def run_extraction(output_dir="data"):
"""
Fonction principale pour orchestrer l'extraction et sauvegarder les données.
"""
print("Démarrage de l'extraction des données du Codex Alimentarius...")
# Créer le dossier de sortie s'il n'existe pas
os.makedirs(output_dir, exist_ok=True)
all_data = {}
total_documents = 0
for key, url in URLS.items():
print(f"\n--- Extraction de la catégorie: {CATEGORY_MAP[key]} ---")
documents = extract_table_data(url, key)
all_data[key] = documents
total_documents += len(documents)
# Pause courte pour être gentil avec le serveur
time.sleep(1)
# Sauvegarder les données dans un fichier JSON structuré
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
output_filename = f"codex_data_{timestamp}.json"
output_path = os.path.join(output_dir, output_filename)
final_output = {
"extraction_date": datetime.now().isoformat(),
"total_documents": total_documents,
"categories": all_data
}
try:
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(final_output, f, ensure_ascii=False, indent=4)
print(f"\n✅ Extraction terminée avec succès!")
print(f" {total_documents} documents ont été extraits et sauvegardés dans '{output_path}'.")
return output_path # Retourner le chemin du fichier créé
except Exception as e:
print(f"\n❌ Erreur lors de la sauvegarde du fichier JSON: {e}")
return None
# Optionnel: Afficher un résumé
# print("\n--- Résumé de l'extraction ---")
# for key, docs in all_data.items():
# print(f" - {CATEGORY_MAP[key]}: {len(docs)} documents")
if __name__ == "__main__":
# Si le script est exécuté directement
run_extraction()