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