Marche Public-Dossier de Consultation des Entreprises

Bonjour à toutes et tous,

Dans le cadre d’un travail citoyen d’archivage des dossiers DCE des marchés, nous téléchargeons systématiquement les dossiers de consultation des entreprises des marchés de la ville de Marseille, et occasionnellement ceux de notre métropole, département, région. Ces collectivités utilisent (classiquement) la solution local-trust de la société éditrice Atexo.

Vu que les données hébergées appartiennent aux collectivités, est-ce que les données fournies ne devraient pas être plus facilement accessibles en répondant aux exigences du code des relations entre le public et l’administration ?

Actuellement, pour chaque dossier, il faut confirmer vouloir y accéder de manière anonyme, faire plusieurs pages et plusieurs clics avant d’accéder à un téléchargement d’un document zippé du dossier.

Ça nous interpelle, nous nous disons que ces données devraient être accessibles bcp plus facilement dans un espace partagé, voir même au-delà de la date de clôture de la consultation.

C’est une deuxième question : est-ce normal que des données soient communiquables que sur un laps de temps ? Même si effectivement l’appel d’offres est clôturé, le dossier de consultation pourrait continuer à être consultable.

Nous allons interroger la Cada sur le sujet, mais peut-être avez-vous des réponses ou interrogations sur le sujet.

Ici plus d’infos sur le caractère communiquable du DCE : https://www.cada.fr/administration/marches-publics

L’accès aux DCE est important pour le citoyen et celui-ci a été nécessaire à de multiples occasions pour défendre ou comprendre ou debattre sur les politiques des collectivités, particulièrement les délégations de service public.

La CADA ne fonctionne pas comme ça, seule une administration peut lui demander un conseil, pour le reste, il n’y a que les saisines qui sont prévues, après un refus implicite ou explicite (pas de réponse, ce qui est malheureusement courant).

Il faut donc commencer par demander à la collectivité de permettre un accès libre et surtout « aisément utilisable par une machine », ce qui n’est pas le cas de ce clicodrome (malheureusment habituel).

Pour avoir plus de chance que cette demande soit prise en compte par la collectivité, il faut retrouver des avis CADA portant sur ce sujet, qui font plus ou moins office de jurisprudence ou au moins permettent de connaitre la doctrine de la CADA. Cela sera aussi utile si la collectivité ne s’exécute pas (ce qui a de fortes chance d’arriver) et qu’il faille ensuite saisir la CADA qui apprécie quand on lui mâche le travail en lui rappelant ses avis passés.

Pour la durée de publication, je ne sais pas s’il y a des textes particuliers qui empêchent que celle-ci aille au-delà de la consultation. Il y a par contre sûrement des textes qui indiquent une période minimale et obligatoire de publication.

Vous avez sûrement plus de chances avec la nouvelle municipalité d’améliorer ça, mais elle ne maitrise pas l’outil d’Atexo, qui a sûrement été un peu pensé dans ce but (ou est très mal conçu).

PS: j’ai rapidement regardé si un scraping automatique était possible. Je pense que c’est possible, mais ce n’est pas trivial et nécessite plus de temps que je n’ai de disponible.

Voici par exemple la récupération des numéros de dossier dans la première page de liste:

curl 'https://mpe-marseille.local-trust.com/?page=entreprise.EntrepriseAdvancedSearch&AllCons' | grep btn-primary.*refConsultation.*orgAcronyme marches | sed 's!^.*refConsultation=!!;s!&org.*$!!'

1 « J'aime »

Merci Christian !

Je vais m’atteler à rechercher les avis cada, en recherche rapide, il semble ne pas y avoir pas grand chose sur la phase consultation des marchés publics.

Je regarde ton début de script.

Pour info, nous utilisons actuellement le flux rss pour être alerté de nouveaux ajouts dossiers, nous nous sommes aperçus que des marchés pouvaient être publiés physiquement alors que la date de début de publication officielle était bien antérieure du jour, si si… Ce qui fait que quelqu’un qui se connecte régulièrement sur la plateforme et trie sur la date de publication pour voir les derniers dossiers publiés peut en louper. C comme ça que sur 2020 nous avons loupé des dossiers de consultation. Le marché de maintenance des caméras (plusieurs dizaines de M€ sur 4 ans) est un exemple de cette bizarrerie.

Encore merci Christian

Il ne faut pas se focaliser sur les DCE dans les avis CADA mais tenter de trouver une doctrine générale.

Le point important ici est la récupération par une machine… car pour le reste, c’est accessible sans ouverture de compte, ni captcha ou autre, juste un problème de clicodrome peu automatisable.

En voici un utile: https://www.cada.fr/20171556

La commission rappelle, en second lieu, qu’aux termes de l’article L300-4 de ce code : « Toute mise à disposition effectuée sous forme électronique en application du présent livre se fait dans un standard ouvert, aisément réutilisable et exploitable par un système de traitement automatisé. »

Il s’agissait d’un accès au données en temps réel de fréquentation des bibliothèques d’une université. L’info était visible sous forme d’image sur le site, mais pas récupération aisément, sauf via une API non documentée.

Je pense qu’à partir du moment où l’on démontre que la récupération n’est pas aisément automatisable, on sort de l’esprit du L300-4.

Merci Christian !

Une approche très intéressante du sujet.

1 « J'aime »

Nous devrons donc solliciter la ville par la prada, (et par les élus Olivia et Christophe) en précisant les informations que tu mentionnes, et autres recherches.Nous pourrions le faire sur madada++ pour démultiplier les demandes sur les collectivités (région,métropole, département) et ensuite au TA pour médiatiser.

L’outil est très répandu, c’est toujours la même gestion de dossier. Nous prendrons contact avec la société également.

L’intérêt est peut être apres la consultation, d’imposer à une collectivité de procéder à un archivage accessible des dossiers de consultations.Et idéalement,d’y retrouver les avis d’attribution associés, que la ville de Marseille ne publie toujours pas à ce jour (mis à part les avis parfois sur le BOAMP qui arrivent sur le tard).

À suivre…

Merci Christian pour tes réponses.

Bonjour,
merci de cette initiative et merci à Christian pour ses conseils, ça me semble super utile d’arriver à mettre dans une archive publique les DCE des collectivités et des acheteurs publics en général.

Je ne sais pas si ça peut vous être utile, mais pour info:

  • j’ai mis en place en 2019 pour France Mobilités une archive ouverte des DCE dans le domaine transports ; comme les DCE n’étaient pas disponibles, le ministère a dû les payer à une des entreprises qui les moissonne pour vendre des services de réponse aux appels d’offre, ce n’est pas satisfaisant mais bon ça a permis de faire le job : https://documentsmarches.francemobilites.fr/Main
  • j’ai découvert à cette occasion better-place développé par Michel Blancard de l’association l’ouvre-boites qui moissonne les DCE de l’état ; j’ai décrit rapidement ça ici https://openmarches.frama.wiki/start
  • il me semble que la place de marchés de la métropole et de la ville de marseille utilisent le même produit que l’état, donc peut être est-ce jouable d’adapter le code scraper-place utilisé pour betterplace.info ; en revanche il me semble (c’était le cas en 2019 en tout cas) que la région paca et est difficile à scraper car il y a un captcha (j’ai habité jusque l’an dernier dans le 13, et avais regardé un peu forcément les appels d’offres des collectivités du dépt)

Cordialement,
Patrick

2 « J'aime »

Oui, PLACE utilisé l’outil d’ATEXO. Merci pour l’information ! Nous allons mettre en place un serveur et vous ferons le retour aussitôt.

Merci Patrick,

Si ça peut nous éviter des dizaines d’heures de travail citoyen, ce serait parfait. Nous nous focalisons sur la libération des données par les collectivités elle-mêmes.

L’emplacement des DCE de la ville de Marseille mis à jour à la main en attendant de : https://citoyen-ne-s-de-marseille.fr/wp-files/eg-transparency/?dir=VILLE/DCE

Bonjour à tous,

Comme l’a dit Patrick, j’ai fait un scrapeur pour la place de marché nationale (https://betterplace.info/) et je me suis évidemment demandé si la publication selon des modalités plus pratiques n’étaient pas prévues par le droit.

J’ai saisi la CADA en octobre 2018 et j’ai reçu l’avis en septembre 2020 bien qu’il soit daté de juin 2019 (un record de durée pour un avis CADA à ma connaissance). Il n’est toujours pas accessible publiquement donc je le retranscris ici :

Avis n° 20185014 du 27 juin 2019
Monsieur XXX a saisi la commission d’accès aux documents administratifs, par courrier enregistré à son secrétariat le 15 octobre 2018, à la suite du refus opposé par le ministre de l’économie et des finances à sa demande de publication des mises à jour quotidiennes des dossiers de consultation des entreprises.
La commission, qui a pris connaissance de la réponse du ministre de l’économie et des finances, relève que la demande ne porte pas sur le principe de la mise en ligne des documents administratifs sollicités mais sur les modalités de cette dernière.
Elle constate que ces modalités sont conformes aux dispositions de l’article L300-4 du code des relations entre le public et l’administration qui dispose que « Toute mise à disposition effectuée sous forme électronique en application du présent livre se fait dans un standard ouvert, aisément éutilisable et exploitable par un système de traitement automatisé.»
Elle rappelle que dans l’hypothèse où le document sollicité est effectivement disponible sous un format répondant aux exigences de l’article L300-4, le code n’impose pas à l’administration d’adopter un format ou des modalités différentes de celui qu’elle utilise déjà, pour satisfaire une demande de communication ou de mise en ligne.
La commission considère par suite que la demande n’est pas recevable.

Bref, je crois que le droit n’est pas pour moi dans cette affaire donc j’ai laissé tomber et je dois de temps en temps mettre à jour mon scrapeur quand le prestataire de la plateforme rajoute des breaking changes.

Par contre, si j’avais demandé la publication des DCE passés, qui sont actuellement rendus invisibles dès la fin de la procédure, là je pense que j’aurais eu un avis positif.

2 « J'aime »

Merci pour toutes ces informations. le sujet que nous allons creuser avec notre collectif est vraiment sur l’archivage accessible des DCE passés. d’autant plus qu’un DCE peut évoluer dans le temps de consultation avec les questions des candidats, et les réponses de la collectivité, voir l’ajout de nouvelles pièces. L’intérêt citoyen réside dans la consultation à postériori d’un dossier de consultation après l’échéance de réponse. Nous allons solliciter la ville de Marseille, et d’autres collectivités dans ce sens (en utilisant madada++ ;-)) pour faire évoluer la doctrine de la CADA comme le stipulait Christian. C’est un peu s’affronter à l’Everest, à suivre, on prépare la cordée…

Vaste sujet, que nous travaillons depuis 2019… les choix faits quant à la dématérialisation des marchés publics ont conduit paradoxalement à ne pas vraiment permettre d’avoir une approche orientée data au-delà des DECP, théoriquement obligatoirement publiées.

Sur le point précis des DCE au-delà des questions de communicabilité CADA l’enjeu majeur est aujourd’hui que certains éditeurs de profil acheteur estiment être propriétaires des données que les acheteurs y saisissent, et demandent aux acheteurs de payer pour récupérer leurs propres données. Il y a là un véritable scandale, l’Etat (la DAJ) devait écrire une doctrine sur le sujet, et ne l’a jamais fait.

Sur le plan plus général d’une vision data-driven de la commande publique, on a publié il y a quelques semaines ce livre blanc : https://datactivist.coop/scope-occitanie/ poke @Emeline.

1 « J'aime »

encore très intéressant, pour notre humble action, nous allons nous limiter dans un premier temps sur l’archivage et la communicabilité par la collectivité de cet archivage des DCE passés, en dehors de toute contrainte liée à leur plateforme de consultation dont le rôle est précisément de s’assurer que la phase de consultation se fasse en toute sécurité et légalité au regard de la commande publique. Mais nous comprenons très bien que la centralisation des données en phase consultation telle qu’elle est pensée dans le livre blanc apporterait de fait cette possibilité d’archivage. Bon …

bon ça donne ça :

# Importer les modules
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import NoSuchElementException

import time
import os
import csv

# permet de patienter jusqu'à la fin du téléchargement
def download_wait(path_to_downloads):
    seconds = 0
    dl_wait = True
    while dl_wait and seconds < 600: #max 10 minutes
        time.sleep(1)
        dl_wait = False
        if not os.path.exists(path_to_downloads):
            dl_wait = True
            seconds += 1
    return seconds

# Adresse de départ (première page de la liste des consultations)
url = "https://mpe-marseille.local-trust.com/?page=Entreprise.EntrepriseAdvancedSearch&AllCons"
emplacement_download = "C:\\Download\\Fichiers\\"

# Préparation des options Chrome
options = Options()
options.add_argument("--start-maximized")
options.add_argument("--disable-features=VizDisplayCompositor")
prefs = {"profile.default_content_settings.popups": 0,
         "download.default_directory": emplacement_download, 
         "directory_upgrade": True}
options.add_experimental_option("prefs", prefs)

# Démarrage de Chrome
browser = webdriver.Chrome(executable_path=r'C:\Download\Tools\chromedriver.exe', options=options)

# Ouverture de la fenêtre, nécessaire pour accéder aux boutons 
browser.maximize_window()

# Allez, ça part de là ...
browser.get(url)

# Préparation des listes
date_limite = list()
annee = list()
liens = list()
nomzip = list()
numdce = list()
intitule = list()
objet = list()

# Tant que c'est vrai , on fait :
while True:

    # On attend le chargement
    delay = 2

    # Chargement des liens d'ouverture de page vers chaque DCE
    liens_html = browser.find_elements_by_link_text('Accéder à la consultation')

    # Mise en tableau    
    for lien in liens_html:
        liens.append(lien.get_attribute("href"))
    
    # Tentons d'aller à la page suivante
    try:
        browser.find_element_by_id('ctl0_CONTENU_PAGE_resultSearch_PagerTop_ctl2').click()

        # On attend le chargement
        delay = 2
        
    except NoSuchElementException:
        # Ya pas ! on sort ...
        break

for t in liens:

    #Verif
    print(t)
    
    # Allez, c'est reparti ...
    browser.get(t)
    
    # On attend le chargement
    delay = 2

    # Click sur le lien "Dossier de consultation"
    element = browser.find_element_by_id('linkDownloadDce')
    browser.execute_script("arguments[0].click();", element)

    # On attend le chargement
    delay = 2

    # Click sur l'option Téléchargement en Anonyme 
    element = browser.find_element_by_css_selector("input[type='radio'][value='ctl0$CONTENU_PAGE$EntrepriseFormulaireDemande$choixAnonyme']")
    browser.execute_script("arguments[0].click();", element)

    # On attend le chargement
    delay = 1 

    # Click sur le bouton de commande Téléchargement 
    element = browser.find_element_by_css_selector("input[type='submit'][value='Valider']")
    browser.execute_script("arguments[0].click();", element)

    # On attend le chargement
    delay = 1 
    
    # On récupère le nom du fichier zip pour le renommer ensuite
    temp = browser.find_element_by_id("ctl0_CONTENU_PAGE_EntrepriseDownloadDce_nomDce").text
    i = temp.find(".zip") + 4
    nomzip.append(temp[0:i])

    # On attend le chargement
    delay = 2

    # On récupère le numéro de la consultation    
    numdce.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_reference").text)

    # On récupère l'intitulé de la consultation    
    intitule.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_intitule").text)

    # On récupère l'objet de la consultation    
    objet.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_objet").text)

    # On récupère la date limite de réponse
    date_limite.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_dateHeureLimiteRemisePlis").text)

    # On récupère l'année (selon date limite de réponse)
    annee.append(date_limite[-1][6:10])

    # Vérification que le fichier n'ait pas été déjà téléchargé
    if not(os.path.exists(emplacement_download+'\\'+nomzip[-1])) and not(os.path.exists(emplacement_download+annee[-1]+'\\'+numdce[-1]+' - '+nomzip[-1])):

        # Click sur le bouton de commande Téléchargement du Dossier complet
        element = browser.find_element_by_link_text("Télécharger le Dossier de consultation")
        browser.execute_script("arguments[0].click();", element)
        # Patiente jusqu' à ce que le fichier ait été téléchargé
        nbs = download_wait(emplacement_download+'\\'+nomzip[-1])
        

# Fermeture du Browser, il a bien travaillé
browser.close
browser.quit

# Renommage en règle
for l,n,nu,i,o,d,a in zip(liens, nomzip, numdce, intitule, objet, date_limite, annee):
    
    # Vérification que le fichier n'ait pas été renommé
    if not os.path.exists(emplacement_download+a+'\\'+nu+' - '+n):
        old_file = os.path.join(emplacement_download, f'{n}')
        new_file = os.path.join(emplacement_download+a+'\\', f'{nu} - {n}')
        os.rename(old_file, new_file)
        
        # Enregistrement dans le csv
        with open(emplacement_download+'_liste.csv', 'a+', newline='') as csvfile:
            f_csv = csv.writer(csvfile, delimiter=';', quoting=csv.QUOTE_ALL)
            f_csv.writerow([l,n,nu,i,o,d,a])

vais industrialiser sur département et métropole, mais à voir aussi pour la région qui est sur une autre plateforme. l’idée est de planifier le traitement : nommage des archives et mise à disposition d’un CSV qui se met à jour.

ça va nous faire un gagner un temps fou, on peut imaginer avoir les marchés de la Soleam, du SPLAIN, et autres babioles. à suivre.

je souhaiterai tagué les DCE selon thématique, et surtout déceler les rendus qui pourront être demandés.

1 « J'aime »

ça c’est pour le CD13

# Importer les modules
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import NoSuchElementException

import time
import os
import csv

# methode pour retrouver le nom du fichier téléchargé
def getDownLoadedFileName(waitTime):
    browser.execute_script("window.open()")
    # switch to new tab
    browser.switch_to.window(browser.window_handles[-1])
    # navigate to chrome downloads
    browser.get('chrome://downloads')
    # define the endTime
    endTime = time.time()+waitTime
    while True:
        try:
            # get downloaded percentage
            downloadPercentage = browser.execute_script(
                "return document.querySelector('downloads-manager').shadowRoot.querySelector('#downloadsList downloads-item').shadowRoot.querySelector('#progress').value")
            # check if downloadPercentage is 100 (otherwise the script will keep waiting)
            if downloadPercentage == 100:
                # return the file name once the download is completed
                return browser.execute_script("return document.querySelector('downloads-manager').shadowRoot.querySelector('#downloadsList downloads-item').shadowRoot.querySelector('div#content  #file-link').text")
        except:
            pass
        time.sleep(1)
        if time.time() > endTime:
            break

# permet de patienter jusqu'à la fin du téléchargement
def download_wait(path_to_downloads):
    seconds = 0
    dl_wait = True
    while dl_wait and seconds < 600: #max 10 minutes
        time.sleep(1)
        dl_wait = False
        if not os.path.exists(path_to_downloads):
            dl_wait = True
            seconds += 1
    return seconds

# Adresse de départ (première page de la liste des consultations)
url = "https://marches.departement13.fr/?page=entreprise.EntrepriseAdvancedSearch&AllCons"
emplacement_download = "C:\\Download\\Fichiers CD13\\"

# Préparation des options Chrome
options = Options()
options.add_argument("--start-maximized")
options.add_argument("--disable-features=VizDisplayCompositor")
prefs = {"profile.default_content_settings.popups": 0,
         "download.default_directory": emplacement_download, 
         "directory_upgrade": True}
options.add_experimental_option("prefs", prefs)

# Démarrage de Chrome
browser = webdriver.Chrome(executable_path=r'C:\Download\Tools\chromedriver.exe', options=options)

# Ouverture de la fenêtre, nécessaire pour accéder aux boutons 
browser.maximize_window()

# Allez, ça part de là ...
browser.get(url)

# Préparation des listes
date_limite = list()
annee = list()
liens = list()
nomzip = list()
numdce = list()
intitule = list()
objet = list()

# Tant que c'est vrai , on fait :
while True:

    # On attend le chargement
    delay = 2

    # Chargement des liens d'ouverture de page vers chaque DCE
    liens_html = browser.find_elements_by_link_text('Accéder à la consultation')

    # Mise en tableau
    for lien in liens_html:
        if lien.get_attribute("href")[0:96] == 'https://marches.departement13.fr/?page=entreprise.EntrepriseDetailsConsultation&refConsultation=':
            liens.append(lien.get_attribute("href"))
    
    # Tentons d'aller à la page suivante
    try:
        browser.find_element_by_id('ctl0_CONTENU_PAGE_resultSearch_PagerTop_ctl2').click()

        # On attend le chargement
        delay = 2
        
    except NoSuchElementException:
        # Ya pas ! on sort ...
        break

for t in liens:

    #Verif
    print(t)
    
    # Allez, c'est reparti ...
    browser.get(t)
    
    # On attend le chargement
    delay = 2

    # Click sur le lien "Dossier de consultation"
    element = browser.find_element_by_id('linkDownloadDce')
    browser.execute_script("arguments[0].click();", element)

    # On attend le chargement
    delay = 2

    # Click sur l'option Téléchargement en Anonyme 
    element = browser.find_element_by_css_selector("input[type='radio'][value='ctl0$CONTENU_PAGE$EntrepriseFormulaireDemande$choixAnonyme']")
    browser.execute_script("arguments[0].click();", element)

    # On attend le chargement
    delay = 1 

    # Click sur le bouton de commande Téléchargement 
    element = browser.find_element_by_css_selector("input[type='submit'][value='Valider']")
    browser.execute_script("arguments[0].click();", element)

    # On attend le chargement
    delay = 2

    # On récupère le numéro de la consultation    
    numdce.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_reference").text)

    # On récupère l'intitulé de la consultation    
    intitule.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_intitule").text)

    # On récupère l'objet de la consultation    
    objet.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_objet").text)

    # On récupère la date limite de réponse
    date_limite.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_dateHeureLimiteRemisePlis").text)

    # On récupère l'année (selon date limite de réponse)
    annee.append(date_limite[-1][6:10])

    # Vérification que le fichier n'ait pas été déjà téléchargé
    if not(os.path.exists(emplacement_download+annee[-1]+'\\'+numdce[-1]+'.zip')):

        # Click sur le bouton de commande Téléchargement du Dossier complet
        element = browser.find_element_by_link_text("Télécharger le Dossier de consultation")
        browser.execute_script("arguments[0].click();", element)
        # Patiente jusqu' à ce que le fichier ait été téléchargé
        nomfichier = ''+getDownLoadedFileName(60)
        print(nomfichier)
        # Renommage + Copie dans le bon dossier
        new_file = os.path.join(emplacement_download+annee[-1]+'\\'+numdce[-1]+'.zip')
        os.rename(emplacement_download+nomfichier, new_file)

# Fermeture du Browser, il a bien travaillé
browser.close
browser.quit

# Renommage en règle
for l,nu,i,o,d,a in zip(liens, numdce, intitule, objet, date_limite, annee):
    
    # Enregistrement dans le csv
    with open(emplacement_download+'_liste.csv', 'a+', newline='') as csvfile:
        f_csv = csv.writer(csvfile, delimiter=';', quoting=csv.QUOTE_ALL)
        f_csv.writerow([l,nu,i,o,d,a])


ça c’est pour la métropole

# Importer les modules
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import NoSuchElementException

import time
import os
import csv

# methode pour retrouver le nom du fichier téléchargé
def getDownLoadedFileName(waitTime):
    browser.execute_script("window.open()")
    # switch to new tab
    browser.switch_to.window(browser.window_handles[-1])
    # navigate to chrome downloads
    browser.get('chrome://downloads')
    # define the endTime
    endTime = time.time()+waitTime
    while True:
        try:
            # get downloaded percentage
            downloadPercentage = browser.execute_script(
                "return document.querySelector('downloads-manager').shadowRoot.querySelector('#downloadsList downloads-item').shadowRoot.querySelector('#progress').value")
            # check if downloadPercentage is 100 (otherwise the script will keep waiting)
            if downloadPercentage == 100:
                # return the file name once the download is completed
                return browser.execute_script("return document.querySelector('downloads-manager').shadowRoot.querySelector('#downloadsList downloads-item').shadowRoot.querySelector('div#content  #file-link').text")
        except:
            pass
        time.sleep(1)
        if time.time() > endTime:
            break

# permet de patienter jusqu'à la fin du téléchargement
def download_wait(path_to_downloads):
    seconds = 0
    dl_wait = True
    while dl_wait and seconds < 600: #max 10 minutes
        time.sleep(1)
        dl_wait = False
        if not os.path.exists(path_to_downloads):
            dl_wait = True
            seconds += 1
    return seconds

# Adresse de départ (première page de la liste des consultations)
url = "https://marchespublics.ampmetropole.fr/?page=entreprise.EntrepriseAdvancedSearch&AllCons"
emplacement_download = "C:\\Download\\Fichiers METROPOLE AIX MARSEILLE\\"

# Préparation des options Chrome
options = Options()
options.add_argument("--start-maximized")
options.add_argument("--disable-features=VizDisplayCompositor")
prefs = {"profile.default_content_settings.popups": 0,
         "download.default_directory": emplacement_download, 
         "directory_upgrade": True}
options.add_experimental_option("prefs", prefs)

# Démarrage de Chrome
browser = webdriver.Chrome(executable_path=r'C:\Download\Tools\chromedriver.exe', options=options)

# Ouverture de la fenêtre, nécessaire pour accéder aux boutons 
browser.maximize_window()

# Allez, ça part de là ...
browser.get(url)

# Préparation des listes
date_limite = list()
annee = list()
liens = list()
nomzip = list()
numdce = list()
intitule = list()
objet = list()

# Tant que c'est vrai , on fait :
while True:

    # On attend le chargement
    delay = 2

    # Chargement des liens d'ouverture de page vers chaque DCE
    liens_html = browser.find_elements_by_link_text('Accéder à la consultation')

    # Mise en tableau
    for lien in liens_html:
        if lien.get_attribute("href")[0:60] == 'https://marchespublics.ampmetropole.fr/app.php/consultation/':
            liens.append(lien.get_attribute("href"))
    
    # Tentons d'aller à la page suivante
    try:
        browser.find_element_by_id('ctl0_CONTENU_PAGE_resultSearch_PagerTop_ctl2').click()

        # On attend le chargement
        delay = 2
        
    except NoSuchElementException:
        # Ya pas ! on sort ...
        break

for t in liens:

    #Verif
    print(t)
    
    # Allez, c'est reparti ...
    browser.get(t)
    
    # On attend le chargement
    delay = 2

    # Click sur le lien "Dossier de consultation"
    element = browser.find_element_by_id('linkDownloadDce')
    browser.execute_script("arguments[0].click();", element)

    # On attend le chargement
    delay = 2

    # Click sur l'option Téléchargement en Anonyme 
    element = browser.find_element_by_css_selector("input[type='radio'][value='ctl0$CONTENU_PAGE$EntrepriseFormulaireDemande$choixAnonyme']")
    browser.execute_script("arguments[0].click();", element)

    # On attend le chargement
    delay = 1 

    # Click sur le bouton de commande Téléchargement 
    element = browser.find_element_by_css_selector("input[type='submit'][value='Valider']")
    browser.execute_script("arguments[0].click();", element)

    # On attend le chargement
    delay = 2

    # On récupère le numéro de la consultation    
    numdce.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_reference").text)

    # On récupère l'intitulé de la consultation    
    intitule.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_intitule").text)

    # On récupère l'objet de la consultation    
    objet.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_objet").text)

    # On récupère la date limite de réponse
    date_limite.append(browser.find_element_by_id("ctl0_CONTENU_PAGE_ctl7_dateHeureLimiteRemisePlis").text)

    # On récupère l'année (selon date limite de réponse)
    annee.append(date_limite[-1][6:10])

    # Vérification que le fichier n'ait pas été déjà téléchargé
    if not(os.path.exists(emplacement_download+annee[-1]+'\\'+numdce[-1]+'.zip')):

        # Click sur le bouton de commande Téléchargement du Dossier complet
        element = browser.find_element_by_link_text("Télécharger le Dossier de consultation")
        browser.execute_script("arguments[0].click();", element)
        # Patiente jusqu' à ce que le fichier ait été téléchargé
        nomfichier = ''+getDownLoadedFileName(60)
        print(nomfichier)
        # Renommage + Copie dans le bon dossier
        new_file = os.path.join(emplacement_download+annee[-1]+'\\'+numdce[-1]+'.zip')
        os.rename(emplacement_download+nomfichier, new_file)

# Fermeture du Browser, il a bien travaillé
browser.close
browser.quit

# Renommage en règle
for l,nu,i,o,d,a in zip(liens, numdce, intitule, objet, date_limite, annee):
    
    # Enregistrement dans le csv
    with open(emplacement_download+'_liste.csv', 'a+', newline='') as csvfile:
        f_csv = csv.writer(csvfile, delimiter=';', quoting=csv.QUOTE_ALL)
        f_csv.writerow([l,nu,i,o,d,a])