File size: 11,229 Bytes
d655094
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9289b47
d655094
9289b47
d655094
 
 
 
 
9289b47
d655094
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9289b47
 
 
 
 
 
 
 
 
 
d655094
 
 
9289b47
 
 
d655094
 
 
 
 
 
 
 
 
 
9289b47
d655094
9289b47
d655094
9289b47
 
d655094
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9289b47
d655094
 
 
 
9289b47
d655094
 
 
 
9289b47
d655094
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9289b47
d655094
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9289b47
d655094
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9289b47
 
 
 
 
 
 
 
 
d655094
 
 
 
 
 
 
 
 
 
9289b47
d655094
 
 
 
9289b47
d655094
9289b47
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import os
import time
import gradio as gr
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from webdriver_manager.chrome import ChromeDriverManager

def wait_for_download_to_complete(download_directory, timeout=15):
    """Vérifie que le téléchargement est bien terminé."""
    end_time = time.time() + timeout
    while time.time() < end_time:
        files = os.listdir(download_directory)
        if any(f.endswith('.crdownload') or f.endswith('.tmp') for f in files):
            time.sleep(0.2)
        else:
            return True
    return False

def lancer_telechargement(nom_produit, mots_cles, nomenclature_str, progress=gr.Progress()):
    """Fonction principale appelée par le bouton Gradio."""
    download_dir = os.path.join(os.getcwd(), "FDES_Telecharges")
    if not os.path.exists(download_dir):
        os.makedirs(download_dir)

    chrome_options = Options()
    chrome_options.page_load_strategy = 'eager'
    chrome_options.add_argument("--headless=new")  # Mode invisible
    chrome_options.add_argument("--disable-gpu")
    chrome_options.add_argument("--window-size=1920,1080")

    chrome_options.add_experimental_option(
        'prefs', {
            "download.default_directory": download_dir,
            "download.prompt_for_download": False,
            "download.directory_upgrade": True,
            "plugins.always_open_pdf_externally": True,
            "profile.default_content_settings.popups": 0,
            "profile.managed_default_content_settings.images": 2
        }
    )

    progress(0, desc="Démarrage du navigateur...")
    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)

    # Autoriser les téléchargements en mode Headless
    driver.command_executor._commands["send_command"] = ("POST", '/session/$sessionId/chromium/send_command')
    params = {'cmd': 'Page.setDownloadBehavior', 'params': {'behavior': 'allow', 'downloadPath': download_dir}}
    driver.execute("send_command", params)

    wait = WebDriverWait(driver, 10)
    actions = ActionChains(driver)

    def navigate_angular_node(keyword, expand=True):
        progress(0, desc=f"Recherche de '{keyword}'...")
        target_node = None
        # Astuce : Gérer les exclusions via une syntaxe spéciale (ex: "Chauffage |NOT| Ventilation")
        exclude_keyword = None
        if "|NOT|" in keyword:
            keyword, exclude_keyword = keyword.split("|NOT|")
            keyword = keyword.strip()
            exclude_keyword = exclude_keyword.strip()

        for _ in range(20):
            wrappers = driver.find_elements(By.CSS_SELECTOR, ".node-wrapper")
            for w in wrappers:
                text_lower = w.text.lower()
                # Si le mot-clé est trouvé
                if keyword.lower() in text_lower:
                    # On vérifie qu'il ne contient PAS le mot exclu (s'il y en a un)
                    if exclude_keyword and exclude_keyword.lower() in text_lower:
                        continue
                    # On vérifie que le noeud est bien affiché à l'écran
                    if w.is_displayed():
                        target_node = w
                        break
            if target_node:
                break
            time.sleep(0.2)

        if not target_node:
            raise Exception(f"❌ Dossier contenant '{keyword}' introuvable à l'écran.")

        driver.execute_script("arguments[0].scrollIntoView({behavior: 'auto', block: 'center'});", target_node)

        if expand:
            try:
                expander = target_node.find_element(By.CSS_SELECTOR, ".toggle-children-wrapper-collapsed")
                driver.execute_script("arguments[0].click();", expander)
                time.sleep(0.5)  # Laisser le temps au dossier de s'ouvrir
            except:
                pass  # Déjà ouvert ou pas de flèche
        else:
            content = target_node.find_element(By.CSS_SELECTOR, ".node-content-wrapper")
            driver.execute_script("arguments[0].click();", content)

    try:
        progress(0.1, desc="Ouverture du site INIES...")
        driver.get("https://base-inies.fr/consultation/recherche")

        wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button.nomenclature-select-btn")))

        # Accepter les cookies
        try:
            cookie_btn = driver.find_element(By.XPATH, "//*[contains(text(), 'compris') or contains(text(), 'Accepter')]")
            driver.execute_script("arguments[0].click();", cookie_btn)
        except:
            pass

        # 1. Remplir le champ "Nom de produit"
        if nom_produit:
            input_nom = driver.find_element(By.CSS_SELECTOR, 'input[formcontrolname="nomProduit"]')
            input_nom.send_keys(nom_produit)

        # 2. Remplir le champ "Mots clés"
        if mots_cles:
            input_mots = driver.find_element(By.CSS_SELECTOR, 'input[formcontrolname="motsCles"]')
            input_mots.send_keys(mots_cles)

        # 3. Gérer la nomenclature (si renseignée)
        if nomenclature_str:
            btn_nomenclature = driver.find_element(By.CSS_SELECTOR, "button.nomenclature-select-btn")
            driver.execute_script("arguments[0].click();", btn_nomenclature)
            wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".node-wrapper")))

            # Découper le chemin fourni par l'utilisateur (séparé par des ;)
            etapes = [e.strip() for e in nomenclature_str.split(';') if e.strip()]
            for i, etape in enumerate(etapes):
                est_dernier = (i == len(etapes) - 1)
                # Déplier tous les dossiers sauf le dernier qu'on doit sélectionner
                navigate_angular_node(etape, expand=not est_dernier)

            btn_ok = wait.until(EC.element_to_be_clickable((By.XPATH, "//div[contains(@class, 'app-modal-footer')]//button[contains(text(), 'Ok')]")))
            driver.execute_script("arguments[0].click();", btn_ok)
            time.sleep(0.5)

        # 4. Lancer la recherche globale
        progress(0.2, desc="Lancement de la recherche...")
        btn_rechercher = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(., 'Rechercher')]")))
        driver.execute_script("arguments[0].click();", btn_rechercher)

        # 5. Attente des résultats
        try:
            wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".result-panel .tree-node-leaf")))
            time.sleep(1)
        except:
            return "Aucun produit trouvé avec ces critères."

        leaf_nodes = driver.find_elements(By.CSS_SELECTOR, ".result-panel .tree-node-leaf .node-content-wrapper")
        num_products = len(leaf_nodes)

        if num_products == 0:
            return "Aucun produit trouvé avec ces critères."

        # 6. Boucle de téléchargement
        telecharges = 0
        for i in range(num_products):
            progress(0.2 + (0.8 * (i / num_products)), desc=f"Téléchargement: {i + 1}/{num_products} produits...")

            wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".result-panel .tree-node-leaf")))
            leaf_nodes = driver.find_elements(By.CSS_SELECTOR, ".result-panel .tree-node-leaf .node-content-wrapper")

            if i >= len(leaf_nodes):
                break

            product_to_click = leaf_nodes[i]

            try:
                driver.execute_script("arguments[0].scrollIntoView({behavior: 'auto', block: 'center'});", product_to_click)
                actions.move_to_element(product_to_click).click().perform()
            except:
                continue

            # Onglet Documents et téléchargement
            try:
                wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".produit-details")))

                tab_docs = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(@class, 'tablinks') and contains(., 'Documents')]")))
                driver.execute_script("arguments[0].click();", tab_docs)

                xpath_pdf = "//td[contains(., 'FDES/PEP')]/following-sibling::td/a"
                pdf_link = wait.until(EC.presence_of_element_located((By.XPATH, xpath_pdf)))

                driver.execute_script("arguments[0].scrollIntoView({behavior: 'auto', block: 'center'});", pdf_link)
                driver.execute_script("arguments[0].click();", pdf_link)

                # Attendre la fin du fichier .crdownload
                wait_for_download_to_complete(download_dir)
                telecharges += 1
            except:
                pass  # Pas de FDES pour ce produit

            driver.back()
            wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ".result-panel .tree-node-leaf")))

        return f"✅ Opération terminée avec succès !\n\n{telecharges} fichier(s) FDES/PEP téléchargé(s) sur un total de {num_products} produit(s) trouvé(s).\nDossier : {download_dir}"

    except Exception as e:
        return f"❌ Une erreur est survenue : {str(e)}"

    finally:
        driver.quit()

# ---------------------------------------------------------
# INTERFACE GRAPHIQUE AVEC GRADIO
# ---------------------------------------------------------
with gr.Blocks(title="Extracteur INIES", theme=gr.themes.Soft()) as interface:
    gr.Markdown("# 🏢 Extracteur Automatique - Base INIES")
    gr.Markdown("Renseignez un ou plusieurs champs. Le robot s'occupera de piloter le site en arrière-plan pour récupérer les PDFs (FDES/PEP) correspondants.")

    with gr.Row():
        with gr.Column(scale=2):
            nom_produit = gr.Textbox(label="Nom de produit", placeholder="Ex: AQUACIAT POWER")
            mots_cles = gr.Textbox(label="Mot(s) clé(s)", placeholder="Ex: Pompe à chaleur")

            gr.Markdown("**Famille de produit** (Nomenclature)\nSéparez chaque étape par un point-virgule (`;`).\n*Astuce : Utilisez `|NOT|` pour exclure un terme si plusieurs dossiers se ressemblent.*")
            nomenclature = gr.Textbox(
                label="",
                placeholder="Ex: électriques; génie climatique |NOT| électriques; Chauffage |NOT| Ventilation; Pompes à chaleur collectives; Air/eau",
                value="électriques; génie climatique |NOT| électriques; Chauffage |NOT| Ventilation; Pompes à chaleur collectives; Air/eau",
                lines=2
            )

            btn_lancer = gr.Button("🚀 Lancer la recherche et le téléchargement", variant="primary")

        with gr.Column(scale=1):
            resultat = gr.Textbox(label="Statut de l'opération", lines=8, interactive=False)

    # Lier le bouton à la fonction Selenium
    btn_lancer.click(
        fn=lancer_telechargement,
        inputs=[nom_produit, mots_cles, nomenclature],
        outputs=resultat
    )

if __name__ == "__main__":
    # Lancement de l'interface
    print("Démarrage de l'interface Gradio...")
    interface.launch()