Introdução: o que você vai conseguir e para quem serve este guia

O Yandex Maps há muito deixou de ser só um navegador de rotas e virou o catálogo de empresas mais completo da Rússia. Nas fichas das organizações está tudo o que qualquer profissional de marketing, afiliado ou dono de negócio precisa: endereços exatos, telefones, sites, horários de funcionamento, nota e milhares de avaliações sinceras de clientes. O problema é que percorrer manualmente quinhentas fichas de clínicas odontológicas em uma cidade é impossível, e os serviços prontos ou são caros ou entregam dados desatualizados.

Neste guia vamos escrever juntos o seu próprio parser do Yandex Maps em Python. Sem programas pagos, sem servidores de terceiros. Ao final do tutorial você vai ter:

  • Um script funcional que coleta fichas de empresas para qualquer busca e cidade.
  • Uma planilha Excel com nomes, categorias, endereços, telefones, sites, nota e horário de funcionamento.
  • Uma planilha separada com avaliações: autor, nota, data, texto.
  • Entendimento de como trabalhar com cuidado, sem tomar captcha nem bloqueio, distribuindo a carga via proxies móveis.

Fronteira importante deste artigo: aqui tratamos especificamente das fichas de empresas dentro do Maps. Parsing da busca comum do Yandex, uso de navegadores antidetect e configuração básica de proxies móveis estão abordados em materiais paralelos do blog; aqui não os repetimos, apenas nos apoiamos neles.

Para quem é este guia

  • Profissionais de marketing e donos de negócio que precisam de uma base de concorrentes ou parceiros na região, com telefones e notas reais.
  • Afiliados que montam nichos offline e ofertas locais para analisar depois.
  • Desenvolvedores encarregados de fazer uma coleta de dados do Maps, mas sem vontade de começar do zero.
  • Analistas que estudam avaliações para medir a qualidade do serviço em um nicho.

O que você precisa saber antes

Nada complicado. Basta saber instalar programas, abrir o terminal e copiar texto. Experiência em programação não é obrigatória: todo o código está pronto, você só precisa trocar os valores pelos seus. Se você já rodou um script em Python alguma vez, vai ser moleza. Na seção para avançados vamos falar de assincronismo e respostas de rede, mas dá para pular.

Quanto tempo vai demandar

  • Preparar o ambiente: 30-40 minutos.
  • Escrever e depurar o parser passo a passo: 1,5-2 horas.
  • Primeira coleta completa de 300-500 fichas: 1-3 horas de tempo em segundo plano, enquanto você faz outras coisas.

No total, em meio expediente você ganha uma ferramenta que vai economizar semanas de trabalho manual daqui pra frente.

Preparação inicial: ferramentas, acessos e requisitos

Antes de escrever código, vamos reunir tudo o que é necessário. Pule esta seção apenas se você já tem o Python instalado e acesso a um proxy móvel.

Requisitos de sistema

  • Windows 10 ou 11, macOS 12 ou mais recente, ou qualquer Linux moderno.
  • Mínimo de 8 GB de RAM: vamos rodar um navegador Chromium de verdade.
  • 3-4 GB de espaço livre em disco para Python, navegador e resultados.
  • Internet estável. Velocidade não é crítica; o importante é não ter quedas.

O que precisa ser instalado

  1. Python 3.11 ou 3.12. Baixe o instalador no site oficial python.org. Na instalação no Windows, marque obrigatoriamente Add Python to PATH na parte de baixo da primeira janela do instalador, senão os comandos no terminal não vão funcionar.
  2. Editor de código. Visual Studio Code, PyCharm Community ou até o Notepad++ servem. Vamos usar o VS Code como referência, mas para as nossas tarefas não faz diferença.
  3. Bibliotecas Python: playwright para controlar o navegador, pandas e openpyxl para planilhas, requests para testar o proxy. Vamos instalá-las no primeiro passo.
  4. Navegador Chromium para o Playwright. Baixado por um comando separado, pesa cerca de 150 MB.

Acesso a um proxy móvel

Esse é o elemento-chave da seção "sem banimentos". O Yandex Maps é sensível à frequência de requisições de um mesmo endereço. Proxies móveis fornecem IPs de operadoras de celular reais, atrás dos quais milhares de usuários comuns estão conectados ao mesmo tempo, então o Yandex os trata com máxima boa vontade. No painel do mobileproxy.space, depois de comprar o proxy, você vai precisar de quatro valores:

  • Host (endereço do servidor) e porta para conexão HTTP.
  • Login e senha para autenticação.
  • Link de troca de IP: uma URL especial que, ao ser acessada, faz o proxy pegar um novo endereço junto à operadora. Copie em um arquivo separado, vai ser útil no quinto passo.

Dica: Escolha um proxy da mesma região, ou pelo menos do mesmo país, da cidade que você vai raspar. O Maps ajusta os resultados conforme a geolocalização, e um proxy de outro país pode exibir uma lista incompleta de organizações ou interface em outro idioma.

Backups e pasta de trabalho

Crie uma pasta no disco, por exemplo maps_parser. Dentro dela ficarão os scripts e os resultados. Vamos salvar os dados intermediários em arquivo depois de cada ficha, então, mesmo se o script travar na tricentésima organização, as duzentas e noventa e nove primeiras não vão se perder. Vale fazer cópia do arquivo de resultados antes de cada nova execução: só renomear o antigo adicionando a data.

Verificação: Abra o terminal (no Windows aperte Win+R, digite cmd e Enter) e rode python --version. Se aparecer uma linha parecida com Python 3.12.x, a preparação está pronta. Se der erro, reinstale o Python marcando Add to PATH.

Conceitos básicos: como o Maps funciona e o que é fazer parsing de fichas

Antes de escrever código, vamos alinhar os termos. São simples, mas sem eles não vai ficar claro por que fazemos as coisas de um jeito e não de outro.

Termos-chave em linguagem simples

  • Parsing (scraping) — coleta automática de informação das páginas de um site. O programa abre a página como um humano faria e extrai dela os trechos necessários.
  • Ficha da empresa — uma página separada no Maps com endereço no formato yandex.ru/maps/org/nome/identificador-numérico/. É ali que estão telefone, endereço, avaliações e os demais dados. A lista de empresas à esquerda é só uma vitrine, da qual pegamos apenas os links das fichas.
  • Seletor — o "endereço" do elemento dentro da página. Por exemplo, a classe business-contacts-view__address aponta para o bloco do endereço. O script encontra o texto certo pelos seletores.
  • Navegador headless — um navegador real controlado por um programa. Pode rodar com janela (você vê o que acontece) ou sem ela.
  • Playwright — biblioteca para controlar o navegador a partir do Python. Escolhemos ela porque o Maps é totalmente construído em JavaScript, e uma requisição simples por HTML retorna uma página vazia, sem dados.
  • Proxy móvel — um intermediário entre o seu computador e o site, com IP de rede celular. O site vê o endereço da operadora, não o seu.
  • Rotação de IP — troca periódica do endereço do proxy, para que a carga não se concentre em um único IP.
  • Captcha — página de verificação "Confirme que você não é um robô". Para nós é um sinal de que estamos correndo demais. Não vamos resolvê-la com serviços externos; apenas paramos, trocamos o IP e diminuímos o ritmo.

Princípios básicos do funcionamento de um parser do Yandex Maps

A lógica é simples e tem três fases. Primeiro obtemos a lista de links das fichas das empresas desejadas. Depois abrimos cada ficha, uma por uma, e extraímos os dados. No fim, jogamos tudo numa planilha. Entre essas ações fazemos pausas de duração aleatória e, de tempos em tempos, trocamos o IP pelo link de troca de endereço.

O que é importante entender sobre legalidade e ética

Atenção: Colete apenas dados públicos de organizações e use-os para análise. O telefone e o endereço de uma empresa não são dados pessoais, mas os nomes dos autores das avaliações podem ser considerados como tal pela LGPD russa (152-FZ). Não guarde esses dados sem necessidade e não use os telefones coletados para disparos em massa sem consentimento — isso viola a lei de publicidade e as regras do Yandex. Lembre também: os termos de uso do Yandex restringem a coleta automatizada, então mantenha a carga mínima e, para projetos comerciais com grandes volumes, considere a API oficial do Yandex para busca por organizações.

Passo 1: Configurando o ambiente e conectando o proxy móvel

Objetivo da etapa: instalar todas as bibliotecas, baixar o navegador e confirmar que as requisições passam pelo proxy móvel e não direto.

Instalação das bibliotecas

  1. Abra o terminal ou prompt de comando.
  2. Vá até a pasta de trabalho com o comando cd e o caminho da pasta. Por exemplo: cd C:\maps_parser no Windows ou cd ~/maps_parser no macOS e Linux.
  3. Crie um ambiente virtual, para que as bibliotecas não interfiram em outros projetos: python -m venv venv
  4. Ative-o. Windows: venv\Scripts\activate. macOS e Linux: source venv/bin/activate. No começo da linha do terminal vai aparecer (venv).
  5. Instale as bibliotecas em um único comando:
pip install playwright pandas openpyxl requests
playwright install chromium

O segundo comando vai baixar o Chromium. Isso leva de 2 a 5 minutos, dependendo da velocidade da internet. Espere até que o prompt de comando volte a aparecer no terminal.

Verificação do proxy

Crie no editor o arquivo check_proxy.py e cole o código, trocando LOGIN, SENHA, HOST e PORTA pelos valores do painel:

import requests

proxy = 'http://LOGIN:SENHA@HOST:PORTA'
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('Seu IP direto:', direct['ip'])
print('IP via proxy:', via_proxy['ip'])

Rode o arquivo com python check_proxy.py. Você deve ver dois endereços diferentes. Se os endereços forem iguais ou aparecer erro de conexão, o proxy não está funcionando e não faz sentido continuar.

Verificação da troca de IP

Abra o link de troca de IP do painel em um navegador comum. Normalmente ele retorna uma resposta curta de sucesso. Espere 15-30 segundos e rode de novo o check_proxy.py. O endereço via proxy deve ter mudado. Anote quantos segundos a troca leva de verdade: esse valor vamos usar no script do quinto passo.

Dica: Salve todas as configurações do proxy em um arquivo separado config.py com as variáveis PROXY_HOST, PROXY_PORT, PROXY_LOGIN, PROXY_PASSWORD e CHANGE_IP_URL. Assim, nos outros scripts basta escrever from config import * e não reescrever senha dez vezes.

Verificação: O comando playwright --version mostra o número da versão, o check_proxy.py exibe um IP de operadora celular diferente do seu IP de casa e, depois de acessar o link de troca, o endereço muda.

Problemas possíveis

  • Erro "pip não é um comando interno". O Python foi instalado sem adicionar ao PATH. Reinstale marcando a opção ou use py -m pip em vez de pip.
  • Erro 407 Proxy Authentication Required. Login ou senha errados. Confira se não há espaços a mais na cópia.
  • Timeout na requisição via proxy. Porta errada ou proxy trocando de IP naquele momento. Espere meio minuto e tente de novo.

Passo 2: Coletando os links das fichas das empresas

Objetivo da etapa: obter um arquivo links.json com a lista de endereços de fichas de todas as empresas da busca desejada na cidade desejada.

Aqui vamos só espiar uma vez a lista de resultados do Maps, para pegar dela os links. Os dados em si vamos tirar exclusivamente das fichas, então a lista serve apenas como um índice.

Como a lista de empresas está estruturada

Quando você digita no Maps uma busca tipo "clínica odontológica Kazan", aparece à esquerda um painel rolável com snippets. Cada snippet contém um link para a ficha. A lista carrega em pedaços conforme a rolagem, então o script vai girar a roda do mouse sobre o painel e coletar links após cada rolagem, até que novos parem de aparecer.

Escrevendo o script de coleta de links

Crie o arquivo 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 = 'clinica odontologica Kazan'
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'Rolagem {i+1}: {len(links)} 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('Total coletado:', len(links))
    browser.close()

Entendendo o que acontece

  1. A linha headless=False abre o navegador com janela. Na fase de depuração isso é obrigatório: você vai ver com os próprios olhos se o mapa carregou e se há captcha.
  2. page.mouse.move(350, 500) coloca o cursor no painel esquerdo. Sem isso, a roda do mouse mexe no mapa em vez da lista.
  3. O seletor a[href*="/maps/org/"] procura todos os links que contenham o trecho /maps/org/. Ele é mais estável do que classes específicas, que o Yandex muda a cada poucos meses.
  4. O contador stale interrompe o laço se quatro rolagens seguidas não trouxerem novos links. Isso significa que a lista terminou.
  5. Os links são limpos dos parâmetros após o ponto de interrogação, para que a mesma empresa não entre duas vezes no arquivo.

Rode o script: python collect_links.py. Vai abrir a janela do navegador, o mapa com os resultados vai carregar, o painel começa a rolar. No terminal vai correr o contador de links. Para uma cidade média, uma única busca normalmente coleta entre 100 e 400 fichas em 2-4 minutos.

Dica: O Maps raramente entrega mais de 500 resultados por busca. Se você precisa de todo um nicho numa metrópole, quebre a busca por regiões: "clínica odontológica região central Kazan", "clínica odontológica região norte Kazan". Junte os links de buscas diferentes num set, como no código acima, e as duplicatas desaparecem sozinhas.

Verificação: Apareceu o arquivo links.json na pasta, contendo uma lista de endereços no formato https://yandex.ru/maps/org/nome/1234567890/. Abra dois ou três endereços manualmente em um navegador comum e confirme que são fichas de empresas corretas.

Problemas possíveis

  • Zero links coletados. Provavelmente a página não terminou de carregar ou o cursor não estava sobre a lista. Aumente a primeira pausa para 10 segundos e verifique as coordenadas do mouse: o painel precisa estar sob o cursor.
  • A lista não rola, o mapa se move. Diminua ou aumente a coordenada X do mouse.move, olhando a largura do painel na sua janela.
  • Apareceu captcha logo de cara. Troque o IP pelo link, espere um minuto e rode de novo. Se repetir, veja outro canal de proxy.

Passo 3: Raspando a ficha da empresa — nome, endereço, telefone, site

Objetivo da etapa: escrever uma função que abre uma ficha e devolve um dicionário com os dados principais da empresa, e rodá-la por todos os links do links.json.

Como encontrar os seletores sozinho

O Yandex renomeia as classes do layout de tempos em tempos. Por isso é importante saber encontrá-las sozinho, em vez de confiar só no código pronto. Faz assim:

  1. Abra qualquer ficha de empresa no Chrome normal.
  2. Clique com o botão direito no telefone da empresa e escolha Inspecionar (ou aperte F12 e clique no elemento com a ferramenta de seleção).
  3. No painel que abriu você vai ver a tag destacada com o atributo class. Por exemplo, class="card-phones-view__phone-number". Esse é o seletor: no código ele se escreve com um ponto no começo.
  4. Repita para nome, endereço, site, nota e horário de funcionamento. Anote as classes num bloco de notas.

A classe pode ter várias palavras separadas por espaço. Pegue a primeira, a mais "falante", com underline duplo dentro. No momento em que este guia foi escrito, valem os seletores do código abaixo, mas confira antes de rodar.

Escrevendo a função de parsing de ficha

Crie o arquivo 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'Total {len(links)}, restam {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('Erro em', url, e)
            time.sleep(random.uniform(4, 9))
    browser.close()

O que é importante aqui

  1. O botão "Mostrar telefone". Em algumas fichas o número completo fica escondido atrás de um botão. Procuramos ele pela classe card-phones-view__more e clicamos se encontrarmos. Só depois coletamos os números.
  2. Gravação após cada ficha. A função f.flush() força os dados para o disco. Se o script quebrar, tudo que foi coletado ficará no orgs.csv.
  3. Retomada do trabalho. A função load_done lê os links já coletados, então, ao rodar de novo, o script continua de onde parou em vez de começar do zero.
  4. Pausas de 4-9 segundos entre fichas. Esse é o ritmo de uma pessoa atenta lendo as informações. Não diminua nos primeiros testes.

Rode python parse_cards.py e observe as primeiras cinco a dez fichas na janela do navegador. Você deve ver a página abrir, o telefone sendo revelado quando necessário, e o nome e o número aparecendo no terminal.

Atenção: Se cinco fichas seguidas vierem com nome vazio, pare o script imediatamente com Ctrl+C. Quase certeza de que o Yandex mudou o layout e os seletores caducaram. Encontre os novos segundo as instruções acima e atualize o código. Continuar coletando linhas vazias é inútil e só aumenta a carga no proxy.

Dica: Além do texto, vale guardar o próprio identificador da organização: é a última parte numérica da URL. Por ele é fácil cruzar bases entre coletas e acompanhar empresas fechadas. Adicione o campo 'org_id': url.rstrip('/').split('/')[-1] no dicionário.

Verificação: O arquivo orgs.csv abre no Excel, com as colunas name, address, phones preenchidas em pelo menos 90% das linhas. Os telefones aparecem no formato +7 (843) 000-00-00. A nota aparece como número com vírgula, por exemplo 4,7.

Problemas possíveis

  • Telefones vazios, embora existam no site. O botão "Mostrar telefone" tem outra classe. Encontre-a pelo F12 e substitua no código.
  • O cirílico no CSV aparece com caracteres estranhos. O Excel não reconheceu a codificação. Abra o arquivo pelo menu Dados, De texto/CSV e escolha UTF-8, ou espere o passo 6, onde convertemos os dados em xlsx.
  • O script trava numa ficha. Adicione o parâmetro timeout=45000 no page.goto, para que, após 45 segundos, dispare uma exceção e o laço siga adiante.

Passo 4: Coletando avaliações das fichas

Objetivo da etapa: para cada empresa, obter uma lista de avaliações com nota, data e texto, e salvá-las em um arquivo separado reviews.csv.

Onde ficam as avaliações

Cada ficha tem uma aba "Avaliações" com endereço no formato https://yandex.ru/maps/org/nome/identificador/reviews/. As avaliações ali carregam em blocos conforme a rolagem, igual à lista de empresas do segundo passo. Por padrão, ficam ordenadas por relevância, e as mais recentes podem ser obtidas alternando a ordenação para "Por novidade".

Escrevendo a função de coleta de avaliações

Crie o arquivo parse_reviews.py. A base é a mesma do passo anterior, então mostramos apenas a função e o laço:

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

No laço pelos links, chame parse_reviews em vez de parse_card e grave cada item da lista como uma linha separada no reviews.csv, com os campos org_url, author, date, stars, text. A lógica de retomada e de gravação após cada empresa continua a mesma.

Entendendo os detalhes

  1. Limite max_scrolls=15. Estabelecimentos populares têm duas ou três mil avaliações. Raramente é preciso coletar todas, e isso consome muito tempo e requisições. Quinze rolagens normalmente bastam para 100-150 avaliações mais recentes.
  2. Nota pelas estrelas. A pontuação da avaliação não vem escrita em texto, mas desenhada em estrelas. Contamos os elementos com o modificador _full, ou seja, as estrelas preenchidas.
  3. Avaliações longas. Parte dos textos fica recolhida e exige clicar em "Mais". Se precisar do texto completo, antes da coleta clique em todos os botões com a classe business-review-view__expand dentro da ficha.

Atenção: Os nomes dos autores das avaliações são dados de usuários, não de empresas. Se a sua tarefa é análise de sentimento ou busca de reclamações típicas, o campo author não lhe é necessário. Não o colete sem objetivo, assim você se livra de perguntas desnecessárias sobre a 152-FZ. Se ainda assim precisar do campo, mantenha o arquivo localmente e não o repasse a terceiros.

Dica: Faça a coleta de avaliações não de todas as empresas, mas de uma lista filtrada: por exemplo, só de nota abaixo de 4,0 ou só de concorrentes diretos. Assim você reduz o volume de requisições várias vezes, sem perder o valor dos dados.

Verificação: No reviews.csv, cada empresa tem entre 20 e 150 linhas, a coluna stars tem números de 1 a 5, e em text há texto em russo com sentido. A data aparece como "15 de janeiro" ou "3 de março de 2026".

Problemas possíveis

  • Zero avaliações encontradas. Confira se a URL termina em /reviews/ e se o painel de avaliações está sob o cursor durante a rolagem.
  • Todas as avaliações com stars igual a 0. A classe da estrela preenchida mudou. Encontre-a pelo F12, clicando na estrela.
  • Avaliações duplicadas. O painel não rolou até o fim e um bloco foi contado duas vezes. Remova as duplicatas no sexto passo pelo par author mais text.

Passo 5: Configurando rotação de IP e proteção contra bloqueios

Objetivo da etapa: ensinar o parser a se comportar como um usuário cuidadoso: trocar de IP conforme o cronograma, reconhecer captcha e reduzir o ritmo automaticamente, em vez de bater de frente com o bloqueio.

Por que os banimentos acontecem

O Yandex não proíbe a visualização de fichas, mas monitora anomalias: centenas de páginas por minuto de um mesmo endereço, intervalos iguais entre requisições, ausência de movimento do mouse, cookies vazios. Quando a desconfiança se acumula, aparece a página do SmartCaptcha e, com insistência, uma restrição temporária para o IP. Nossa estratégia não é "furar" a proteção, mas não dar motivo para ela ligar.

Três regras do parser tranquilo do Yandex Maps

  1. Ritmo humano. No máximo 8-12 fichas por minuto por IP. Pausas aleatórias, não fixas.
  2. Troca regular de IP. A cada 25-40 fichas ou a cada 10-15 minutos acessamos o link de troca de endereço. O proxy móvel obtém em segundos um novo IP da operadora, e o histórico de requisições "zera" do ponto de vista do site.
  3. Recuo imediato diante de captcha. Viu a verificação — não tentamos passar por ela, apenas fazemos uma pausa de 2-3 minutos, trocamos o IP e continuamos da mesma ficha em um novo contexto do navegador.

Adicionando a rotação no código

Insira no parse_cards.py e no parse_reviews.py as funções abaixo e chame-as no laço principal:

import requests

def change_ip():
    try:
        r = requests.get(CHANGE_IP_URL, timeout=30)
        print('Troca de IP:', r.status_code)
    except Exception as e:
        print('Falha ao trocar 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()

E o laço principal fica assim:

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('Captcha! Pausa e troca de 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))

O que é importante entender

  • Novo contexto junto com o novo IP. Fechando o contexto, zeramos cookies e armazenamento local. Trocar o endereço sem zerar os cookies não adianta: o site vai continuar te reconhecendo pela sessão.
  • A variável IP_CHANGE_WAIT no config.py é o tempo medido no primeiro passo, normalmente 15-30 segundos. Não dá para colocar menos: o navegador abriria a página pelo endereço antigo.
  • Tamanho de janela aleatório adiciona variedade à impressão digital do navegador. É uma medida leve, mas é de graça.
  • Proxies móveis com rotação por link aqui são mais práticos que qualquer outro: você não fica alternando entre dezenas de endereços na mão, só puxa uma URL.

Dica: Faça um log simples: registre em arquivo o horário, o número da ficha e o evento (sucesso, captcha, troca de IP). Depois de uma semana, o log vai mostrar com clareza em que ritmo não surge captcha nenhum, e você ajusta as pausas ao seu canal de proxy.

Verificação: Em uma hora de trabalho contínuo, o script coletou 400-600 fichas, com no máximo uma ou duas mensagens sobre captcha no terminal, e após cada uma delas a coleta continuou automaticamente. O IP via proxy mudou ao menos dez vezes (dá para ver pelas linhas "Troca de IP: 200").

Problemas possíveis

  • Captcha aparece a cada 10 fichas. Ritmo alto demais para o seu canal. Aumente as pausas para 8-15 segundos e troque o IP a cada 15 fichas.
  • Depois da troca de IP, as páginas não carregam. Aumente o IP_CHANGE_WAIT: a operadora ainda não liberou o novo endereço.
  • O link de troca de IP retorna erro de acessos frequentes. A maioria dos planos tem um intervalo mínimo entre trocas, geralmente de um a dois minutos. Não troque o IP com mais frequência.

Passo 6: Limpando os dados e salvando em Excel

Objetivo da etapa: transformar os CSVs brutos em planilhas Excel bem arrumadas, sem duplicatas, com telefones normalizados e nota numérica.

Escrevendo o script de limpeza

Crie o arquivo 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='Empresas', index=False)
    reviews.to_excel(w, sheet_name='Avaliacoes', index=False)
print('Empresas:', len(orgs), 'Avaliacoes:', len(reviews))

Na linha que extrai o número de avaliações, usa-se uma expressão regular com uma barra invertida e a letra d entre parênteses: ela puxa o primeiro número de um texto como "312 avaliações". Copie com atenção.

O que o script faz

  1. Remove repetições de empresas por URL.
  2. Padroniza todos os telefones no formato +7XXXXXXXXXX, tirando parênteses, espaços e hifens. Esse formato é bom para CRM e para cruzar bases.
  3. Troca vírgula por ponto na nota, para o Excel entender como número e permitir ordenação.
  4. Extrai a quantidade de avaliações do texto.
  5. Remove avaliações duplicadas e grava duas abas em um único arquivo xlsx.

Rode python clean_export.py e abra o yandex_maps_result.xlsx. Na primeira aba, as empresas estão ordenadas por nota, os telefones padronizados; na segunda, as avaliações vinculadas à empresa pela coluna org_url.

Dica: Adicione no script uma coluna "data da coleta" com a data atual. Depois de um mês, repita a coleta e compare as tabelas com a função merge do pandas: você vai ver novas empresas, pontos que fecharam e mudanças de nota dos concorrentes. Isso já é monitoramento de mercado completo.

Verificação: O arquivo xlsx abre sem avisos, o cirílico lê normalmente, a coluna rating ordena como número, na coluna phones não há parênteses nem espaços, e o número de linhas na aba "Empresas" confere com a quantidade de links únicos no links.json menos os erros.

Verificação do resultado: checklist do parser pronto

Passe pela lista. Se você respondeu "sim" em cada item, o parser do Yandex Maps está pronto para uso recorrente.

Checklist

  • check_proxy.py mostra um IP de operadora celular diferente do seu IP de casa.
  • collect_links.py coleta mais de 50 links por busca média e para sozinho.
  • parse_cards.py preenche nome, endereço e telefone em pelo menos 90% das fichas.
  • O botão "Mostrar telefone" é revelado automaticamente.
  • parse_reviews.py devolve avaliações com nota de 1 a 5.
  • Diante de captcha, o script faz pausa, troca o IP e continua sem a sua intervenção.
  • Após uma parada de emergência, rodar de novo continua de onde parou.
  • clean_export.py cria um xlsx com duas abas e telefones normalizados.

Como testar o fluxo completo

  1. Pegue uma busca pequena, com 30-60 empresas na cidade, por exemplo "oficina de pneus" em uma cidade pequena.
  2. Rode todos os scripts na ordem e cronometre o tempo. Para 50 fichas com avaliações, deve levar de 15 a 25 minutos.
  3. Escolha cinco empresas aleatórias da tabela e confira telefones e endereços manualmente contra as fichas.
  4. Pare o parse_cards.py no meio com Ctrl+C e rode de novo. Confirme que o contador "restam" diminuiu, sem ter zerado.

Indicadores de sucesso

  • Precisão dos dados na checagem manual: 100% de coincidência em telefones e endereços.
  • Proporção de nomes vazios: menos de 3%.
  • Frequência de captcha: no máximo uma a cada 200-300 fichas.
  • Velocidade: 400-600 fichas por hora em um canal de proxy, em ritmo seguro.

Erros típicos e como resolvê-los

Reunimos os problemas que quase todo mundo enfrenta ao escrever um parser do Yandex Maps pela primeira vez, com as soluções.

Campos vazios na maioria das fichas

Causa: O Yandex atualizou o layout, as classes dos elementos mudaram. Solução: abra a ficha no Chrome, aperte F12, encontre as novas classes e substitua na função parse_card. Mantenha os seletores num único dicionário no início do arquivo, para editar só um lugar.

Captcha aparece logo na primeira página

Causa: o IP do proxy já está "cansado" de atividade anterior, ou o navegador é iniciado com parâmetros suspeitos. Solução: troque o IP antes de começar, garanta que locale='ru-RU' esteja definido e não use o modo headless nos primeiros testes: ele emite mais sinais de automação.

A rolagem move o mapa em vez do painel

Causa: o cursor do mouse está fora do painel esquerdo. Solução: ajuste as coordenadas do page.mouse.move ao tamanho da sua janela. Com largura de 1400 pixels, o painel ocupa mais ou menos os primeiros 450 pixels na horizontal.

Sempre coletam as mesmas 20 empresas

Causa: o painel não rola até o fim, o contador stale dispara cedo demais. Solução: aumente a pausa após a rolagem para 3-4 segundos e o limite do stale para 6. Às vezes o Maps demora para carregar o próximo bloco.

Telefones aparecem no navegador, mas a tabela fica vazia

Causa: o número só aparece depois do clique e o script coleta antes de terminar, ou o botão tem outra classe. Solução: aumente a pausa após o clique para 2-3 segundos e confira a classe do botão.

Erro Target closed ou Browser has been closed

Causa: você fechou o contexto ao trocar de IP, mas continua usando o objeto page antigo. Solução: garanta que, após cada page.context.close(), a variável page seja reatribuída via new_page(browser), como no exemplo do passo 5.

Depois de trocar o IP, as páginas carregam para sempre

Causa: a operadora ainda não liberou o novo endereço, o proxy está em transição. Solução: aumente o IP_CHANGE_WAIT para 30-40 segundos e adicione timeout no page.goto, para que o travamento não bloqueie o laço.

Excel abre o CSV com caracteres estranhos

Causa: o Excel não reconhece UTF-8 sem o BOM. Solução: use o clean_export.py e trabalhe com o xlsx, ou, ao gravar CSV, defina encoding='utf-8-sig'.

Recursos adicionais para avançados

O parser básico já resolve 90% dos casos. Se quiser mais velocidade e dados, veja por onde evoluir.

Interceptar respostas de rede em vez de analisar o layout

O Maps carrega os dados das fichas em JSON em requisições separadas. O Playwright permite assinar esses eventos com page.on('response', handler). Dentro do handler, verifique se response.url contém o trecho /maps/api/ e salve response.json(). Vantagem do método: os dados são estruturados e não dependem das classes do layout. Desvantagem: os endereços internos mudam sem aviso, e analisar JSON aninhado é mais difícil do que ler texto da página. É bom combinar com o método principal: se a resposta JSON chegou, use-a, senão caia no parsing do HTML.

Trabalho paralelo com vários canais de proxy

Um proxy móvel, em ritmo seguro, entrega 400-600 fichas por hora. Se precisar mais rápido, compre dois ou três canais e rode um processo separado para cada um, dividindo o links.json em partes iguais. Não rode vários navegadores pelo mesmo canal: o ritmo somado no IP vai crescer e o captcha volta. Para orquestrar, um script simples com subprocess funciona, ou a biblioteca asyncio com async_playwright, em que cada worker recebe o seu contexto e o seu proxy no parâmetro proxy do new_context.

Monitoramento de mudanças

Salve os resultados de cada coleta em um arquivo separado com a data e compare-os por org_id. Identificadores que apareceram são novos jogadores no mercado, os que sumiram são pontos fechados, e mudanças em rating e reviews_count indicam a dinâmica da reputação. Configure execução a cada duas semanas pelo Agendador de Tarefas do Windows ou pelo cron, e você terá um mapa vivo do nicho.

Ampliando os campos

As fichas têm outros blocos úteis: lista de serviços com preços, links para redes sociais, a marcação "Organização verificada", quantidade de fotos, metrô mais próximo. Cada campo se adiciona pela mesma receita: achar a classe pelo F12 e acrescentar um grab ao dicionário. O bloco de preços é especialmente valioso para afiliados que avaliam o tíquete médio do nicho.

Análise das avaliações

Os textos coletados alimentam uma análise simples: conte a frequência das palavras entre avaliações de uma a duas estrelas e obtenha a lista das principais reclamações dos clientes contra os concorrentes. Para isso bastam pandas e Counter da biblioteca padrão. A versão avançada é passar os textos por um modelo de linguagem para classificar por tema: preço, qualidade, atendimento, espera.

A API oficial como alternativa

Para grandes projetos comerciais, considere a API do Yandex para busca por organizações. Ela devolve nome, endereço, telefone e categorias de forma estruturada, sem risco de bloqueio. Avaliações não tem, os limites são pagos, mas, para montar base de contatos, esse é o caminho mais limpo. Nesse caso, o parser de fichas continua sendo a ferramenta para avaliações e campos que a API não traz.

FAQ: perguntas frequentes sobre parsing do Yandex Maps

É legal coletar telefones de empresas do Yandex Maps?

Os dados de contato das empresas são publicados publicamente pelas próprias organizações e não são dados pessoais. A coleta para análise própria é admitida. Mas usar esses números para ligações e disparos em massa sem consentimento viola a lei de publicidade. Lembre-se também das restrições dos termos de uso do Yandex à coleta automatizada e mantenha a carga mínima.

Por que não dá para fazer só com requisições requests, sem navegador?

O Maps é desenhado inteiramente por código JavaScript. O servidor entrega um HTML quase vazio e os dados são carregados depois. O requests vai receber só a casca, sem telefones nem endereços. Por isso precisamos do Playwright com um Chromium de verdade.

É obrigatório usar proxy móvel? Dá para raspar com IP de casa?

Tecnicamente, as primeiras 50-100 fichas até saem. Mas aí aparece o captcha e o IP de casa fica horas sob restrição, e você não vai conseguir usar o Yandex normalmente. Proxies móveis resolvem o problema por duas vias: o endereço de operadora celular já inspira mais confiança, e a rotação por link distribui a carga entre endereços sem parar a coleta.

Quantas fichas dá para coletar por dia com um proxy?

No ritmo seguro de 8-12 fichas por minuto, com pausas e troca de IP a cada 30 fichas, cerca de 5-8 mil em 24 horas de trabalho contínuo. Com avaliações, o volume cai duas ou três vezes, porque cada página de reviews exige rolagem.

O que fazer se o Yandex mudar o layout e o parser quebrar?

É uma situação normal, acontece algumas vezes por ano. Abra a ficha, aperte F12, encontre as novas classes dos elementos e substitua no código. Leva de 10 a 15 minutos. Para facilitar, guarde todos os seletores num único dicionário no início do arquivo.

Como coletar empresas de toda uma região, não de uma cidade só?

Monte uma lista de localidades e rode o collect_links.py em laço, substituindo o nome na QUERY. Junte os links num único set. Para cidades grandes, quebre também por regiões, já que uma busca raramente entrega mais de 500 resultados.

Dá para rodar o parser em segundo plano, sem janela do navegador?

Sim, defina headless=True. Mas faça isso só depois de depurar os seletores e confirmar que o captcha não aparece. No modo sem janela é mais difícil notar um problema, e alguns sinais de automação ficam mais evidentes. Um meio-termo: headless=True mais um screenshot da página a cada erro, via page.screenshot(path='error.png').

Como saber que o script pegou captcha, se não estou olhando a tela?

A função is_captcha do passo 5 confere o endereço da página e a presença do bloco de verificação. Adicione o envio de uma notificação para você, por exemplo, gravação em arquivo de log ou mensagem em um mensageiro por bot, e você fica sabendo do problema na hora.

As avaliações coletadas são só as últimas cem. Como obter mais?

Aumente o max_scrolls no parse_reviews para 50-100. Lembre que cada rolagem é uma requisição adicional ao servidor, então, para empresas com milhares de avaliações, a coleta vai levar alguns minutos e exigir troca de IP mais frequente.

Por que este método é melhor que os serviços prontos de parsing?

Você controla totalmente os campos, o ritmo e o frescor dos dados, sem pagar por linha e sem depender da agenda de atualização de terceiros. Serviços prontos são convenientes para uma tarefa pontual de algumas centenas de fichas; já um parser próprio do Yandex Maps se paga já na segunda coleta.

Conclusão

Vamos resumir. Você instalou Python e Playwright, conectou o proxy móvel e verificou a troca de IP. Coletou os links das fichas de empresas por busca e cidade. Escreveu a função que abre cada ficha, revela o telefone escondido e pega nome, endereço, site, nota e horário de funcionamento. Acrescentou a coleta de avaliações com notas. Ensinou o parser a trocar de IP conforme o cronograma e a reagir corretamente ao captcha. Por fim, limpou os dados e obteve um Excel bem arrumado com duas abas.

O mais importante para guardar: a robustez do parser do Yandex Maps não se apoia em truques, mas em três coisas — ritmo humano, rotação regular de endereços via proxies móveis e prontidão para parar ao primeiro sinal. Um script que respeita o recurso funciona por meses sem intervenção.

O que fazer depois

  • Rode a primeira coleta completa do seu nicho e confira cinco a dez fichas manualmente.
  • Coloque a coleta em agenda a cada duas semanas e comece a acumular histórico de mudanças.
  • Experimente a interceptação de respostas JSON da seção para avançados, para depender menos do layout.
  • Se os volumes crescerem, adicione um segundo canal de proxy e paralelize o trabalho.

Para onde evoluir

O próximo passo lógico é a análise automática das avaliações coletadas e a integração da tabela com o seu CRM ou painel de anúncios. E se você trabalha com várias plataformas, a mesma abordagem com Playwright e proxies móveis se transporta para qualquer site com carregamento dinâmico. Os princípios são os mesmos, só mudam os seletores. Boas coletas e dados limpos!

Sobre o autor

Roman Melnikov

Roman Melnikov

Technical Writer and System Administrator

Experiência profissional: Technical writer and DevOps engineer with 9 years of experience. Created over 50 detailed guides on system configuration and administration. His instructions helped thousands of professionals successfully solve technical tasks. Popular author on Habr and YouTube.
Formação: Bauman Moscow State Technical University. Information Systems and Technologies
Especialização:
Technical Documentation DevOps System Administration Linux Docker and Kubernetes CI/CD Infrastructure Automation Cloud Technologies System Monitoring Bash and Python Scripting

Compartilhe este artigo: