MMOON commited on
Commit
997d370
·
verified ·
1 Parent(s): fe6e33f

Create extract_codex.py

Browse files
Files changed (1) hide show
  1. src/extract_codex.py +171 -0
src/extract_codex.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # extract_codex.py
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
+ import json
5
+ import time
6
+ from datetime import datetime
7
+ import os
8
+
9
+ # URLs des sections du Codex Alimentarius
10
+ URLS = {
11
+ "guidelines": "https://www.fao.org/fao-who-codexalimentarius/codex-texts/guidelines/fr/",
12
+ "standards": "https://www.fao.org/fao-who-codexalimentarius/codex-texts/list-standards/fr/",
13
+ "codes_of_practice": "https://www.fao.org/fao-who-codexalimentarius/codex-texts/codes-of-practice/fr/",
14
+ "miscellaneous": "https://www.fao.org/fao-who-codexalimentarius/codex-texts/miscellaneous/fr/"
15
+ }
16
+
17
+ # Mapping des catégories pour les noms de fichiers ou l'identification
18
+ CATEGORY_MAP = {
19
+ "guidelines": "Directives (CXG)",
20
+ "standards": "Normes (CXS)",
21
+ "codes_of_practice": "Codes de Pratique (CXC)",
22
+ "miscellaneous": "Divers (CXM)"
23
+ }
24
+
25
+ HEADERS = {
26
+ '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'
27
+ }
28
+
29
+ def extract_table_data(url, category_key):
30
+ """
31
+ Extrait les données d'un tableau spécifique d'une page Codex.
32
+ """
33
+ print(f"Extraction depuis: {url}")
34
+ try:
35
+ response = requests.get(url, headers=HEADERS, timeout=15) # Ajout d'un timeout
36
+ response.raise_for_status()
37
+ soup = BeautifulSoup(response.content, 'html.parser')
38
+
39
+ data_lines = []
40
+ # Trouver tous les tableaux
41
+ tables = soup.find_all('table')
42
+
43
+ target_table = None
44
+ # Heuristique : trouver le tableau avec le plus de lignes significatives
45
+ max_rows = 0
46
+ for table in tables:
47
+ rows = table.find_all('tr')
48
+ # Compter les lignes avec des cellules de données (td), pas seulement des en-têtes (th)
49
+ data_rows_count = sum(1 for row in rows if row.find('td'))
50
+ if data_rows_count > max_rows:
51
+ max_rows = data_rows_count
52
+ target_table = table
53
+
54
+ if not target_table:
55
+ print(f" Avertissement: Aucun tableau principal trouvé sur {url}")
56
+ return []
57
+
58
+ # Extraire les lignes du tableau trouvé
59
+ rows = target_table.find_all('tr')
60
+ # Identifier les en-têtes si nécessaire (optionnel ici car la structure est connue)
61
+ # Nous supposons que la première ligne est l'en-tête et les suivantes sont les données
62
+ # Parcourir les lignes de données
63
+ for row in rows:
64
+ cells = row.find_all(['td', 'th']) # Inclure th au cas où l'en-tête serait dans le tbody
65
+ if len(cells) >= 4: # S'assurer qu'il y a suffisamment de colonnes
66
+ # Extraire le texte de chaque cellule et le nettoyer
67
+ # La structure semble être: Code | Titre | Comité | Année | Status
68
+ code_raw = cells[0].get_text(strip=True) if cells[0] else ""
69
+ title_raw = cells[1].get_text(strip=True) if cells[1] else ""
70
+ committee_raw = cells[2].get_text(strip=True) if cells[2] else ""
71
+ year_raw = cells[3].get_text(strip=True) if cells[3] else ""
72
+ # status_raw = cells[4].get_text(strip=True) if len(cells) > 4 else "" # Optionnel
73
+
74
+ # Nettoyer les données extraites
75
+ # Parfois, le code contient aussi le titre, essayons de les séparer si c'est le cas
76
+ # Mais souvent, ils sont dans des cellules séparées. Vérifions simplement.
77
+ code = code_raw
78
+ title = title_raw
79
+ committee = committee_raw
80
+ year = year_raw
81
+
82
+ # Vérifier si les données de base sont présentes
83
+ if code and title and committee and year:
84
+ # Déterminer le préfixe pour la catégorie (CXG, CXS, etc.)
85
+ prefix_map = {
86
+ "guidelines": "CXG",
87
+ "standards": "CXS",
88
+ "codes_of_practice": "CXC",
89
+ "miscellaneous": "CXM"
90
+ }
91
+ expected_prefix = prefix_map.get(category_key, "CX")
92
+ # Vérifier si le code commence par le bon préfixe (optionnel mais bon filtre)
93
+ # Parfois les pages peuvent avoir des données supplémentaires, donc on filtre
94
+ if code.startswith(expected_prefix) or category_key == "miscellaneous": # Divers peut avoir différents codes
95
+ data_lines.append({
96
+ "code": code,
97
+ "title": title,
98
+ "committee": committee,
99
+ "year": year,
100
+ # "status": status_raw # Ajouter si pertinent
101
+ })
102
+ # else:
103
+ # print(f" Debug: Code '{code}' ne commence pas par '{expected_prefix}', ignoré sur {url}.") # Pour debug
104
+ # else:
105
+ # print(f" Debug: Ligne ignorée en raison de données manquantes: {code_raw}, {title_raw}, {committee_raw}, {year_raw} sur {url}") # Pour debug
106
+
107
+ print(f" {len(data_lines)} documents extraits de {category_key}.")
108
+ return data_lines
109
+
110
+ except requests.exceptions.Timeout:
111
+ print(f" Erreur: Timeout lors de la requête HTTP pour {url}")
112
+ return []
113
+ except requests.exceptions.RequestException as e:
114
+ print(f" Erreur lors de la requête HTTP pour {url}: {e}")
115
+ return []
116
+ except Exception as e:
117
+ print(f" Erreur inattendue lors du parsing de {url}: {e}")
118
+ import traceback
119
+ traceback.print_exc() # Affiche la pile d'appel pour le débogage
120
+ return []
121
+
122
+
123
+ def run_extraction(output_dir="data"):
124
+ """
125
+ Fonction principale pour orchestrer l'extraction et sauvegarder les données.
126
+ """
127
+ print("Démarrage de l'extraction des données du Codex Alimentarius...")
128
+
129
+ # Créer le dossier de sortie s'il n'existe pas
130
+ os.makedirs(output_dir, exist_ok=True)
131
+
132
+ all_data = {}
133
+ total_documents = 0
134
+
135
+ for key, url in URLS.items():
136
+ print(f"\n--- Extraction de la catégorie: {CATEGORY_MAP[key]} ---")
137
+ documents = extract_table_data(url, key)
138
+ all_data[key] = documents
139
+ total_documents += len(documents)
140
+ # Pause courte pour être gentil avec le serveur
141
+ time.sleep(1)
142
+
143
+ # Sauvegarder les données dans un fichier JSON structuré
144
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
145
+ output_filename = f"codex_data_{timestamp}.json"
146
+ output_path = os.path.join(output_dir, output_filename)
147
+
148
+ final_output = {
149
+ "extraction_date": datetime.now().isoformat(),
150
+ "total_documents": total_documents,
151
+ "categories": all_data
152
+ }
153
+
154
+ try:
155
+ with open(output_path, 'w', encoding='utf-8') as f:
156
+ json.dump(final_output, f, ensure_ascii=False, indent=4)
157
+ print(f"\n✅ Extraction terminée avec succès!")
158
+ print(f" {total_documents} documents ont été extraits et sauvegardés dans '{output_path}'.")
159
+ return output_path # Retourner le chemin du fichier créé
160
+ except Exception as e:
161
+ print(f"\n❌ Erreur lors de la sauvegarde du fichier JSON: {e}")
162
+ return None
163
+
164
+ # Optionnel: Afficher un résumé
165
+ # print("\n--- Résumé de l'extraction ---")
166
+ # for key, docs in all_data.items():
167
+ # print(f" - {CATEGORY_MAP[key]}: {len(docs)} documents")
168
+
169
+ if __name__ == "__main__":
170
+ # Si le script est exécuté directement
171
+ run_extraction()