Raspe Avaliações do Google Maps com Python: Guia 2025
Se você quer raspar avaliações do Google Maps com Python, está lidando com um dos alvos de scraping mais complicados da web. O Google carrega avaliações dinamicamente, altera sua estrutura HTML e detecta ativamente bots. Este guia cobre dois métodos funcionais — Playwright e Selenium — com código completo, técnicas de anti-detecção e notas honestas sobre o que quebra e por quê.
Sem enrolação. Apenas código que funciona.
O Que É Raspar Avaliações do Google?
Raspar avaliações do Google é a extração automatizada de dados de avaliações de clientes de listagens do Google Maps e Google Business. Em vez de copiar avaliações manualmente, um script visita páginas de negócios e puxa os dados para você.
Cada avaliação contém campos úteis:
- Avaliação em estrelas (1–5)
- Texto da avaliação
- Nome do avaliador
- Data da postagem
- Resposta do negócio (se houver)
Esses dados têm valor real. Monitoramento de reputação, análise competitiva, rastreamento de sentimentos, qualificação de leads — tudo começa com dados brutos de avaliações.
Por Que Não Usar a API Oficial do Google?
A API Google Places fornece avaliações, mas com limites rigorosos. Você obtém no máximo 5 avaliações por negócio. Sem dados históricos. Sem avaliações de concorrentes. Os preços aumentam rapidamente uma vez que você excede o nível gratuito.
O web scraping dá acesso a todas as avaliações públicas, sem um limite artificial. O trade-off: você precisa lidar com os sistemas anti-bot do Google por conta própria.
Por Que Python Para Esta Tarefa?
Python tem o melhor ecossistema para automação de navegadores e extração de dados. Três bibliotecas fazem a maior parte do trabalho pesado:
- Playwright — moderno, rápido, pronto para async, com recursos de stealth embutidos
- Selenium — testado em batalha, enorme comunidade, máxima compatibilidade
- BeautifulSoup — parsing leve de HTML uma vez que você tenha o conteúdo bruto
As avaliações do Google carregam via JavaScript. Scrapers estáticos (requests + BeautifulSoup sozinhos) não funcionarão aqui. Você precisa de um navegador real que execute JS, role a página e clique em botões — exatamente o que o Playwright e o Selenium fazem.
O Desafio Principal: Por Que o Google Reage
Antes de escrever uma única linha de código, entenda com o que você está lidando.
Carregamento Dinâmico de Conteúdo
O Google não serve todas as avaliações no HTML inicial. O primeiro carregamento da página mostra de 10 a 20 avaliações. Mais carregam à medida que você rola. Cada lote aciona solicitações de rede separadas. Seu scraper deve simular a rolagem para acionar esses carregamentos.
Camadas de Detecção de Bots
O Google executa vários sistemas de detecção simultaneamente:
- Fingerprinting do navegador — resolução de tela, fontes, fuso horário, idioma
- Análise comportamental — padrões de movimento do mouse, velocidade de rolagem, tempo de clique
- Reconhecimento de padrão de solicitação — frequência de solicitações não humanas
- Reputação de IP — sinalização de IPs que enviam muitas solicitações
Acertar qualquer um desses gatilhos e você verá CAPTCHAs, resultados vazios ou um bloqueio total.
Estrutura HTML em Constante Mudança
O Google atualiza seu frontend regularmente. Um seletor CSS que funciona hoje pode retornar zero resultados na próxima semana. Scrapers robustos usam múltiplos seletores de fallback para cada campo.
Método 1: Playwright (Recomendado para 2025)
Playwright é o melhor ponto de partida para novos projetos. É 2–3x mais rápido que o Selenium, tem suporte async embutido e lida com anti-detecção com menos configuração manual.
Configuração
python -m venv google_scraper_env
source google_scraper_env/bin/activate # Windows: google_scraper_env\Scripts\activate
pip install playwright pandas emoji beautifulsoup4 lxml
playwright install chromium
Scraper Completo do Playwright
from playwright.sync_api import sync_playwright
import pandas as pd
import re
import emoji
import logging
import time
import random
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class GoogleReviewsScraper:
def __init__(self, headless=True):
self.headless = headless
self.reviews_data = []
def clean_text(self, text):
text = emoji.replace_emoji(text, replace='')
text = re.sub(r'\s+', ' ', text).strip()
return text
def random_delay(self, min_delay=1, max_delay=3):
time.sleep(random.uniform(min_delay, max_delay))
def initialize_browser(self):
playwright = sync_playwright().start()
browser = playwright.chromium.launch(
headless=self.headless,
args=[
'--disable-blink-features=AutomationControlled',
'--disable-extensions',
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu'
]
)
context = browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
viewport={'width': 1366, 'height': 768}
)
page = context.new_page()
page.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
"""
)
return playwright, browser, page
def search_business(self, page, business_name):
try:
page.goto("https://www.google.com/maps", wait_until="networkidle")
self.random_delay(2, 4)
search_box = page.locator("input[id='searchboxinput']")
search_box.fill(business_name)
search_box.press("Enter")
page.wait_for_timeout(5000)
logger.info(f"Searched for: {business_name}")
return True
except Exception as e:
logger.error(f"Error searching: {e}")
return False
def navigate_to_reviews(self, page):
try:
reviews_tab = page.get_by_role("tab", name=re.compile("Reviews|reviews", re.IGNORECASE))
if reviews_tab.is_visible():
reviews_tab.click()
page.wait_for_timeout(3000)
logger.info("Navigated to reviews section")
return True
logger.warning("Reviews tab not found")
return False
except Exception as e:
logger.error(f"Error navigating to reviews: {e}")
return False
def scroll_and_load_reviews(self, page, max_reviews=100):
loaded_reviews = 0
scroll_attempts = 0
max_scroll_attempts = 20
while loaded_reviews < max_reviews and scroll_attempts < max_scroll_attempts:
try:
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
self.random_delay(2, 4)
current_reviews = page.locator('[data-review-id]').count()
if current_reviews > loaded_reviews:
loaded_reviews = current_reviews
logger.info(f"Loaded {loaded_reviews} reviews...")
scroll_attempts = 0
else:
scroll_attempts += 1
try:
more_button = page.locator("button", has_text=re.compile("more|More", re.IGNORECASE))
if more_button.is_visible():
more_button.click()
self.random_delay(2, 3)
except:
pass
except Exception as e:
logger.error(f"Error during scrolling: {e}")
break
logger.info(f"Total reviews found: {loaded_reviews}")
return loaded_reviews
def extract_review_data(self, page):
reviews = []
try:
review_elements = page.locator('[data-review-id]').all()
for element in review_elements:
try:
review_data = {}
name_element = element.locator('div[class*="name"] span, div[class*="Name"] span').first
review_data['reviewer_name'] = name_element.inner_text() if name_element.is_visible() else "Anonymous"
rating_element = element.locator('[role="img"][aria-label*="star"]').first
if rating_element.is_visible():
rating_text = rating_element.get_attribute('aria-label')
rating_match = re.search(r'(\d+)', rating_text)
review_data['rating'] = int(rating_match.group(1)) if rating_match else None
text_elements = element.locator('span[class*="review-text"], div[class*="review-text"]').all()
review_text = ""
for text_elem in text_elements:
if text_elem.is_visible():
review_text += text_elem.inner_text() + " "
review_data['review_text'] = self.clean_text(review_text.strip())
date_element = element.locator('span[class*="date"], div[class*="date"]').first
review_data['review_date'] = date_element.inner_text() if date_element.is_visible() else "Unknown"
if review_data['review_text']:
reviews.append(review_data)
except Exception as e:
logger.warning(f"Error on individual review: {e}")
continue
logger.info(f"Extracted {len(reviews)} reviews")
return reviews
except Exception as e:
logger.error(f"Extraction error: {e}")
return []
def scrape_reviews(self, business_name, max_reviews=100):
playwright, browser, page = self.initialize_browser()
try:
if not self.search_business(page, business_name):
return []
if not self.navigate_to_reviews(page):
return []
self.scroll_and_load_reviews(page, max_reviews)
reviews = self.extract_review_data(page)
self.reviews_data = reviews
return reviews
except Exception as e:
logger.error(f"Scraping failed: {e}")
return []
finally:
browser.close()
playwright.stop()
def save_to_csv(self, filename="google_reviews.csv"):
if self.reviews_data:
df = pd.DataFrame(self.reviews_data)
df.to_csv(filename, index=False, encoding='utf-8')
logger.info(f"Saved to {filename}")
else:
logger.warning("No reviews to save")
if __name__ == "__main__":
scraper = GoogleReviewsScraper(headless=False)
business_name = "Starbucks Times Square New York"
reviews = scraper.scrape_reviews(business_name, max_reviews=50)
if reviews:
scraper.save_to_csv(f"reviews_{business_name.replace(' ', '_')}.csv")
print(f"Scraped {len(reviews)} reviews.")
else:
print("No reviews scraped.")
O Que Este Código Faz
- Bandeiras de stealth escondem a impressão digital de automação da camada de detecção do Google
- Atrasos aleatórios entre 1–4 segundos imitam o ritmo de navegação humano
- Loop de rolagem continua carregando até atingir
max_reviewsou ficar sem conteúdo - Múltiplos seletores de fallback lidam com as frequentes mudanças de HTML do Google
- Exportação CSV fornece um arquivo limpo pronto para análise ou importação em qualquer ferramenta
Método 2: Selenium (Alternativa Confiável)
Selenium tem sido o padrão para automação de navegadores por mais de uma década. É mais lento que o Playwright, mas tem uma comunidade maior e mais documentação.
Quando Escolher Selenium
- Você está trabalhando com infraestrutura legada que já o utiliza
- Você precisa de máxima compatibilidade entre versões de navegadores
- Sua equipe já tem experiência com Selenium
Configuração
pip install selenium pandas
Você também precisará do ChromeDriver correspondente à sua versão do Chrome. O Selenium moderno (4.6+) gerencia automaticamente o driver.
Scraper Completo do Selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import pandas as pd
import time
import random
import re
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class SeleniumGoogleReviewsScraper:
def __init__(self, headless=True):
self.headless = headless
self.driver = None
self.wait = None
self.reviews_data = []
def setup_driver(self):
options = Options()
if self.headless:
options.add_argument("--headless")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--window-size=1366,768")
options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
self.driver = webdriver.Chrome(options=options)
self.driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined,});")
self.wait = WebDriverWait(self.driver, 20)
logger.info("Driver initialized")
def random_delay(self, min_s=1, max_s=3):
time.sleep(random.uniform(min_s, max_s))
def search_google_maps(self, business_name):
try:
self.driver.get("https://www.google.com/maps")
self.random_delay(2, 4)
search_box = self.wait.until(EC.presence_of_element_located((By.ID, "searchboxinput")))
search_box.clear()
for char in business_name:
search_box.send_keys(char)
time.sleep(random.uniform(0.05, 0.15))
search_box.submit()
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "[data-value='Reviews']")))
logger.info(f"Searched for: {business_name}")
return True
except TimeoutException:
logger.error("Timeout on search")
return False
def click_reviews_tab(self):
try:
reviews_tab = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "[data-value='Reviews']")))
self.driver.execute_script("arguments[0].scrollIntoView(true);")
self.random_delay(1, 2)
reviews_tab.click()
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "[data-review-id]")))
logger.info("Reviews tab clicked")
return True
except TimeoutException:
logger.error("Reviews tab not found")
return False
def scroll_to_load_reviews(self, target_reviews=100):
last_height = self.driver.execute_script("return document.body.scrollHeight")
reviews_loaded = 0
scroll_attempts = 0
while reviews_loaded < target_reviews and scroll_attempts < 30:
self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
self.random_delay(2, 4)
try:
show_more = self.driver.find_element(By.XPATH, "//button[contains(text(), 'more') or contains(text(), 'More')]\")
if show_more.is_displayed():
ActionChains(self.driver).move_to_element(show_more).click().perform()
self.random_delay(2, 3)
except NoSuchElementException:
pass
current_count = len(self.driver.find_elements(By.CSS_SELECTOR, "[data-review-id]"))
if current_count > reviews_loaded:
reviews_loaded = current_count
logger.info(f"Loaded {reviews_loaded} reviews...")
scroll_attempts = 0
else:
scroll_attempts += 1
new_height = self.driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
scroll_attempts += 1
last_height = new_height
return reviews_loaded
def extract_reviews(self):
reviews = []
review_elements = self.driver.find_elements(By.CSS_SELECTOR, "[data-review-id]")
for element in review_elements:
try:
review_data = {}
try:
review_data['reviewer_name'] = element.find_element(By.CSS_SELECTOR, "div[class*='name'] span").text.strip()
except NoSuchElementException:
review_data['reviewer_name'] = "Anonymous"
try:
aria_label = element.find_element(By.CSS_SELECTOR, "[role='img'][aria-label*='star']").get_attribute('aria-label')
match = re.search(r'(\d+)', aria_label)
review_data['rating'] = int(match.group(1)) if match else None
except NoSuchElementException:
review_data['rating'] = None
try:
text_elems = element.find_elements(By.CSS_SELECTOR, "span[class*='review-text']")
review_data['review_text'] = " ".join([e.text for e in text_elems if e.text]).strip()
except NoSuchElementException:
review_data['review_text'] = ""
try:
review_data['review_date'] = element.find_element(By.CSS_SELECTOR, "span[class*='date']").text.strip()
except NoSuchElementException:
review_data['review_date'] = "Unknown"
if review_data['review_text']:
reviews.append(review_data)
except Exception as e:
logger.warning(f"Review extraction error: {e}")
continue
logger.info(f"Extracted {len(reviews)} reviews")
return reviews
def scrape_business_reviews(self, business_name, max_reviews=100):
try:
self.setup_driver()
if not self.search_google_maps(business_name):
return []
if not self.click_reviews_tab():
return []
self.scroll_to_load_reviews(max_reviews)
reviews = self.extract_reviews()
self.reviews_data = reviews
return reviews
except Exception as e:
logger.error(f"Scraping failed: {e}")
return []
finally:
if self.driver:
self.driver.quit()
def save_to_csv(self, filename="selenium_reviews.csv"):
if self.reviews_data:
pd.DataFrame(self.reviews_data).to_csv(filename, index=False, encoding='utf-8')
logger.info(f"Saved to {filename}")
if __name__ == "__main__":
scraper = SeleniumGoogleReviewsScraper(headless=False)
reviews = scraper.scrape_business_reviews("McDonald's Times Square", max_reviews=75)
if reviews:
scraper.save_to_csv("mcdonalds_times_square_reviews.csv")
print(f"Scraped {len(reviews)} reviews.")
Técnicas de Anti-Detecção Que Realmente Funcionam
Ambos os scrapers acima incluem stealth básico. Aqui está o que adicionar quando você precisa ir mais longe.
Rotação de Proxy
Raspar com um único IP é bloqueado rapidamente. Rode proxies para distribuir solicitações:
import random
PROXY_LIST = [
"http://user:pass@proxy1:port",
"http://user:pass@proxy2:port",
"http://user:pass@proxy3:port",
]
def get_random_proxy():
return random.choice(PROXY_LIST)
# No Playwright:
context = browser.new_context(proxy={"server": get_random_proxy()})
Proxies residenciais funcionam melhor do que proxies de datacenter para o Google especificamente. IPs de datacenter são sinalizados mais rapidamente.
Rotação de User Agent
USER_AGENTS = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/120.0',
]
def get_random_ua():
return random.choice(USER_AGENTS)
Digitação Semelhante à Humana
def human_type(element, text):
for char in text:
element.send_keys(char)
time.sleep(random.uniform(0.05, 0.2))
Digitar em velocidade uniforme é um sinal de bot. Atrasos variáveis por caractere parecem humanos.
Aquecimento de Sessão
Não vá direto para o Google Maps. Visite o Google Search primeiro, espere alguns segundos e depois navegue para o Maps. Sessões frias que pulam diretamente para alvos de scraping são sinalizadas com mais frequência.
Lidando com Conteúdo Dinâmico
As avaliações do Google usam rolagem infinita — sem números de página, sem botão "próximo". Seu scraper precisa continuar rolando até que:
- Ele atinja seu alvo de
max_reviews, ou - Nenhuma nova avaliação carregue após várias tentativas de rolagem
O código acima lida com isso com um contador de consecutive_failures. Após 5 rolagens sem novas avaliações, ele para. Essa é a abordagem certa — não loop para sempre.
Expandindo Avaliações Truncadas
Avaliações longas são cortadas com um link "Mais". Para obter o texto completo:
def expand_truncated_reviews(page):
expand_buttons = page.locator("button:has-text('More'), span:has-text('...')")
count = expand_buttons.count()
for i in range(min(count, 100)):
btn = expand_buttons.nth(i)
if btn.is_visible():
btn.click()
page.wait_for_timeout(300)
logger.info(f"Expanded {count} truncated reviews")
Execute isso após carregar todas as avaliações, antes da extração.
Considerações Legais e Éticas
Raspar dados visíveis publicamente é geralmente legal na maioria das jurisdições. A decisão HiQ v. LinkedIn de 2022 nos EUA confirmou que raspar dados públicos não viola a Lei de Fraude e Abuso de Computadores.
Dito isso, algumas regras se aplicam:
- Não sobrecarregue servidores. Mantenha as solicitações abaixo de 10 por minuto para uso casual.
- Respeite o robots.txt. O robots.txt do Google restringe alguns caminhos — verifique-o.
- Não republifique conteúdo raspado literalmente. Agregue e analise, não copie e cole.
- Evite dados pessoais. Nomes de avaliadores são públicos, mas não construa perfis sobre indivíduos.
- Uso comercial precisa de revisão legal. Se você está vendendo dados raspados, consulte um advogado.
A abordagem mais segura: raspe para análise interna, não para redistribuição.
Quando Raspar com Python Não É a Ferramenta Certa
Escrever e manter um scraper do Google Maps exige um esforço real. O Google muda sua estrutura HTML regularmente. Seletores quebram. Medidas anti-bot evoluem. Você gastará tempo depurando, não analisando.
Se você precisa de dados de avaliações do Google Maps em grande escala — em centenas ou milhares de negócios — um banco de dados pré-indexado é mais rápido e confiável do que um scraper DIY.
O IBLead indexa mais de 50 milhões de negócios em 37 países, com até 500 avaliações do Google por listagem: texto completo, avaliação em estrelas, data e nome do avaliador. Os dados são atualizados semanalmente e exportam instantaneamente para CSV. Sem infraestrutura de scraping para manter, sem proxies para gerenciar, sem seletores para corrigir quando o Google atualiza seu frontend.
Para pesquisas pontuais em um punhado de negócios, a abordagem em Python deste guia funciona bem. Para geração de leads contínua ou monitoramento de reputação em larga escala, $52 por 10.000 leads é difícil de superar.
Comece grátis — 200 créditos incluídos
FAQ
Quantas avaliações posso raspar por dia sem ser bloqueado?
Comece conservador: 100–500 avaliações por dia, em 5–10 negócios, com atrasos de 2–3 segundos entre as ações. Com rotação de proxies e gerenciamento adequado de sessões, você pode chegar a 1.000–2.000 avaliações por dia. Scraping agressivo (5.000+ avaliações/dia) requer redes de proxies residenciais e várias sessões de navegador rodando em paralelo.
Playwright ou Selenium é melhor para raspar avaliações do Google Maps com Python?
Playwright é a melhor escolha para novos projetos em 2025. É 2–3x mais rápido, tem suporte async embutido e lida com anti-detecção com menos configuração manual. O Selenium ainda é válido se você tiver infraestrutura existente ou precisar de suporte máximo da comunidade. Ambos os métodos funcionam — o código neste guia demonstra ambos.
Por que meus seletores estão retornando resultados vazios?
O Google atualiza seu frontend regularmente. Um seletor que funcionou no mês passado pode não retornar nada hoje. A solução: use múltiplos seletores de fallback para cada campo e teste com headless=False para que você possa ver como a página realmente se parece. O padrão extract_with_fallbacks() mostrado neste guia lida com isso sistematicamente.
Posso raspar avaliações do Google para análise de concorrentes?
Sim. Dados de avaliações públicas são acessíveis publicamente. Analisar o sentimento dos concorrentes, rastrear tendências de avaliação ou identificar reclamações comuns é um caso de uso legítimo. Não republique avaliações individuais literalmente ou construa perfis pessoais sobre avaliadores. Foque em insights agregados.
Como lido com CAPTCHAs?
A prevenção é melhor que a solução. Diminua sua taxa de solicitação, use proxies residenciais, adicione atrasos realistas e aqueça sessões antes de raspar. Quando um CAPTCHA aparecer de qualquer maneira: em desenvolvimento, execute com headless=False e resolva manualmente. Em produção, integre um serviço de resolução de CAPTCHA ou implemente um backoff exponencial que espera 5–10 minutos antes de tentar novamente.
Pronto para começar?
Aceda a todas as empresas do Google Maps, enriquecidas com emails e dados legais.
Experimente o IBLead gratuitamenteArtigos relacionados
10 Dicas Comprovadas para Fazer Clientes Deixarem Mais Avaliações no Google Maps
Aprenda 10 estratégias práticas para aumentar as avaliações no Google Maps. Táticas que realmente funcionam.
7 Erros de Cold Email para Evitar: Exemplos e Modelos
Evite esses 7 erros de cold email que matam as taxas de resposta. Exemplos reais, modelos AIDA e soluções comprovadas para melhor prospecção.
Dados do Google Maps para ABM: O Guia Estratégico Completo
Descubra como os dados do Google Maps para marketing baseado em contas geram 208% mais receita. Crie listas de alvos precisas com 50M+ empresas.