Scrap du tableau de l'ordre des architectes

Bonjour à tous,

Dans le cadre d’une commande, je dois scraper la liste des architectes (sociétés et libéraux) dans le Morbihan.

Le site : https://annuaire.architectes.org/

Dans un premier temps, je pensais scraper l’ensemble des architectes de Bretagne avec un script Python, mais l’URL ne change pas lorsqu’on filtre par région.

Je suis bloqué et je voudrais savoir vers quelle solution me tourner.

1 « J'aime »

Hello,

Ton site fonctionne avec des requête POST, qui ne demandent pas de paramètre dans l’url pour afficher les résultats (à la différence du get).
Donc à chaque fois que tu sélectionne une région, ton ordi va envoyer une requête post à leur serveur avec ce qu’on appelle un ‹ payload ›, qui identifiera ce que tu as demandé.

Le payload dans la requête POST, c’est l’équivalent des paramètres url de la requête GET (pour faire simple).

Dans ton cas, quand je recherche une région (par ex la Bretagne), j’envoie un payload incluant la valeur ‹ Bretagne ›. En python ca donne :

import requests

url = 'https://annuaire.architectes.org/'
headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Accept-Language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7',
    'Cache-Control': 'max-age=0',
    'Content-Type': 'application/x-www-form-urlencoded',
    'DNT': '1',
    'Origin': 'https://annuaire.architectes.org',
    'Referer': 'https://annuaire.architectes.org/',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
}

payload = {
    'type': 'non_habilite',
    'posted': '1',
    'nom': '',
    'prenom': '',
    'cp': '',
    'ville': '',
    'code_region': '103', ## Code Région de la Bretagne
    'submit': 'Rechercher'
}

response = requests.post(url, headers=headers, data=payload)

print(response.text) ## html de la réponse, avec les prénoms/noms de tes archi

Il te suffit de parser la réponse html de cette requête pour sortir tous les architectes bretons.
Si on va plus loin (autant aller plus loin), tu vois dans le code source de la page les codes région de chacune des régions :

select id="edit-region--2" name="code_region" class="form-select">
<option value="">- Toutes les r&eacute;gions -</option>
<option value="101" >AUVERGNE-RHÔNE-ALPES</option>
<option value="102" >BOURGOGNE-FRANCHE-COMTÉ</option>
<option value="103" selected>BRETAGNE</option>
<option value="104" >CENTRE-VAL DE LOIRE</option>
<option value="105" >CORSE</option>
<option value="106" >GRAND EST</option>
<option value="107" >GUADELOUPE</option>
<option value="108" >GUYANE</option>
<option value="109" >HAUTS-DE-FRANCE</option>
<option value="110" >ILE-DE-FRANCE</option>
<option value="111" >MARTINIQUE</option>
<option value="112" >NORMANDIE</option>
<option value="113" >NOUVELLE-AQUITAINE</option>
<option value="114" >OCCITANIE</option>
<option value="115" >PAYS-DE-LA-LOIRE</option>
<option value="116" >PROVENCE-ALPES-COTE-D&#039;AZUR</option>
<option value="117" >REUNION-MAYOTTE</option>
</select>

Tu rajoutes une loop à ton premier code, pour faire chacune des région :

import requests

url = 'https://annuaire.architectes.org/'
headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Accept-Language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7',
    'Cache-Control': 'max-age=0',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Cookie': 'PHPSESSID=b6103ufhlp8o30ba1m1me6vfte', ## Pas forcément besoin
    'Origin': 'https://annuaire.architectes.org',
    'Referer': 'https://annuaire.architectes.org/',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
}

code_regions = [
    "101", "102", "103", "104", "105", "106", "107", "108", "109", 
    "110", "111", "112", "113", "114", "115", "116", "117"
]

for code_region in code_regions:
    data = {
        'type': 'non_habilite',
        'posted': '1',
        'nom': '',
        'prenom': '',
        'cp': '',
        'ville': '',
        'code_region': code_region,
        'submit': 'Rechercher'
    }
    
    response = requests.post(url, headers=headers, data=data)
    ## Rajouter ton code de Parsing

Dis moi si c’est compréhensible !

4 « J'aime »

Bonjour Scalon, merci pour ta réponse qui me permets de valider la première étape de mon scrapping, il faut maintenant que je récupère les éléments de la div class « détails top print », sachant que pour chaque archi il faut cliquer sur le nom de l’archi.

Encore une fois merci beaucoup pour ton aide,

Yes. Chaque architecte a un ID, avec lequel tu peux reconstruire une URL pour accéder à ses détails :

Ca ne rajoute pas beaucoup de code, mais ca rajoute pas mal de requêtes. Prends bien ton temps pour le code : utilise bien des time.sleep() à différents endroits pour ne pas surcharger leur serveur, et ne pas te faire bannir ton IP.

1 « J'aime »

Ok j’ai récupéré les ID et construit une requête, mais il ne trouve pas les infos il me renvoie un message « Pas de section ‹ details toprint › trouvée pour l’ID : 077896 » et ainsi de suite

import requests
from bs4 import BeautifulSoup
import time

# URL de base pour l'annuaire
url = 'https://annuaire.architectes.org/'

# Headers pour la requête
headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Accept-Language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7',
    'Cache-Control': 'max-age=0',
    'Content-Type': 'application/x-www-form-urlencoded',
    'DNT': '1',
    'Origin': 'https://annuaire.architectes.org',
    'Referer': 'https://annuaire.architectes.org/',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
}

# Payload pour la requête POST (Région Bretagne)
payload = {
    'type': 'non_habilite',
    'id': '',
    'posted': '1',
    'nom': '',
    'prenom': '',
    'cp': '',
    'ville': '',
    'code_region': '103',  # Code Région de la Bretagne
    'submit': 'Rechercher'
}

# Requête POST pour récupérer la liste des architectes
response = requests.post(url, headers=headers, data=payload)

# Vérification de la requête POST
if response.status_code == 200:
    soup = BeautifulSoup(response.text, 'html.parser')

    # Trouver les architectes avec la classe 'summary'
    architects = soup.find_all('div', class_='summary')

    # Liste pour stocker les IDs des architectes
    architect_ids = []

    # Parcourir les résultats et extraire les IDs
    for architect in architects:
        architect_id = architect.find('span', class_='id').text.strip()
        architect_ids.append(architect_id)
        print(f"ID trouvé: {architect_id}")

    # Boucle sur chaque ID pour récupérer les détails via GET
    for architect_id in architect_ids:
        # Construire l'URL pour chaque architecte
        detail_url = f"https://annuaire.architectes.org/architecte/{architect_id}/"

        # Requête GET pour récupérer les détails de chaque architecte
        detail_response = requests.get(detail_url, headers=headers)

        if detail_response.status_code == 200:
            detail_soup = BeautifulSoup(detail_response.text, 'html.parser')

            # Récupérer la section des détails avec la classe 'details toprint'
            details_section = detail_soup.find('div', class_='details toprint')

            if details_section:
                # Extraire les informations
                nom = details_section.find('div', class_='elt nom').find('span', class_='value').text.strip()
                prenom = details_section.find('div', class_='elt prenom').find('span', class_='value').text.strip()
                adresse = details_section.find('div', class_='elt adresse').find('span', class_='value').text.strip()
                telephone = details_section.find('div', class_='elt telephone').find('span', class_='value').text.strip()
                email = details_section.find('div', class_='elt email')
                email = email.find('a').get('href') if email else "Non fourni"
                diplome = details_section.find('div', class_='elt diplome').find('span', class_='value').text.strip()

                # Afficher les informations
                print(f"\nDétails pour l'ID {architect_id}:")
                print(f"Nom: {nom}")
                print(f"Prénom: {prenom}")
                print(f"Adresse: {adresse}")
                print(f"Téléphone: {telephone}")
                print(f"Email: {email}")
                print(f"Diplôme: {diplome}")
            else:
                print(f"Pas de section 'details toprint' trouvée pour l'ID {architect_id}")
        else:
            print(f"Erreur lors de la requête GET pour l'ID {architect_id}: {detail_response.status_code}")
        
        # Pause de 5 à 10 secondes entre les requêtes pour éviter d'être bloqué par le serveur
        time.sleep(5 + (time.time() % 5))

else:
    print(f"Erreur lors de la requête POST: {response.status_code}")

II fallait modifier un peu les headers que tu injectais dans la requête get pour les archi. Le code modifié :

import requests
from bs4 import BeautifulSoup
import time

# URL de base pour l'annuaire
url = "https://annuaire.architectes.org/"

# Headers pour la requête
headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
    "Cache-Control": "max-age=0",
    "Content-Type": "application/x-www-form-urlencoded",
    "DNT": "1",
    "Origin": "https://annuaire.architectes.org",
    "Referer": "https://annuaire.architectes.org/",
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
}

# Payload pour la requête POST (Région Bretagne)
payload = {
    "type": "non_habilite",
    "id": "",
    "posted": "1",
    "nom": "",
    "prenom": "",
    "cp": "",
    "ville": "",
    "code_region": "103",  # Code Région de la Bretagne
    "submit": "Rechercher",
}

# Requête POST pour récupérer la liste des architectes
response = requests.post(url, headers=headers, data=payload)

# Vérification de la requête POST
if response.status_code == 200:
    soup = BeautifulSoup(response.text, "html.parser")

    # Trouver les architectes avec la classe 'summary'
    architects = soup.find_all("div", class_="summary")

    # Liste pour stocker les IDs des architectes
    architect_ids = []

    # Parcourir les résultats et extraire les IDs
    for architect in architects:
        architect_id = architect.find("span", class_="id").text.strip()
        architect_ids.append(architect_id)
        print(f"ID trouvé: {architect_id}")

    # Boucle sur chaque ID pour récupérer les détails via GET
    for architect_id in architect_ids:
        # Construire l'URL pour chaque architecte
        detail_url = f"https://annuaire.architectes.org/architecte/{architect_id}/"

        headers2 = {
            "Accept": "text/html, */*; q=0.01",
            "Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
            "Cookie": "PHPSESSID=b6103ufhlp8o30ba1m1me6vfte",
            "DNT": "1",
            "Priority": "u=1, i",
            "Referer": "https://annuaire.architectes.org/",
            "Sec-CH-UA": '"Chromium";v="129", "Not=A?Brand";v="8"',
            "Sec-CH-UA-Mobile": "?0",
            "Sec-CH-UA-Platform": '"macOS"',
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-origin",
            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
            "X-Requested-With": "XMLHttpRequest",
        }

        detail_response = requests.get(detail_url, headers=headers2)
        print(detail_response.text)
        if detail_response.status_code == 200:
            detail_soup = BeautifulSoup(detail_response.text, "html.parser")

            # Récupérer la section des détails avec la classe 'details toprint'
            details_section = detail_soup.find("div", class_="details toprint")

            if details_section:
                # Extraire les informations
                nom = (
                    details_section.find("div", class_="elt nom")
                    .find("span", class_="value")
                    .text.strip()
                )
                prenom = (
                    details_section.find("div", class_="elt prenom")
                    .find("span", class_="value")
                    .text.strip()
                )
                adresse = (
                    details_section.find("div", class_="elt adresse")
                    .find("span", class_="value")
                    .text.strip()
                )
                telephone = (
                    details_section.find("div", class_="elt telephone")
                    .find("span", class_="value")
                    .text.strip()
                )
                email = details_section.find("div", class_="elt email")
                email = email.find("a").get("href") if email else "Non fourni"
                diplome = (
                    details_section.find("div", class_="elt diplome")
                    .find("span", class_="value")
                    .text.strip()
                )

                # Afficher les informations
                print(f"\nDétails pour l'ID {architect_id}:")
                print(f"Nom: {nom}")
                print(f"Prénom: {prenom}")
                print(f"Adresse: {adresse}")
                print(f"Téléphone: {telephone}")
                print(f"Email: {email}")
                print(f"Diplôme: {diplome}")
            else:
                print(
                    f"Pas de section 'details toprint' trouvée pour l'ID {architect_id}"
                )
        else:
            print(
                f"Erreur lors de la requête GET pour l'ID {architect_id}: {detail_response.status_code}"
            )

        # Pause de 5 à 10 secondes entre les requêtes pour éviter d'être bloqué par le serveur
        time.sleep(5 + (time.time() % 5))

else:
    print(f"Erreur lors de la requête POST: {response.status_code}")

1 « J'aime »

Super merci beaucoup !
Je découvre un nouveau monde j’ai l’impression !
Bonne journée

3 « J'aime »

bonjour, j’y connais absolument rien en code, est ce que tu peux récupérer les infos ( liste mails) des architectes de la région nouvelle aquitaine ?

1 « J'aime »

Hellos Comment allez-vous !! qui a toujours l’annuaire complets des (https://annuaire.architectes.org/) si c’est le cas j’ai besoins de cette liste moyennant une renumération

1 « J'aime »