Introduction : ce que vous allez obtenir et à qui ce guide s'adresse

Yandex Maps est depuis longtemps passé du simple navigateur au catalogue d'entreprises le plus complet de Russie. Les fiches d'entreprises contiennent tout ce dont un marketeur, un arbitragiste ou un propriétaire d'entreprise a besoin : adresses précises, téléphones, sites web, horaires d'ouverture, notes et des milliers d'avis clients authentiques. Le problème, c'est qu'il est impossible de parcourir manuellement cinq cents fiches de dentistes dans une seule ville, et les services clés en main sont soit chers, soit fournissent des données obsolètes.

Dans ce guide, nous allons écrire ensemble notre propre scraper Yandex Maps en Python. Pas de logiciels payants, pas de serveurs tiers. À la fin de ce guide, vous aurez :

  • Un script fonctionnel qui collecte les fiches d'entreprises pour n'importe quelle requête et ville.
  • Un tableau Excel avec noms, catégories, adresses, téléphones, sites web, notes et horaires d'ouverture.
  • Un tableau séparé avec les avis : auteur, note, date, texte.
  • Une compréhension de la manière de travailler proprement sans déclencher de captcha ni de blocage, en répartissant la charge via des proxys mobiles.

Frontière importante de cet article : nous nous concentrons sur les fiches d'entreprises dans Maps. Le scraping des résultats de recherche classiques de Yandex, l'utilisation de navigateurs anti-détection et la configuration de base des proxys mobiles sont traités dans d'autres articles du blog ; nous ne les reprenons pas ici, nous nous appuyons simplement dessus.

À qui s'adresse ce guide

  • Marketeurs et propriétaires d'entreprises qui ont besoin d'une base de concurrents ou de partenaires dans une région avec de vrais téléphones et notes.
  • Arbitragistes qui collectent des niches hors ligne et des offres locales pour analyse ultérieure.
  • Développeurs à qui l'on a confié la collecte de données depuis Maps et qui ne veulent pas partir de zéro.
  • Analystes qui étudient les avis pour évaluer la qualité de service dans une niche.

Ce qu'il faut savoir au préalable

Rien de compliqué. Il suffit de savoir installer des programmes, ouvrir une invite de commandes et copier du texte. Aucune expérience en programmation n'est requise : tout le code est prêt, il vous suffit de remplacer les valeurs par les vôtres. Si vous avez déjà exécuté un script Python, ce sera très facile. Dans la section pour les avancés, nous aborderons l'asynchrone et le traitement des réponses réseau, mais vous pouvez la sauter.

Combien de temps cela prendra

  • Préparation de l'environnement : 30-40 minutes.
  • Écriture et débogage du scraper étape par étape : 1,5-2 heures.
  • Première collecte complète de 300-500 fiches : 1-3 heures en arrière-plan pendant que vous faites autre chose.

Au total, en une demi-journée de travail, vous obtenez un outil qui vous fera ensuite gagner des semaines de travail manuel.

Préparation préalable : outils, accès et prérequis

Avant d'écrire du code, rassemblons tout le nécessaire. Ne sautez cette section que si Python est déjà installé et que vous avez accès à un proxy mobile.

Configuration requise

  • Windows 10 ou 11, macOS 12 ou plus récent, ou tout Linux moderne.
  • Au moins 8 Go de RAM : nous allons lancer un vrai navigateur Chromium.
  • 3-4 Go d'espace disque libre pour Python, le navigateur et les résultats.
  • Connexion Internet stable. La vitesse n'est pas critique, l'absence de coupures l'est.

Ce qu'il faut installer

  1. Python 3.11 ou 3.12. Téléchargez l'installateur sur le site officiel python.org. Sous Windows, cochez impérativement Add Python to PATH en bas de la première fenêtre d'installation, sinon les commandes dans la console ne fonctionneront pas.
  2. Éditeur de code. Visual Studio Code, PyCharm Community ou même Notepad++ feront l'affaire. Nous utiliserons VS Code, mais cela n'a pas d'importance pour nos tâches.
  3. Bibliothèques Python : playwright pour gérer le navigateur, pandas et openpyxl pour les tableaux, requests pour tester le proxy. Nous les installerons à la première étape.
  4. Navigateur Chromium pour Playwright. Se télécharge avec une commande séparée, pèse environ 150 Mo.

Accès au proxy mobile

C'est l'élément clé de la section « sans bannissement ». Yandex Maps est sensible à la fréquence des requêtes depuis une même adresse. Les proxys mobiles fournissent des adresses IP de vrais opérateurs cellulaires, derrière lesquels des milliers d'utilisateurs ordinaires sont connectés simultanément, ce qui fait que Yandex les traite avec une grande bienveillance. Dans votre espace client mobileproxy.space après l'achat du proxy, vous aurez besoin de quatre valeurs :

  • Hôte (adresse du serveur) et port pour la connexion HTTP.
  • Login et mot de passe pour l'authentification.
  • Lien de changement d'IP : une URL spéciale qui, lorsqu'elle est appelée, permet au proxy d'obtenir une nouvelle adresse auprès de l'opérateur. Copiez-la dans un fichier séparé, elle sera utile à la cinquième étape.

Conseil : Choisissez un proxy de la même région, ou au moins du même pays, que la ville que vous scrapez. Maps adapte les résultats à la géolocalisation, et un proxy d'un autre pays peut afficher une liste incomplète d'entreprises ou une interface dans une autre langue.

Sauvegardes et dossier de travail

Créez un dossier sur votre disque, par exemple maps_parser. Il contiendra les scripts et les résultats. Nous sauvegarderons toutes les données intermédiaires dans des fichiers après chaque fiche, donc même si le script plante à la trois centième organisation, les deux cent quatre-vingt-dix-neuf premières ne seront pas perdues. Il est judicieux de faire une copie du fichier de résultats avant chaque nouvelle exécution : renommez simplement l'ancien fichier en ajoutant la date.

Vérification : Ouvrez l'invite de commandes (sous Windows, appuyez sur Win+R, tapez cmd et Entrée) et tapez la commande python --version. Si vous voyez une ligne du type Python 3.12.x, la préparation est terminée. Si vous obtenez une erreur, réinstallez Python en cochant Add to PATH.

Concepts de base : comment fonctionne Maps et ce qu'est le scraping de fiches

Avant d'écrire du code, familiarisons-nous avec les termes. Ils sont simples, mais sans eux, il sera difficile de comprendre pourquoi nous procédons ainsi.

Termes clés en langage simple

  • Scraping (extraction) — collecte automatique d'informations sur les pages d'un site. Le programme ouvre la page comme le ferait un humain et en extrait les fragments nécessaires.
  • Fiche d'entreprise — une page distincte dans Maps avec une adresse du type yandex.ru/maps/org/nom/identifiant-numérique/. C'est là que se trouvent le téléphone, l'adresse, les avis et les autres données. La liste des entreprises à gauche n'est qu'une vitrine dont nous ne prenons que les liens vers les fiches.
  • Sélecteur — « l'adresse » d'un élément dans la page. Par exemple, la classe business-contacts-view__address indique le bloc contenant l'adresse. Grâce aux sélecteurs, le script trouve le texte voulu.
  • Navigateur headless — un vrai navigateur contrôlé par un programme. Il peut fonctionner avec une fenêtre (vous voyez ce qui se passe) ou sans.
  • Playwright — une bibliothèque pour contrôler un navigateur depuis Python. Nous l'avons choisie parce que Maps est entièrement construit en JavaScript, et une simple requête HTML renverrait une page vide sans données.
  • Proxy mobile — un intermédiaire entre votre ordinateur et le site, avec une adresse IP de réseau cellulaire. Le site voit non pas votre adresse, mais celle de l'opérateur.
  • Rotation d'IP — changement périodique de l'adresse du proxy pour que la charge ne se concentre pas sur une seule IP.
  • Captcha — page de vérification « Confirmez que vous n'êtes pas un robot ». Pour nous, c'est un signal que nous allons trop vite. Nous ne la résoudrons pas avec des services tiers, nous ferons simplement une pause, changerons d'IP et ralentirons le rythme.

Principes de base du scraper Yandex Maps

La logique est simple et se compose de trois phases. D'abord, nous obtenons une liste de liens vers les fiches des entreprises souhaitées. Ensuite, nous ouvrons chaque fiche une par une et extrayons les données. Enfin, nous mettons tout dans un tableau. Entre ces actions, nous faisons des pauses de durée aléatoire et, de temps en temps, changeons d'IP via le lien de changement d'adresse.

Ce qu'il faut comprendre sur la légalité et l'éthique

Attention : Ne collectez que des données d'entreprises publiquement accessibles et utilisez-les à des fins d'analyse. Le téléphone et l'adresse d'une entreprise ne sont pas des données personnelles, mais les noms des auteurs d'avis peuvent l'être selon la loi 152-FZ. Ne les stockez pas sans nécessité et n'utilisez pas les téléphones collectés pour des envois massifs sans consentement, cela viole la loi sur la publicité et les règles de Yandex. Rappelez-vous aussi : les conditions d'utilisation de Yandex limitent la collecte automatisée, donc gardez la charge minimale, et pour les projets commerciaux à gros volumes, envisagez l'API officielle de Yandex pour la recherche d'entreprises.

Étape 1 : Configurer l'environnement et connecter le proxy mobile

Objectif de cette étape : installer toutes les bibliothèques, télécharger le navigateur et s'assurer que les requêtes passent par le proxy mobile et non directement.

Installation des bibliothèques

  1. Ouvrez l'invite de commandes ou le terminal.
  2. Accédez au dossier de travail avec la commande cd et le chemin du dossier. Par exemple : cd C:\maps_parser sous Windows ou cd ~/maps_parser sous macOS et Linux.
  3. Créez un environnement virtuel pour que les bibliothèques ne perturbent pas d'autres projets : python -m venv venv
  4. Activez-le. Windows : venv\Scripts\activate. macOS et Linux : source venv/bin/activate. Le début de la ligne du terminal affichera (venv).
  5. Installez les bibliothèques en une seule commande :
pip install playwright pandas openpyxl requests
playwright install chromium

La deuxième commande téléchargera Chromium. Cela prend 2-5 minutes selon la vitesse d'Internet. Attendez que l'invite de commande réapparaisse dans le terminal.

Test du proxy

Créez dans l'éditeur un fichier check_proxy.py et collez le code en remplaçant LOGIN, MOT_DE_PASSE, HOTE et PORT par les valeurs de votre espace client :

import requests

proxy = 'http://ЛОГИН:ПАРОЛЬ@ХОСТ:ПОРТ'
proxies = {'http': proxy, 'https': proxy}

direct = requests.get('https://api.ipify.org?format=json', timeout=30).json()
via_proxy = requests.get('https://api.ipify.org?format=json', proxies=proxies, timeout=30).json()
print('Ваш прямой IP:', direct['ip'])
print('IP через прокси:', via_proxy['ip'])

Exécutez le fichier avec la commande python check_proxy.py. Vous devriez voir deux adresses différentes. Si les adresses sont identiques ou qu'une erreur de connexion apparaît, le proxy ne fonctionne pas et il est inutile de continuer.

Test du changement d'IP

Ouvrez le lien de changement d'IP de votre espace client dans un navigateur classique. Il renvoie généralement une courte réponse indiquant le succès du changement. Attendez 15-30 secondes et relancez check_proxy.py. L'adresse via le proxy doit avoir changé. Notez le nombre de secondes réellement nécessaires au changement : nous utiliserons cette valeur dans le script à la cinquième étape.

Conseil : Enregistrez tous les paramètres du proxy dans un fichier séparé config.py avec les variables PROXY_HOST, PROXY_PORT, PROXY_LOGIN, PROXY_PASSWORD et CHANGE_IP_URL. Ainsi, dans les autres scripts, il suffira d'écrire from config import * et de ne pas réécrire les mots de passe dix fois.

Vérification : La commande playwright --version affiche le numéro de version, check_proxy.py montre une IP d'opérateur cellulaire différente de votre IP domestique, et après avoir appelé le lien de changement, l'adresse change.

Problèmes possibles

  • Erreur « pip n'est pas reconnu comme commande interne ». Python a été installé sans être ajouté au PATH. Réinstallez en cochant la case ou utilisez la commande py -m pip au lieu de pip.
  • Erreur 407 Proxy Authentication Required. Login ou mot de passe incorrect. Vérifiez qu'il n'y a pas d'espaces superflus lors du copier-coller.
  • Timeout lors de la requête via le proxy. Le port est incorrect ou le proxy est en train de changer d'IP. Attendez une demi-minute et réessayez.

Étape 2 : Collecter les liens vers les fiches d'entreprises

Objectif de cette étape : obtenir un fichier links.json avec la liste des adresses des fiches de toutes les entreprises pour la requête et la ville souhaitées.

Ici, nous ne jetterons qu'un seul coup d'œil à la liste des résultats de Maps pour en extraire les liens. Les données elles-mêmes seront prises exclusivement dans les fiches, donc la liste nous sert de table des matières, rien de plus.

Comment est structurée la liste des entreprises

Lorsque vous saisissez une requête dans Maps comme « стоматология Казань », un panneau défilant avec des extraits apparaît à gauche. Chaque extrait contient un lien vers la fiche. La liste se charge par portions au fur et à mesure du défilement, donc le script fera défiler le panneau avec la molette de la souris et collectera les liens après chaque défilement, jusqu'à ce que de nouveaux liens cessent d'apparaître.

Écrivons le script de collecte des liens

Créez le fichier collect_links.py :

from playwright.sync_api import sync_playwright
import time, random, json
from config import *

PROXY = {'server': f'http://{PROXY_HOST}:{PROXY_PORT}', 'username': PROXY_LOGIN, 'password': PROXY_PASSWORD}
QUERY = 'стоматология Казань'
MAX_SCROLLS = 40

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, proxy=PROXY)
    context = browser.new_context(locale='ru-RU', viewport={'width': 1400, 'height': 900})
    page = context.new_page()
    page.goto('https://yandex.ru/maps/?text=' + QUERY, wait_until='domcontentloaded')
    time.sleep(random.uniform(5, 8))
    page.mouse.move(350, 500)
    links = set()
    stale = 0
    for i in range(MAX_SCROLLS):
        before = len(links)
        page.mouse.wheel(0, random.randint(1200, 2000))
        time.sleep(random.uniform(1.5, 3.5))
        for a in page.query_selector_all('a[href*="/maps/org/"]'):
            href = a.get_attribute('href') or ''
            clean = href.split('?')[0]
            if clean.startswith('/'):
                clean = 'https://yandex.ru' + clean
            links.add(clean)
        stale = stale + 1 if len(links) == before else 0
        print(f'Прокрутка {i+1}: ссылок {len(links)}')
        if stale >= 4:
            break
    with open('links.json', 'w', encoding='utf-8') as f:
        json.dump(sorted(links), f, ensure_ascii=False, indent=2)
    print('Итого собрано:', len(links))
    browser.close()

Analysons ce qui se passe

  1. La ligne headless=False ouvre le navigateur avec une fenêtre. En phase de débogage, c'est indispensable : vous verrez de vos propres yeux si la carte s'est chargée et s'il n'y a pas de captcha.
  2. page.mouse.move(350, 500) place le curseur sur le panneau de gauche. Sans cela, la molette de la souris déplacera la carte elle-même, pas la liste.
  3. Le sélecteur a[href*="/maps/org/"] cherche tous les liens contenant le fragment /maps/org/. Il est plus stable que les classes spécifiques que Yandex change tous les quelques mois.
  4. Le compteur stale arrête la boucle si quatre défilements consécutifs n'ont pas apporté de nouveaux liens. Cela signifie que la liste est terminée.
  5. Les liens sont nettoyés des paramètres après le point d'interrogation pour qu'une même organisation ne soit pas incluse deux fois dans le fichier.

Lancez le script : python collect_links.py. Une fenêtre de navigateur s'ouvrira, la carte avec les résultats se chargera, le panneau commencera à défiler. Dans le terminal, le compteur de liens défilera. Pour une ville moyenne, une seule requête collecte généralement de 100 à 400 fiches en 2-4 minutes.

Conseil : Maps renvoie rarement plus de 500 résultats pour une seule requête. Si vous avez besoin de toute la niche dans une grande ville, divisez la requête par quartiers : « стоматология Вахитовский район Казань », « стоматология Советский район Казань ». Combinez les liens de différentes requêtes via un set, comme dans le code ci-dessus, les doublons disparaîtront d'eux-mêmes.

Vérification : Le fichier links.json est apparu dans le dossier, contenant une liste d'adresses du type https://yandex.ru/maps/org/название/1234567890/. Ouvrez deux ou trois adresses manuellement dans un navigateur classique et assurez-vous qu'il s'agit bien des fiches des entreprises souhaitées.

Problèmes possibles

  • Zéro lien collecté. Probablement que la page n'a pas eu le temps de se charger ou que le curseur n'était pas sur la liste. Augmentez la première pause à 10 secondes et vérifiez les coordonnées de la souris : le panneau doit être sous le curseur.
  • La liste ne défile pas, c'est la carte qui bouge. Diminuez ou augmentez la coordonnée X dans mouse.move, en vous basant sur la largeur du panneau dans votre fenêtre.
  • Un captcha apparaît immédiatement. Changez d'IP via le lien, attendez une minute et relancez. Si cela se répète, essayez un autre canal proxy.

Étape 3 : Scraper la fiche d'entreprise — nom, adresse, téléphone, site

Objectif de cette étape : écrire une fonction qui ouvre une fiche et renvoie un dictionnaire avec les données principales de l'entreprise, puis la faire tourner sur tous les liens de links.json.

Comment trouver les sélecteurs soi-même

Yandex renomme périodiquement les classes dans le code. Il est donc important de savoir les trouver soi-même, plutôt que de se fier uniquement au code prêt. Voici comment faire :

  1. Ouvrez n'importe quelle fiche d'entreprise dans un navigateur Chrome classique.
  2. Faites un clic droit sur le téléphone de l'entreprise et choisissez Inspecter (ou appuyez sur F12 et cliquez sur l'élément avec l'outil de sélection).
  3. Dans le panneau qui s'ouvre, vous verrez la balise surlignée avec l'attribut class. Par exemple, class="card-phones-view__phone-number". C'est le sélecteur : dans le code, il s'écrit avec un point au début.
  4. Répétez pour le nom, l'adresse, le site, la note et les horaires. Notez les classes dans un bloc-notes.

Une classe peut être composée de plusieurs mots séparés par des espaces. Prenez le premier, le plus « parlant », avec un double underscore à l'intérieur. Au moment de la rédaction de ce guide, les sélecteurs du code ci-dessous sont à jour, mais vérifiez-les avant de lancer.

Écrivons la fonction de scraping de la fiche

Créez le fichier parse_cards.py :

from playwright.sync_api import sync_playwright
import time, random, json, csv, os
from config import *

PROXY = {'server': f'http://{PROXY_HOST}:{PROXY_PORT}', 'username': PROXY_LOGIN, 'password': PROXY_PASSWORD}
OUT = 'orgs.csv'
FIELDS = ['url', 'name', 'category', 'address', 'phones', 'site', 'rating', 'reviews_count', 'hours']

def grab(page, selector):
    el = page.query_selector(selector)
    return el.inner_text().strip() if el else ''

def parse_card(page, url):
    page.goto(url, wait_until='domcontentloaded')
    time.sleep(random.uniform(3, 6))
    more = page.query_selector('.card-phones-view__more')
    if more:
        more.click()
        time.sleep(random.uniform(1, 2))
    phones = [e.inner_text().strip() for e in page.query_selector_all('.card-phones-view__phone-number')]
    return {
        'url': url,
        'name': grab(page, 'h1.orgpage-header-view__header'),
        'category': grab(page, '.orgpage-categories-info-view'),
        'address': grab(page, '.business-contacts-view__address'),
        'phones': '; '.join(dict.fromkeys(phones)),
        'site': grab(page, '.business-urls-view__text'),
        'rating': grab(page, '.business-rating-badge-view__rating-text'),
        'reviews_count': grab(page, '.business-header-rating-view__text'),
        'hours': grab(page, '.business-working-status-view'),
    }

def load_done():
    if not os.path.exists(OUT):
        return set()
    with open(OUT, encoding='utf-8') as f:
        return {row['url'] for row in csv.DictReader(f)}

links = json.load(open('links.json', encoding='utf-8'))
done = load_done()
todo = [u for u in links if u not in done]
print(f'Всего {len(links)}, осталось {len(todo)}')

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, proxy=PROXY)
    page = browser.new_context(locale='ru-RU').new_page()
    new_file = not os.path.exists(OUT)
    with open(OUT, 'a', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        if new_file:
            writer.writeheader()
        for n, url in enumerate(todo, 1):
            try:
                row = parse_card(page, url)
                writer.writerow(row)
                f.flush()
                print(f'{n}/{len(todo)} {row["name"]} | {row["phones"]}')
            except Exception as e:
                print('Ошибка на', url, e)
            time.sleep(random.uniform(4, 9))
    browser.close()

Ce qui est important ici

  1. Le bouton « Afficher le téléphone ». Sur certaines fiches, le numéro complet est caché derrière un bouton. Nous le cherchons par la classe card-phones-view__more et cliquons dessus si nous le trouvons. C'est seulement après que nous collectons les numéros.
  2. Enregistrement après chaque fiche. La fonction f.flush() force l'écriture des données sur le disque. Si le script plante, tout ce qui a été collecté restera dans orgs.csv.
  3. Reprise du travail. La fonction load_done lit les liens déjà collectés, et lors d'une nouvelle exécution, le script reprend là où il s'était arrêté, sans recommencer depuis le début.
  4. Pauses de 4 à 9 secondes entre les fiches. C'est le rythme d'une personne attentive qui lit les informations. Ne le réduisez pas lors des premières exécutions.

Lancez python parse_cards.py et observez les cinq à dix premières fiches dans la fenêtre du navigateur. Vous devriez voir la page s'ouvrir, le téléphone se déplier si nécessaire, et le nom et le numéro apparaître dans le terminal.

Attention : Si cinq fiches consécutives renvoient des noms vides, arrêtez immédiatement le script avec Ctrl+C. Il est presque certain que Yandex a modifié la mise en page et que les sélecteurs sont obsolètes. Trouvez les nouveaux selon les instructions ci-dessus et mettez à jour le code. Continuer à collecter des lignes vides n'a aucun sens et ne fait qu'augmenter la charge sur le proxy.

Conseil : En plus du texte, il est utile de sauvegarder l'identifiant de l'organisation : c'est la dernière partie numérique de l'URL. Cela permet de comparer facilement les bases entre les collectes et de suivre les entreprises fermées. Ajoutez le champ 'org_id': url.rstrip('/').split('/')[-1] au dictionnaire.

Vérification : Le fichier orgs.csv s'ouvre dans Excel, les colonnes name, address, phones sont remplies pour au moins 90 % des lignes. Les téléphones s'affichent au format +7 (843) 000-00-00. La note ressemble à un nombre avec une virgule, par exemple 4,7.

Problèmes possibles

  • Les téléphones sont vides alors qu'ils sont sur le site. Le bouton « Afficher le téléphone » a une autre classe. Trouvez-la via F12 et remplacez-la dans le code.
  • Le cyrillique dans le CSV s'affiche en charabia. Excel n'a pas reconnu l'encodage. Ouvrez le fichier via le menu Données, À partir d'un texte/CSV et choisissez UTF-8, ou attendez l'étape 6 où nous convertissons les données en xlsx.
  • Le script se bloque sur une fiche. Ajoutez le paramètre timeout=45000 dans page.goto pour qu'une exception se déclenche au bout de 45 secondes et que la boucle continue.

Étape 4 : Collecter les avis depuis les fiches

Objectif de cette étape : pour chaque entreprise, obtenir une liste d'avis avec note, date et texte, et les enregistrer dans un fichier séparé reviews.csv.

Où se trouvent les avis

Chaque fiche a un onglet « Avis » avec une adresse du type https://yandex.ru/maps/org/название/identifiant/reviews/. Les avis s'y chargent par portions au défilement, comme la liste des entreprises à la deuxième étape. Par défaut, ils sont triés par pertinence, et les plus récents peuvent être obtenus en basculant le tri sur « Par nouveauté ».

Écrivons la fonction de collecte des avis

Créez le fichier parse_reviews.py. La base est la même que dans l'étape précédente, donc nous ne montrons que la fonction et la boucle :

def parse_reviews(page, url, max_scrolls=15):
    page.goto(url.rstrip('/') + '/reviews/', wait_until='domcontentloaded')
    time.sleep(random.uniform(3, 6))
    page.mouse.move(350, 600)
    seen = 0
    for _ in range(max_scrolls):
        page.mouse.wheel(0, random.randint(1500, 2500))
        time.sleep(random.uniform(1.5, 3))
        cards = page.query_selector_all('.business-review-view')
        if len(cards) == seen:
            break
        seen = len(cards)
    result = []
    for c in page.query_selector_all('.business-review-view'):
        def sub(sel):
            el = c.query_selector(sel)
            return el.inner_text().strip() if el else ''
        stars = c.query_selector_all('.business-rating-badge-view__star._full')
        result.append({
            'org_url': url,
            'author': sub('.business-review-view__author-name'),
            'date': sub('.business-review-view__date'),
            'stars': len(stars),
            'text': sub('.business-review-view__body-text'),
        })
    return result

Dans la boucle sur les liens, appelez parse_reviews au lieu de parse_card et écrivez chaque élément de la liste sur une ligne séparée dans reviews.csv avec les champs org_url, author, date, stars, text. La logique de reprise et d'enregistrement après chaque organisation reste la même.

Analysons les détails

  1. Limite max_scrolls=15. Les établissements populaires peuvent avoir deux ou trois mille avis. Les collecter tous est rarement nécessaire, et cela consomme beaucoup de temps et de requêtes. Quinze défilements suffisent généralement pour les 100-150 derniers avis.
  2. Note via les étoiles. Le nombre de points dans un avis n'est pas écrit en texte, mais dessiné en étoiles. Nous comptons les éléments avec le modificateur _full, c'est-à-dire les étoiles remplies.
  3. Avis longs. Certains textes sont repliés et nécessitent un clic sur « Plus ». Si vous avez besoin du texte complet, avant la collecte, cliquez sur tous les boutons avec la classe business-review-view__expand à l'intérieur de la fiche.

Attention : Les noms des auteurs d'avis sont des données d'utilisateurs, pas d'entreprises. Si votre tâche est l'analyse de sentiment ou la recherche de plaintes typiques, le champ author ne vous est pas nécessaire. Ne le collectez pas sans but, cela vous évitera des questions au titre de la loi 152-FZ. Si le champ est tout de même nécessaire, conservez le fichier en local et ne le transmettez pas à des tiers.

Conseil : Lancez la collecte d'avis non pas pour toutes les entreprises, mais pour une liste filtrée : par exemple, uniquement celles avec une note inférieure à 4,0 ou uniquement les concurrents directs. Cela réduira considérablement le volume de requêtes sans perdre la valeur des données.

Vérification : Dans reviews.csv, chaque organisation a entre 20 et 150 lignes, la colonne stars contient des nombres de 1 à 5, et text contient du texte russe significatif. La date ressemble à « 15 января » ou « 3 марта 2026 ».

Problèmes possibles

  • Zéro avis trouvé. Vérifiez que l'URL se termine par /reviews/ et que le panneau d'avis est sous le curseur lors du défilement.
  • Tous les avis ont stars égal à 0. La classe de l'étoile remplie a changé. Trouvez-la via F12 en cliquant sur une étoile.
  • Les avis sont dupliqués. Le panneau n'a pas défilé complètement et un bloc a été compté deux fois. Supprimez les doublons à la sixième étape par la paire author plus text.

Étape 5 : Configurer la rotation d'IP et la protection contre les blocages

Objectif de cette étape : apprendre au scraper à se comporter comme un utilisateur soigneux : changer d'IP selon un planning, reconnaître le captcha et ralentir automatiquement le rythme au lieu de se heurter à un blocage.

Pourquoi les bannissements surviennent-ils

Yandex n'interdit pas de consulter les fiches, mais surveille les anomalies : des centaines de pages par minute depuis une même adresse, des intervalles identiques entre les requêtes, l'absence de mouvements de souris, des cookies vides. Lorsque les soupçons s'accumulent, une page SmartCaptcha apparaît, et en cas d'insistance, une restriction temporaire pour l'IP. Notre stratégie n'est pas de « percer » la protection, mais de ne pas donner de raisons de l'activer.

Trois règles d'un scraper Yandex Maps serein

  1. Rythme humain. Pas plus de 8-12 fiches par minute par IP. Des pauses aléatoires, pas fixes.
  2. Changement d'IP régulier. Toutes les 25-40 fiches ou toutes les 10-15 minutes, nous appelons le lien de changement d'adresse. Le proxy mobile obtient en quelques secondes une nouvelle IP d'opérateur, et l'historique des requêtes est « remis à zéro » du point de vue du site.
  3. Repli immédiat en cas de captcha. Si vous voyez une vérification, n'essayez pas de la passer, mais faites une pause de 2-3 minutes, changez d'IP et reprenez à la même fiche dans un nouveau contexte de navigateur.

Ajoutons la rotation dans le code

Insérez dans parse_cards.py et parse_reviews.py les fonctions suivantes et appelez-les dans la boucle principale :

import requests

def change_ip():
    try:
        r = requests.get(CHANGE_IP_URL, timeout=30)
        print('Смена IP:', r.status_code)
    except Exception as e:
        print('Не удалось сменить IP:', e)
    time.sleep(IP_CHANGE_WAIT)

def is_captcha(page):
    if 'showcaptcha' in page.url or 'checkcaptcha' in page.url:
        return True
    return page.query_selector('.CheckboxCaptcha') is not None

def new_page(browser):
    ctx = browser.new_context(locale='ru-RU', viewport={'width': random.choice([1366, 1440, 1536]), 'height': 900})
    return ctx.new_page()

Et la boucle principale prend cette forme :

page = new_page(browser)
since_change = 0
for n, url in enumerate(todo, 1):
    if since_change >= random.randint(25, 40):
        page.context.close()
        change_ip()
        page = new_page(browser)
        since_change = 0
    row = parse_card(page, url)
    if is_captcha(page):
        print('Капча! Пауза и смена IP')
        page.context.close()
        time.sleep(random.uniform(120, 180))
        change_ip()
        page = new_page(browser)
        row = parse_card(page, url)
    writer.writerow(row)
    f.flush()
    since_change += 1
    time.sleep(random.uniform(4, 9))

Ce qu'il faut comprendre

  • Nouveau contexte avec nouvelle IP. En fermant le contexte, nous réinitialisons les cookies et le stockage local. Changer d'adresse sans réinitialiser les cookies est inutile : le site continuera à vous reconnaître par la session.
  • La variable IP_CHANGE_WAIT dans config.py est le temps mesuré à la première étape, généralement 15-30 secondes. On ne peut pas mettre moins : le navigateur ouvrirait la page via l'ancienne adresse.
  • La taille de fenêtre aléatoire ajoute de la diversité à l'empreinte du navigateur. C'est une mesure douce, mais elle est gratuite.
  • Les proxys mobiles avec rotation par lien sont ici plus pratiques que tout autre : vous ne passez pas manuellement entre des dizaines d'adresses, vous tirez simplement une URL.

Conseil : Tenez un journal simple : notez dans un fichier l'heure, le numéro de la fiche et l'événement (succès, captcha, changement d'IP). Au bout d'une semaine, le journal vous montrera exactement à quel rythme le captcha n'apparaît pas du tout, et vous ajusterez les pauses en fonction de votre canal proxy.

Vérification : En une heure de travail continu, le script a collecté 400-600 fiches, le terminal n'a pas affiché plus d'un ou deux messages de captcha, et après chacun d'eux, la collecte a repris automatiquement. L'IP via le proxy a changé au moins dix fois (cela se voit aux lignes « Смена IP: 200 »).

Problèmes possibles

  • Le captcha apparaît toutes les 10 fiches. Le rythme est trop élevé pour votre canal. Augmentez les pauses à 8-15 secondes et changez d'IP toutes les 15 fiches.
  • Après le changement d'IP, les pages ne se chargent pas. Augmentez IP_CHANGE_WAIT : l'opérateur n'a pas encore attribué une nouvelle adresse.
  • Le lien de changement d'IP renvoie une erreur de trop fréquentes requêtes. La plupart des forfaits ont un intervalle minimum entre les changements, généralement d'une à deux minutes. Ne changez pas d'IP plus souvent.

Étape 6 : Nettoyer les données et les enregistrer dans Excel

Objectif de cette étape : transformer les CSV bruts en tableaux Excel soignés, sans doublons, avec des téléphones normalisés et une note numérique.

Écrivons le script de nettoyage

Créez le fichier clean_export.py :

import pandas as pd

def norm_phone(value):
    out = []
    for raw in str(value).split(';'):
        digits = ''.join(ch for ch in raw if ch.isdigit())
        if len(digits) == 11 and digits[0] in '78':
            out.append('+7' + digits[1:])
        elif len(digits) == 10:
            out.append('+7' + digits)
    return '; '.join(dict.fromkeys(out))

orgs = pd.read_csv('orgs.csv', encoding='utf-8')
orgs = orgs.drop_duplicates(subset='url')
orgs['phones'] = orgs['phones'].fillna('').apply(norm_phone)
orgs['rating'] = pd.to_numeric(orgs['rating'].astype(str).str.replace(',', '.'), errors='coerce')
orgs['reviews_count'] = pd.to_numeric(orgs['reviews_count'].astype(str).str.extract('(\d+)')[0], errors='coerce')
orgs = orgs.sort_values('rating', ascending=False)

reviews = pd.read_csv('reviews.csv', encoding='utf-8')
reviews = reviews.drop_duplicates(subset=['org_url', 'author', 'text'])

with pd.ExcelWriter('yandex_maps_result.xlsx', engine='openpyxl') as w:
    orgs.to_excel(w, sheet_name='Организации', index=False)
    reviews.to_excel(w, sheet_name='Отзывы', index=False)
print('Организаций:', len(orgs), 'Отзывов:', len(reviews))

Dans la ligne d'extraction du nombre d'avis, une expression régulière avec une barre oblique inversée et la lettre d entre parenthèses est utilisée : elle extrait le premier nombre d'un texte comme « 312 отзывов ». Copiez-la attentivement.

Ce que fait le script

  1. Supprime les doublons d'organisations par URL.
  2. Met tous les téléphones au format +7XXXXXXXXXX, en enlevant les parenthèses, espaces et tirets. Ce format est pratique pour le CRM et la comparaison de bases.
  3. Remplace la virgule par un point dans la note pour qu'Excel la considère comme un nombre et permette le tri.
  4. Extrait le nombre d'avis à partir de la chaîne de texte.
  5. Supprime les avis en double et écrit deux feuilles dans un seul fichier xlsx.

Lancez python clean_export.py et ouvrez yandex_maps_result.xlsx. Sur la première feuille, les organisations sont triées par note, les téléphones sont uniformes, sur la deuxième feuille se trouvent les avis liés à l'organisation par la colonne org_url.

Conseil : Ajoutez au script une colonne « date de collecte » avec la date du jour. Dans un mois, refaites la collecte et comparez les tableaux avec la fonction merge de pandas : vous verrez les nouvelles organisations, les points fermés et les changements de note des concurrents. C'est déjà un véritable suivi de marché.

Vérification : Le fichier xlsx s'ouvre sans avertissement, le cyrillique est lisible, la colonne rating se trie comme un nombre, la colonne phones ne contient ni parenthèses ni espaces, le nombre de lignes de la feuille « Организации » correspond au nombre de liens uniques dans links.json moins les erreurs.

Vérification du résultat : checklist d'un scraper prêt

Passez en revue la liste. Si vous avez répondu « oui » à chaque point, le scraper Yandex Maps est prêt pour un travail régulier.

Checklist

  • check_proxy.py affiche une IP d'opérateur cellulaire différente de celle de votre domicile.
  • collect_links.py collecte plus de 50 liens pour une requête moyenne et s'arrête tout seul.
  • parse_cards.py remplit le nom, l'adresse et le téléphone pour au moins 90 % des fiches.
  • Le bouton « Afficher le téléphone » se déplie automatiquement.
  • parse_reviews.py renvoie des avis avec une note de 1 à 5.
  • En cas de captcha, le script fait une pause, change d'IP et continue sans votre intervention.
  • Après un arrêt d'urgence, une nouvelle exécution reprend là où elle s'était arrêtée.
  • clean_export.py crée un xlsx avec deux feuilles et des téléphones normalisés.

Comment tester de bout en bout

  1. Prenez une petite requête pour laquelle il y a 30-60 organisations dans la ville, par exemple « шиномонтаж » dans une ville moyenne.
  2. Exécutez tous les scripts dans l'ordre et chronométrez. Pour 50 fiches avec avis, cela devrait prendre 15-25 minutes.
  3. Choisissez cinq organisations au hasard dans le tableau et vérifiez les téléphones et adresses manuellement avec les fiches.
  4. Arrêtez parse_cards.py au milieu avec Ctrl+C et relancez-le. Vérifiez que le compteur « осталось » a diminué et ne s'est pas réinitialisé.

Indicateurs de réussite

  • Précision des données lors de la vérification manuelle : 100 % de correspondance sur les téléphones et adresses.
  • Part de noms vides : moins de 3 %.
  • Fréquence du captcha : pas plus d'un pour 200-300 fiches.
  • Vitesse : 400-600 fiches par heure sur un canal proxy à rythme sûr.

Erreurs typiques et leurs solutions

Nous avons rassemblé les problèmes auxquels presque tous ceux qui écrivent un scraper Yandex Maps pour la première fois sont confrontés, et les moyens de les résoudre.

Champs vides sur la plupart des fiches

Cause : Yandex a mis à jour la mise en page, les classes des éléments ont changé. Solution : ouvrez la fiche dans Chrome, appuyez sur F12, trouvez les nouvelles classes et remplacez-les dans la fonction parse_card. Gardez les sélecteurs dans un dictionnaire unique en début de fichier pour ne modifier qu'un seul endroit.

Le captcha apparaît dès la première page

Cause : l'IP du proxy est déjà « fatiguée » par une activité précédente ou le navigateur se lance avec des paramètres suspects. Solution : changez d'IP avant de commencer, assurez-vous que locale='ru-RU' est défini, et n'utilisez pas le mode headless lors des premières exécutions : il donne plus de signaux d'automatisation.

Le défilement de la liste déplace la carte, pas le panneau

Cause : le curseur de la souris est en dehors du panneau de gauche. Solution : ajustez les coordonnées de page.mouse.move à la taille de votre fenêtre. Avec une largeur de 1400 pixels, le panneau occupe environ les 450 premiers pixels horizontaux.

Les mêmes 20 organisations sont collectées

Cause : le panneau ne défile pas jusqu'au bout, le compteur stale se déclenche trop tôt. Solution : augmentez la pause après le défilement à 3-4 secondes et le seuil stale à 6, Maps charge parfois la portion suivante lentement.

Les téléphones sont visibles dans le navigateur, mais vides dans le tableau

Cause : le numéro n'apparaît qu'après le clic, et le script collecte les données avant la fin, ou le bouton a une autre classe. Solution : augmentez la pause après le clic à 2-3 secondes et vérifiez la classe du bouton.

Erreur Target closed ou Browser has been closed

Cause : vous avez fermé le contexte lors du changement d'IP, mais continuez à utiliser l'ancien objet page. Solution : assurez-vous qu'après chaque page.context.close(), la variable page est réassignée via new_page(browser), comme dans l'exemple de l'étape 5.

Après le changement d'IP, les pages se chargent indéfiniment

Cause : l'opérateur n'a pas encore attribué une nouvelle adresse, le proxy est en transition. Solution : augmentez IP_CHANGE_WAIT à 30-40 secondes et ajoutez un timeout dans page.goto pour que le blocage ne bloque pas la boucle.

Excel ouvre le CSV avec des charabias

Cause : Excel ne reconnaît pas l'UTF-8 sans BOM. Solution : utilisez clean_export.py et travaillez avec xlsx, ou lors de l'écriture du CSV, indiquez encoding='utf-8-sig'.

Fonctionnalités supplémentaires pour les avancés

Le scraper de base résout déjà 90 % des tâches. Si vous voulez plus de vitesse et de données, voici où évoluer.

Intercepter les réponses réseau au lieu d'analyser la mise en page

Maps charge les données des fiches au format JSON via des requêtes séparées. Playwright peut s'y abonner via page.on('response', handler). Dans le gestionnaire, vérifiez si response.url contient le fragment /maps/api/, et enregistrez response.json(). Avantage de la méthode : les données sont structurées et ne dépendent pas des classes de mise en page. Inconvénient : les adresses internes changent sans préavis, et analyser le JSON imbriqué est plus complexe que lire le texte de la page. L'approche se combine bien avec la principale : si la réponse JSON arrive — on la prend, sinon on se rabat sur l'analyse HTML.

Travail parallèle avec plusieurs canaux proxy

Un proxy mobile à rythme sûr donne 400-600 fiches par heure. Si vous avez besoin de plus, achetez deux ou trois canaux et lancez un processus séparé pour chacun, en divisant links.json en parts égales. Ne lancez pas plusieurs navigateurs via un même canal : le rythme total par IP augmentera et le captcha reviendra. Pour l'orchestration, un simple script de lancement avec subprocess ou la bibliothèque asyncio avec async_playwright fera l'affaire, où chaque worker reçoit son propre contexte et son propre proxy dans le paramètre proxy de new_context.

Suivi des changements

Enregistrez les résultats de chaque collecte dans un fichier séparé avec la date et comparez-les par org_id. Les identifiants apparus sont de nouveaux acteurs sur le marché, les disparus sont des fermetures, le changement de rating et reviews_count est la dynamique de la réputation. Configurez une exécution toutes les deux semaines via le Planificateur de tâches Windows ou cron, et vous aurez une carte vivante de la niche.

Extension des champs

Les fiches contiennent d'autres blocs utiles : liste des services avec prix, liens vers les réseaux sociaux, mention « Organisation vérifiée », nombre de photos, métro le plus proche. Chaque champ s'ajoute selon le même schéma : trouver la classe via F12, ajouter grab au dictionnaire. Le bloc des prix est particulièrement précieux pour les arbitragistes qui évaluent le panier moyen de la niche.

Analyse des avis

Les textes collectés se prêtent bien à une analyse simple : comptez la fréquence des mots parmi les avis avec une ou deux étoiles et obtenez la liste des principales plaintes des clients envers les concurrents. Pour cela, pandas et Counter de la bibliothèque standard suffisent. La variante avancée consiste à faire passer les textes par un modèle de langage pour les classer par thèmes : prix, qualité, service, attente.

L'API officielle comme alternative

Pour les grands projets commerciaux, envisagez l'API Yandex pour la recherche d'entreprises. Elle renvoie le nom, l'adresse, le téléphone et les catégories sous forme structurée et ne crée aucun risque de blocage. Elle ne contient pas d'avis, les limites sont payantes, mais pour collecter une base de contacts, c'est la voie la plus propre. Le scraper de fiches reste alors un outil pour les avis et les champs absents de l'API.

FAQ : questions fréquentes sur le scraping de Yandex Maps

Est-il légal de collecter les téléphones d'entreprises sur Yandex Maps ?

Les coordonnées des entreprises sont publiées publiquement par les organisations elles-mêmes et ne sont pas des données personnelles. La collecte pour votre propre analyse est autorisée. Mais l'utilisation de ces numéros pour des appels et envois massifs sans consentement viole la loi sur la publicité. Rappelez-vous aussi les restrictions des conditions d'utilisation de Yandex sur la collecte automatisée et gardez la charge minimale.

Pourquoi ne pas se contenter de requêtes requests classiques sans navigateur ?

Maps est entièrement dessiné par du code JavaScript. Le serveur renvoie un HTML presque vide, et les données se chargent ensuite. requests obtiendrait une coquille sans téléphones ni adresses. C'est pourquoi Playwright avec un vrai Chromium est nécessaire.

Est-il obligatoire d'utiliser des proxys mobiles, peut-on scraper depuis une IP domestique ?

Techniquement, les 50-100 premières fiches se collecteront ainsi. Mais ensuite, un captcha apparaîtra, et l'IP domestique sera soumise à des restrictions pendant plusieurs heures, vous ne pourrez plus utiliser Yandex normalement. Les proxys mobiles résolvent le problème de deux manières : l'adresse d'opérateur cellulaire inspire plus confiance dès le départ, et la rotation par lien permet de répartir la charge entre les adresses sans arrêter la collecte.

Combien de fiches peut-on collecter par jour avec un seul proxy ?

À rythme sûr de 8-12 fiches par minute avec pauses et changement d'IP toutes les 30 fiches — environ 5 000 à 8 000 par jour de travail continu. Avec les avis, le volume diminuera de deux à trois fois, car chaque page d'avis nécessite un défilement.

Que faire si Yandex change la mise en page et que le scraper casse ?

C'est une situation normale, cela arrive plusieurs fois par an. Ouvrez une fiche, appuyez sur F12, trouvez les nouvelles classes des éléments nécessaires et remplacez-les dans le code. Cela prend 10-15 minutes. Pour simplifier le processus, gardez tous les sélecteurs dans un dictionnaire unique en début de fichier.

Comment collecter les organisations de toute une région, pas d'une seule ville ?

Faites une liste de localités et lancez collect_links.py en boucle, en insérant le nom dans QUERY. Combinez les liens dans un seul set. Pour les grandes villes, divisez en plus par quartiers, car une seule requête renvoie rarement plus de 500 résultats.

Peut-on lancer le scraper en arrière-plan sans fenêtre de navigateur ?

Oui, mettez headless=True. Mais ne le faites qu'après avoir débogué les sélecteurs et vérifié que le captcha n'apparaît pas. En mode sans fenêtre, il est plus difficile de remarquer un problème, et certains signaux d'automatisation se manifestent plus fortement. Compromis : headless=True plus une capture d'écran de la page à chaque erreur via page.screenshot(path='error.png').

Comment savoir que le script a reçu un captcha si je ne regarde pas l'écran ?

La fonction is_captcha de l'étape 5 vérifie l'adresse de la page et la présence du bloc de vérification. Ajoutez l'envoi d'une notification, par exemple une écriture dans un fichier de log ou un message dans un messager via un bot, et vous serez informé du problème immédiatement.

Les avis collectés ne sont que les cent derniers. Comment en obtenir plus ?

Augmentez max_scrolls dans parse_reviews à 50-100. Notez que chaque défilement est une requête supplémentaire au serveur, donc pour les organisations avec des milliers d'avis, la collecte prendra plusieurs minutes et nécessitera un changement d'IP plus fréquent.

En quoi cette méthode est-elle meilleure que les services de scraping clés en main ?

Vous contrôlez entièrement les champs, le rythme et la fraîcheur des données, vous ne payez pas à la ligne et ne dépendez pas du calendrier de mise à jour d'un tiers. Les services clés en main sont pratiques pour une tâche ponctuelle de quelques centaines de fiches, mais votre propre scraper Yandex Maps est rentabilisé dès la deuxième collecte.

Conclusion

Résumons. Vous avez installé Python et Playwright, connecté un proxy mobile et vérifié le changement d'IP. Vous avez collecté les liens vers les fiches d'entreprises par requête et ville. Vous avez écrit une fonction qui ouvre chaque fiche, déplie le téléphone caché et récupère le nom, l'adresse, le site, la note et les horaires. Vous avez ajouté la collecte des avis avec notes. Vous avez appris au scraper à changer d'IP selon un planning et à réagir correctement au captcha. Enfin, vous avez nettoyé les données et obtenu un Excel soigné avec deux feuilles.

L'essentiel à retenir : la robustesse du scraper Yandex Maps ne repose pas sur des astuces, mais sur trois choses — un rythme humain, une rotation régulière des adresses via des proxys mobiles et la volonté de s'arrêter au premier signal. Un script qui respecte la ressource fonctionne des mois sans intervention.

Que faire ensuite

  • Lancez la première collecte complète pour votre niche et vérifiez cinq à dix fiches manuellement.
  • Planifiez la collecte toutes les deux semaines et commencez à accumuler l'historique des changements.
  • Essayez l'interception des réponses JSON de la section avancée pour moins dépendre de la mise en page.
  • Si les volumes augmentent, ajoutez un deuxième canal proxy et parallélisez le travail.

Où évoluer

L'étape logique suivante est l'analyse automatique des avis collectés et la liaison du tableau avec votre CRM ou votre régie publicitaire. Et si vous travaillez avec plusieurs plateformes, la même approche avec Playwright et des proxys mobiles se transpose à tout site à chargement dynamique. Les principes sont identiques, seuls les sélecteurs changent. Bonnes collectes et données propres !