Como Raspar o TripAdvisor com Python (Sem Ser Bloqueado)

Última atualização em April 17, 2026

Na semana passada, tentei coletar avaliações, notas e quantidade de reviews de cerca de 200 hotéis em três cidades europeias usando o TripAdvisor. Meu primeiro script — um simples requests.get() com headers padrão — devolveu um elegante erro 403 Forbidden em todas as requisições. Nenhum byte de dado útil.

O TripAdvisor é uma das fontes públicas mais ricas do setor de turismo: mais de , mais de 8 milhões de perfis de negócios e cerca de 460 milhões de visitantes únicos por mês. Ele influencia mais de em gastos anuais com viagens. Mas conseguir esses dados programaticamente? Aí a coisa complica. O TripAdvisor usa DataDome para detecção de bots, Cloudflare WAF, fingerprinting de TLS e desafios em JavaScript — uma defesa em camadas que bloqueia a maioria das tentativas ingênuas antes mesmo de começarem. Este guia é o recurso único que eu gostaria de ter tido: uma comparação direta entre três abordagens em Python para scraping (mais uma opção sem código), com código completo para cada uma, uma seção estruturada de troubleshooting anti-bot e padrões reutilizáveis que funcionam para hotéis, restaurantes e atrações. Seja você iniciante em Python ou um desenvolvedor experiente, isso deve poupar muitos 403 inúteis.

Não Quer Escrever Código? Extraia Dados do TripAdvisor do Jeito Mais Fácil

Quero ser direto sobre um ponto. Muita gente que busca "raspar o TripAdvisor com Python" na verdade não está apaixonada pela ideia de programar. O que ela quer é o dado — nomes de hotéis, avaliações, número de reviews, preços — em uma planilha, rápido. Se isso parece com o seu caso, existe um caminho bem mais curto.

é uma extensão Chrome com IA que nós criamos e que consegue ler qualquer página do TripAdvisor e sugerir automaticamente as colunas certas para extração. O fluxo real é de apenas dois cliques:

  1. Abra uma página de listagem do TripAdvisor (por exemplo, resultados de "Hotels in Paris").
  2. Clique em "AI Suggest Fields" na barra lateral do Thunderbit. A IA analisa a página e propõe colunas como Nome do Hotel, Avaliação, Quantidade de Reviews, Preço e Localização.
  3. Clique em "Scrape". O Thunderbit extrai os dados de todas as listagens da página — e lida com paginação automaticamente, se você precisar de mais resultados.
  4. Exporte para Excel, Google Sheets, Airtable ou Notion. As exportações são gratuitas em qualquer plano.

O Thunderbit funciona para hotéis, restaurantes e atrações sem precisar mudar configuração nenhuma — a IA se adapta ao que estiver na página. Em resultados paginados, ele detecta automaticamente botões de "Next" e rolagem infinita. E, como roda dentro do seu navegador Chrome real, ele herda cookies de sessão e o fingerprint do navegador, o que lhe dá uma vantagem natural contra sistemas anti-bot.

Você pode testá-lo com a — o plano gratuito oferece 6 páginas por mês, o suficiente para validar o fluxo.

Se você precisa de controle via código, lógica de parsing personalizada ou pretende raspar mais de 10.000 páginas, Python é o caminho. Continue lendo.

Por que Raspar o TripAdvisor com Python?

Os dados do TripAdvisor têm impacto de negócio direto e mensurável. Um mostrou que um aumento de 1 ponto no Global Review Index de um hotel, numa escala de 100 pontos, leva a um aumento de 0,89% na diária média e de 1,42% na receita por quarto disponível. Outro mostrou que um aumento exógeno de 1 estrela na nota do TripAdvisor se traduz em US$ 55 mil a US$ 75 mil extras de receita anual para um hotel médio. Reviews não são apenas métricas de vaidade — elas movem receita.

Veja como diferentes equipes usam dados do TripAdvisor:

Caso de usoQuem se beneficiaDados necessários
Análise de concorrência hoteleiraRedes de hotéis, revenue managersAvaliações, preços, volume de reviews, comodidades
Pesquisa de mercado para restaurantesGrupos de restaurantes, marcas de food serviceTipo de culinária, faixa de preço, sentimento das avaliações
Monitoramento de tendências de atraçõesOperadoras de turismo, órgãos de turismoRanking de popularidade, padrões sazonais
Análise de sentimentoPesquisadores, analistas de dadosTexto completo das reviews, estrelas, datas
Geração de leadsEquipes de vendas, agências de viagemNome do negócio, contato, localização

Por que Python especificamente? Três motivos. Primeiro, o ecossistema: BeautifulSoup, Selenium, Playwright, Scrapy, httpx, pandas — Python tem bibliotecas de scraping e análise de dados mais maduras do que qualquer outra linguagem. Segundo, usam Python, o que significa mais suporte da comunidade, mais respostas no Stack Overflow e guias mais atualizados. Terceiro, a vantagem de pipeline: você pode raspar com BeautifulSoup, limpar com pandas, rodar análise de sentimento com Hugging Face Transformers e montar dashboards — tudo na mesma linguagem. Sem trocar de contexto.

Três Maneiras de Raspar o TripAdvisor com Python (Comparadas)

Todo guia concorrente escolhe uma abordagem só e segue em frente. Isso não ajuda quando você precisa decidir antes de escrever código. Aqui está a tabela comparativa que eu gostaria que alguém tivesse me mostrado:

AbordagemVelocidadeSuporte a JSResistência a anti-botComplexidadeMelhor para
requests + BeautifulSoup⚡ Rápida (~120–200 páginas/min bruto)❌ Nenhum⚠️ BaixaFácilPáginas estáticas de listagem, projetos pequenos
Selenium / Navegador headless🐢 Lenta (~8–20 páginas/min)✅ Completo⚠️ MédiaMédiaConteúdo dinâmico, cliques em "Read more", banners de cookies
JSON oculto / API GraphQL⚡⚡ A mais rápida (~200–600 páginas/min bruto)N/A✅ Mais altaDifícilExtração em grande escala de reviews/hotéis
Sem código (Thunderbit)⚡ Rápida✅ Nativo✅ NativoA mais fácilNão desenvolvedores, exportações pontuais rápidas

Alguns avisos importantes. Essas velocidades brutas são teóricas — os limites do TripAdvisor (~10–15 requisições por minuto por IP) restringem o throughput real a cerca de 10 páginas/minuto por IP, independentemente da abordagem. O método com JSON oculto traz mais dados por requisição, o que significa menos chamadas totais e menos exposição a rate limiting. Selenium é 5x mais lento que abordagens baseadas em requests em benchmarks reais, mas é a única opção quando você precisa clicar em botões ou renderizar JavaScript.

O restante deste guia percorre os três métodos em Python com código completo. Escolha o que melhor se encaixa no seu caso, ou combine os métodos (eu frequentemente uso requests+BS4 para páginas de listagem e JSON oculto para páginas de detalhes).

Preparando o Ambiente Python

Antes de começar, vamos deixar o ambiente pronto. Você vai precisar do Python 3.10+ (recomendo 3.12 ou 3.13 — todos os pacotes principais oferecem suporte sem problemas conhecidos).

Instale tudo de uma vez:

1pip install requests beautifulsoup4 selenium httpx parsel pandas curl-cffi

Notas sobre os pacotes:

  • requests (2.33.1) — Requisições HTTP, requer Python 3.10+
  • beautifulsoup4 (4.14.3) — Parsing de HTML
  • selenium (4.43.0) — Automação de navegador, requer Python 3.10+
  • httpx (0.28.1) — Cliente HTTP assíncrono
  • parsel (1.11.0) — Seletores CSS/XPath (mais leve que BS4)
  • pandas (3.0.2) — Exportação de dados, requer Python 3.11+
  • curl_cffi (0.15.0) — Impersonação de fingerprint TLS (crucial para contornar o Cloudflare)

ChromeDriver: Se você usa Selenium, boa notícia — desde o Selenium 4.6+, o Selenium Manager baixa e armazena automaticamente a versão correta do ChromeDriver. Não precisa instalar manualmente. Ele faz o match de versão dinamicamente, então você não precisa se preocupar com incompatibilidades entre o Chrome e o driver.

Ambiente virtual (recomendado):

1python -m venv tripadvisor-scraper
2source tripadvisor-scraper/bin/activate  # macOS/Linux
3tripadvisor-scraper\Scripts\activate     # Windows

Abordagem 1: Raspar o TripAdvisor com Requests e BeautifulSoup

Esta é a abordagem mais simples. Funciona bem para páginas de listagem (resultados de hotéis, listas de restaurantes) quando os dados necessários já estão no HTML estático. Sem navegador, sem renderização de JavaScript, consumo mínimo de recursos.

Entendendo os Padrões de URL do TripAdvisor

As URLs do TripAdvisor seguem padrões previsíveis por categoria:

  • Hotéis: https://www.tripadvisor.com/Hotels-g{locationId}-{Location_Name}-Hotels.html
  • Restaurantes: https://www.tripadvisor.com/Restaurants-g{locationId}-{Location_Name}.html
  • Atrações: https://www.tripadvisor.com/Attractions-g{locationId}-Activities-{Location_Name}.html

A paginação usa o parâmetro oa (offset anchors), inserido na URL. Cada página mostra 30 resultados:

  • Página 1: URL base (sem parâmetro oa)
  • Página 2: Hotels-g187768-oa30-Italy-Hotels.html
  • Página 3: Hotels-g187768-oa60-Italy-Hotels.html

Para páginas de reviews, o parâmetro de offset é or, com incrementos de 10:

  • Página 1: Reviews-or0-Hotel_Name.html
  • Página 2: Reviews-or10-Hotel_Name.html

Para obter reviews em todos os idiomas, adicione ?filterLang=ALL à URL.

Enviando Requisições com Headers Realistas

O TripAdvisor verifica headers com agressividade. Uma requisição com headers padrão do Python é bloqueada na hora. Você precisa simular um navegador Chrome real:

1import requests
2import time
3import random
4session = requests.Session()
5headers = {
6    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
7    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
8    "Accept-Language": "en-US,en;q=0.9",
9    "Accept-Encoding": "gzip, deflate, br",
10    "Referer": "https://www.tripadvisor.com/",
11    "Sec-Fetch-Dest": "document",
12    "Sec-Fetch-Mode": "navigate",
13    "Sec-Fetch-Site": "none",
14    "Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
15    "Sec-CH-UA-Mobile": "?0",
16    "Sec-CH-UA-Platform": '"Windows"',
17}
18session.headers.update(headers)
19url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
20response = session.get(url)
21print(f"Status: {response.status_code}")
22print(f"Content length: {len(response.text)} characters")

Detalhe crucial: O TripAdvisor valida se os headers User-Agent e Sec-CH-UA são consistentes. Se você diz que é Chrome 135 no User-Agent, mas o Sec-CH-UA indica Chrome 120, você será sinalizado. Sempre rotacione conjuntos completos de headers juntos, e não headers individuais.

Fazendo Parsing das Listagens com BeautifulSoup

Depois de obter uma resposta válida, extraia os dados com BeautifulSoup. O TripAdvisor usa atributos data-automation e data-test-attribute, que são mais estáveis do que nomes de classes CSS (que mudam com frequência):

1from bs4 import BeautifulSoup
2soup = BeautifulSoup(response.text, "html.parser")
3# Encontrar todos os cards de hotéis
4cards = soup.select('div[data-test-attribute="location-results-card"]')
5hotels = []
6for card in cards:
7    # Nome do hotel
8    title_el = card.select_one('div[data-automation="hotel-card-title"]')
9    name = title_el.get_text(strip=True) if title_el else None
10    # Link para a página de detalhes
11    link_el = card.select_one('div[data-automation="hotel-card-title"] a')
12    link = "https://www.tripadvisor.com" + link_el["href"] if link_el else None
13    # Avaliação
14    rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
15    rating = rating_el.get_text(strip=True) if rating_el else None
16    # Quantidade de reviews
17    review_el = card.select_one('[data-automation="bubbleReviewCount"]')
18    review_count = review_el.get_text(strip=True).replace(",", "").split()[0] if review_el else None
19    hotels.append({
20        "name": name,
21        "rating": rating,
22        "review_count": review_count,
23        "url": link,
24    })
25print(f"Encontrados {len(hotels)} hotéis nesta página")
26for h in hotels[:3]:
27    print(h)

Observação sobre seletores: O TripAdvisor usa nomes de classes CSS ofuscados (como FGwzt, yyzcQ) que mudam a cada atualização do site. Os atributos data-automation e data-test-target são muito mais estáveis. Sempre prefira atributos de dados em vez de classes.

Lidando com Paginação

Para raspar várias páginas, faça um loop pelo parâmetro de offset com um intervalo respeitoso entre as requisições:

1import pandas as pd
2all_hotels = []
3base_url = "https://www.tripadvisor.com/Hotels-g187147-oa{offset}-Paris_Ile_de_France-Hotels.html"
4for page in range(5):  # Primeiras 5 páginas
5    offset = page * 30
6    url = base_url.format(offset=offset) if page > 0 else "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
7    response = session.get(url)
8    if response.status_code != 200:
9        print(f"Página {page + 1}: status {response.status_code}, encerrando.")
10        break
11    soup = BeautifulSoup(response.text, "html.parser")
12    cards = soup.select('div[data-test-attribute="location-results-card"]')
13    for card in cards:
14        title_el = card.select_one('div[data-automation="hotel-card-title"]')
15        name = title_el.get_text(strip=True) if title_el else None
16        rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
17        rating = rating_el.get_text(strip=True) if rating_el else None
18        review_el = card.select_one('[data-automation="bubbleReviewCount"]')
19        review_count = review_el.get_text(strip=True).replace(",", "").split()[0] if review_el else None
20        all_hotels.append({"name": name, "rating": rating, "review_count": review_count})
21    print(f"Página {page + 1}: {len(cards)} hotéis encontrados")
22    time.sleep(random.uniform(3, 7))  # Atraso aleatório para evitar rate limiting
23df = pd.DataFrame(all_hotels)
24print(f"\nTotal de hotéis extraídos: {len(df)}")

O time.sleep(random.uniform(3, 7)) é importante. O limite do TripAdvisor é de cerca de 10–15 requisições por minuto por IP. Ir mais rápido do que isso pode acionar CAPTCHAs ou erros 429.

Limitações Dessa Abordagem

Onde isso falha? A abordagem com requests+BS4 quebra quando:

  • O TripAdvisor entrega conteúdo renderizado em JavaScript (algumas páginas de resultados exigem JS)
  • O texto da review fica truncado atrás de botões "Read more"
  • Medidas anti-bot escalam para desafios em JavaScript ou CAPTCHAs
  • Você precisa de dados que só aparecem após renderização no cliente (preços, disponibilidade)

Para esses cenários, você precisa do Selenium (Abordagem 2) ou do método com JSON oculto (Abordagem 3).

Abordagem 2: Raspar o TripAdvisor com Selenium (Navegador Headless)

O Selenium abre um navegador real, o que significa que ele consegue renderizar JavaScript, clicar em botões, lidar com banners de consentimento de cookies e interagir com conteúdo dinâmico. O custo: ele é cerca de e usa 300–500 MB de RAM por instância de navegador.

Configurando o Selenium com Defesas Anti-Detecção

Do jeito padrão, o Selenium é detectável com facilidade. O fingerprinting do TripAdvisor identifica isso imediatamente. Você precisa desativar flags de automação:

1from selenium import webdriver
2from selenium.webdriver.chrome.options import Options
3from selenium.webdriver.common.by import By
4from selenium.webdriver.support.ui import WebDriverWait
5from selenium.webdriver.support import expected_conditions as EC
6options = Options()
7options.add_argument("--headless=new")  # Use o novo modo headless (Chrome 112+)
8options.add_argument("--disable-blink-features=AutomationControlled")
9options.add_argument("--window-size=1920,1080")
10options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36")
11options.add_experimental_option("excludeSwitches", ["enable-automation"])
12options.add_experimental_option("useAutomationExtension", False)
13driver = webdriver.Chrome(options=options)
14# Remove a propriedade webdriver do navigator
15driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
16    "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
17})

Isso é suficiente para o TripAdvisor? Para scraping em pequena escala (menos de 50 páginas), essa configuração com proxies residenciais geralmente funciona. Para volumes maiores, talvez você precise de undetected-chromedriver ou nodriver — a proteção DataDome do TripAdvisor analisa mais de 1.000 sinais por requisição, incluindo fingerprints de TLS que o Selenium puro não consegue mascarar.

Extraindo Resultados de Busca de Hotéis com Selenium

1import time
2import random
3url = "https://www.tripadvisor.com/Hotels-g187147-Paris_Ile_de_France-Hotels.html"
4driver.get(url)
5# Aguarda os cards de hotéis carregarem
6wait = WebDriverWait(driver, 15)
7wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')))
8# Lida com o pop-up de consentimento de cookies (se aparecer)
9try:
10    cookie_btn = driver.find_element(By.ID, "onetrust-accept-btn-handler")
11    cookie_btn.click()
12    time.sleep(1)
13except:
14    pass  # Nenhum pop-up de cookies
15# Extrai os dados dos hotéis
16cards = driver.find_elements(By.CSS_SELECTOR, 'div[data-test-attribute="location-results-card"]')
17hotels = []
18for card in cards:
19    try:
20        name = card.find_element(By.CSS_SELECTOR, 'div[data-automation="hotel-card-title"]').text
21    except:
22        name = None
23    try:
24        rating = card.find_element(By.CSS_SELECTOR, '[data-automation="bubbleRatingValue"]').text
25    except:
26        rating = None
27    try:
28        reviews = card.find_element(By.CSS_SELECTOR, '[data-automation="bubbleReviewCount"]').text
29    except:
30        reviews = None
31    hotels.append({"name": name, "rating": rating, "review_count": reviews})
32print(f"Extraídos {len(hotels)} hotéis")
33for h in hotels[:3]:
34    print(h)

Isso levou cerca de 8 segundos para uma única página na minha máquina — contra menos de 1 segundo com requests+BS4. Essa diferença de 8x cresce rápido quando você está raspando centenas de páginas.

Expandindo "Read More" e Extraindo Reviews Completas

As páginas de review truncam textos longos atrás de um botão "Read more". O Selenium pode clicar nele:

1review_url = "https://www.tripadvisor.com/Hotel_Review-g187147-d188726-Reviews-Le_Marais_Hotel-Paris_Ile_de_France.html"
2driver.get(review_url)
3wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-reviewid]')))
4time.sleep(2)
5# Clica em todos os botões "Read more"
6read_more_buttons = driver.find_elements(By.XPATH, '//button//*[contains(text(), "Read more")]/..')
7for btn in read_more_buttons:
8    try:
9        driver.execute_script("arguments[0].click();", btn)
10        time.sleep(0.3)
11    except:
12        pass
13# Extrai as reviews
14review_elements = driver.find_elements(By.CSS_SELECTOR, 'div[data-reviewid]')
15reviews = []
16for rev in review_elements:
17    try:
18        title = rev.find_element(By.CSS_SELECTOR, 'div[data-test-target="review-title"]').text
19    except:
20        title = None
21    try:
22        body = rev.find_element(By.CSS_SELECTOR, 'q.IRsGHoPm span').text
23    except:
24        try:
25            body = rev.find_element(By.CSS_SELECTOR, 'p.partial_entry').text
26        except:
27            body = None
28    try:
29        rating_class = rev.find_element(By.CSS_SELECTOR, 'div[data-test-target="review-rating"] span').get_attribute("class")
30        # A nota fica embutida em uma classe como "ui_bubble_rating bubble_50" = 5.0
31        rating_num = [c for c in rating_class.split() if "bubble_" in c][0].replace("bubble_", "")
32        rating = int(rating_num) / 10
33    except:
34        rating = None
35    reviews.append({"title": title, "body": body, "rating": rating})
36print(f"Extraídas {len(reviews)} reviews")

Adicionando Rotação de Proxy ao Selenium

Para scraping contínuo, você vai precisar rotacionar proxies. Como o selenium-wire foi descontinuado desde janeiro de 2024, use o suporte nativo de proxy do Chrome:

1# Com proxy sem autenticação
2proxy = "http://seu-endereco-de-proxy:porta"
3options.add_argument(f"--proxy-server={proxy}")
4# Para proxies com autenticação, use uma extensão do Chrome ou o protocolo BiDi do Selenium 4

Para rotacionar proxies programaticamente, crie uma nova instância do driver com um proxy diferente para cada lote de requisições. Não é elegante, mas é confiável.

Abordagem 3: Método do JSON Oculto (Sem Precisar Parsear HTML)

A maioria dos guias ignora totalmente essa abordagem, o que é uma pena — ela é a mais rápida e limpa das três. O TripAdvisor embute dados estruturados como JSON diretamente nas páginas HTML — dentro de tags <script>, como variáveis JavaScript do tipo pageManifest e urqlCache. Extrair esse JSON fornece dados mais limpos (avaliações como números, datas em formato ISO), com menos requisições e sem necessidade de renderizar JavaScript.

Encontrando o JSON Embutido no Código da Página

A ideia principal: você pode usar um simples requests.get() para baixar a página e extrair o JSON do HTML bruto sem renderizar JavaScript.

1import requests
2import re
3import json
4headers = {
5    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
6    "Accept-Language": "en-US,en;q=0.9",
7    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
8    "Referer": "https://www.tripadvisor.com/",
9    "Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
10    "Sec-CH-UA-Mobile": "?0",
11    "Sec-CH-UA-Platform": '"macOS"',
12}
13url = "https://www.tripadvisor.com/Hotel_Review-g188590-d194317-Reviews-NH_City_Centre_Amsterdam.html"
14response = requests.get(url, headers=headers)
15# Extrai o bloco JSON pageManifest
16match = re.search(r"pageManifest:({.+?})};", response.text)
17if match:
18    page_data = json.loads(match.group(1))
19    print("Encontrados dados em pageManifest")
20    print(f"Chaves: {list(page_data.keys())[:10]}")

Como descobrir o nome da variável sozinho: Abra qualquer página de hotel do TripAdvisor no Chrome, clique com o botão direito → Ver código-fonte da página, depois use Ctrl+F para procurar pageManifest, urqlCache ou aggregateRating. Os dados estão lá, prontos para serem extraídos.

Fazendo Parsing do JSON e Extraindo Dados Estruturados

O TripAdvisor também embute dados schema.org em application/ld+json, que são ainda mais fáceis de extrair:

1from parsel import Selector
2sel = Selector(text=response.text)
3# Extrai dados estruturados JSON-LD
4json_ld_scripts = sel.xpath("//script[@type='application/ld+json']/text()").getall()
5for script in json_ld_scripts:
6    data = json.loads(script)
7    if isinstance(data, dict) and data.get("@type") in ["Hotel", "Restaurant", "TouristAttraction"]:
8        print(f"Nome: {data.get('name')}")
9        print(f"Avaliação: {data.get('aggregateRating', {}).get('ratingValue')}")
10        print(f"Quantidade de reviews: {data.get('aggregateRating', {}).get('reviewCount')}")
11        print(f"Faixa de preço: {data.get('priceRange')}")
12        print(f"Endereço: {data.get('address', {}).get('streetAddress')}")
13        print(f"Coordenadas: {data.get('geo', {}).get('latitude')}, {data.get('geo', {}).get('longitude')}")
14        break

Os dados JSON-LD ficam embutidos no HTML estático e não exigem renderização em JavaScript. Eles oferecem nome do local, avaliação agregada, número de reviews, endereço, coordenadas, faixa de preço e URLs de fotos — tudo sem parsear uma única tag HTML.

Para dados mais ricos (reviews individuais, detalhamento por nota, listas de comodidades), você precisa do objeto urqlCache:

1# Extrai urqlCache para dados detalhados de reviews
2cache_match = re.search(r'"urqlCache"\s*:\s*({.+?})\s*,\s*"redux"', response.text)
3if cache_match:
4    cache_data = json.loads(cache_match.group(1))
5    # Navega no cache para encontrar dados de reviews
6    for key, value in cache_data.items():
7        if "reviews" in str(value).lower()[:100]:
8            reviews_data = json.loads(value.get("data", "{}")) if isinstance(value, dict) else None
9            if reviews_data:
10                print(f"Encontrada entrada de cache de reviews: {key[:50]}...")
11                break

Os caminhos exatos do JSON mudam ocasionalmente quando o TripAdvisor atualiza o frontend, mas a estrutura geral — JSON-LD para dados resumidos, urqlCache para dados detalhados — se manteve estável por anos.

Fazendo Engenharia Reversa da GraphQL API do TripAdvisor (Avançado)

Para extração em grande escala, os endpoints GraphQL do TripAdvisor retornam dados estruturados diretamente. Este é o método mais rápido, mas também o que exige mais manutenção.

1import httpx
2import random
3import string
4def generate_request_id():
5    """Gera o valor do header X-Requested-By"""
6    random_chars = ''.join(random.choices(string.ascii_letters + string.digits, k=180))
7    return f"TNI1625!{random_chars}"
8# Buscar hotéis em Paris
9search_payload = [{
10    "variables": {
11        "request": {
12            "query": "hotels in Paris",
13            "limit": 10,
14            "scope": "WORLDWIDE",
15            "locale": "en-US",
16            "scopeGeoId": 1,
17            "searchCenter": None,
18            "types": ["LOCATION", "QUERY_SUGGESTION", "RESCUE_RESULT"],
19            "locationTypes": ["GEO", "AIRPORT", "ACCOMMODATION", "ATTRACTION", "EATERY", "NEIGHBORHOOD"]
20        }
21    },
22    "extensions": {
23        "preRegisteredQueryId": "84b17ed122fbdbd4"
24    }
25}]
26graphql_headers = {
27    "Content-Type": "application/json",
28    "Accept": "*/*",
29    "Accept-Language": "en-US,en;q=0.9",
30    "Origin": "https://www.tripadvisor.com",
31    "Referer": "https://www.tripadvisor.com/Hotels",
32    "X-Requested-By": generate_request_id(),
33    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
34}
35with httpx.Client() as client:
36    response = client.post(
37        "https://www.tripadvisor.com/data/graphql/ids",
38        json=search_payload,
39        headers=graphql_headers
40    )
41    if response.status_code == 200:
42        results = response.json()
43        print(json.dumps(results, indent=2)[:1000])
44    else:
45        print(f"Solicitação GraphQL falhou: {response.status_code}")

Para buscar reviews via GraphQL:

1review_payload = [{
2    "variables": {
3        "locationId": 194317,  # NH City Centre Amsterdam
4        "offset": 0,
5        "limit": 20,
6        "filters": {},
7        "sortType": None,
8        "sortBy": "date",
9        "language": "en",
10        "doMachineTranslation": False,
11        "photosPerReviewLimit": 3
12    },
13    "extensions": {
14        "preRegisteredQueryId": "ef1a9f94012220d3"
15    }
16}]
17with httpx.Client() as client:
18    response = client.post(
19        "https://www.tripadvisor.com/data/graphql/ids",
20        json=review_payload,
21        headers=graphql_headers
22    )
23    if response.status_code == 200:
24        data = response.json()
25        reviews = data[0]["data"]["locations"][0]["reviewListPage"]["reviews"]
26        total = data[0]["data"]["locations"][0]["reviewListPage"]["totalCount"]
27        print(f"Total de reviews: {total}")
28        for r in reviews[:3]:
29            print(f"  [{r['rating']}/5] {r['title']} - {r['createdDate']}")

Cuidado importante: Os valores preRegisteredQueryId (como 84b17ed122fbdbd4 para busca e ef1a9f94012220d3 para reviews) podem quebrar quando o TripAdvisor fizer uma nova publicação. Quando isso acontece, suas requisições falham silenciosamente. Você vai precisar redescobrir os query IDs monitorando as requisições de rede no DevTools do navegador.

Por que Este Método Reduz a Necessidade de Proxies

A matemática é simples. Com requests+BS4, raspar 100 páginas de detalhes de hotéis exige 100 requisições. Com o método de JSON oculto, cada requisição retorna todos os dados necessários de uma única carga de página — sem requisições extras para expandir reviews ou carregar conteúdo dinâmico. Com GraphQL, uma única chamada de API pode retornar 20 reviews de uma vez. Menos requisições = menos exposição a rate limiting = menos necessidade de rotação de proxy. Para projetos pequenos a médios (menos de 1.000 páginas), talvez você nem precise de proxies, desde que adicione atrasos sensatos.

Raspe Hotéis, Restaurantes e Atrações com um Script Reutilizável

Quatro em cada cinco guias concorrentes cobrem apenas hotéis. Mas o TripAdvisor tem três categorias principais de conteúdo, e os padrões de URL e campos de dados mudam entre elas. Veja como criar uma função que lide com as três.

Campos de Dados Disponíveis por Categoria

CampoHotéisRestaurantesAtrações
Nome
Avaliação
Quantidade de reviews
Preço/Faixa de preçoÀs vezes
Endereço
Tipo de culinária
Duração/Tipo de passeio
Comodidades
Coordenadas

Criando uma Função Reutilizável scrape_tripadvisor()

1import requests
2from bs4 import BeautifulSoup
3import pandas as pd
4import time
5import random
6import re
7import json
8def scrape_tripadvisor(category, location_id, location_name, num_pages=3):
9    """
10    Extrai listagens do TripAdvisor para hotéis, restaurantes ou atrações.
11    Args:
12        category: "hotels", "restaurants" ou "attractions"
13        location_id: Geo ID do TripAdvisor (ex.: "187147" para Paris)
14        location_name: Nome amigável para URL (ex.: "Paris_Ile_de_France")
15        num_pages: Número de páginas a raspar
16    """
17    url_patterns = {
18        "hotels": "https://www.tripadvisor.com/Hotels-g{geo}-oa{offset}-{name}-Hotels.html",
19        "restaurants": "https://www.tripadvisor.com/Restaurants-g{geo}-oa{offset}-{name}.html",
20        "attractions": "https://www.tripadvisor.com/Attractions-g{geo}-oa{offset}-Activities-{name}.html",
21    }
22    first_page_patterns = {
23        "hotels": "https://www.tripadvisor.com/Hotels-g{geo}-{name}-Hotels.html",
24        "restaurants": "https://www.tripadvisor.com/Restaurants-g{geo}-{name}.html",
25        "attractions": "https://www.tripadvisor.com/Attractions-g{geo}-Activities-{name}.html",
26    }
27    headers = {
28        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
29        "Accept-Language": "en-US,en;q=0.9",
30        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
31        "Referer": "https://www.tripadvisor.com/",
32        "Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
33        "Sec-CH-UA-Mobile": "?0",
34        "Sec-CH-UA-Platform": '"Windows"',
35    }
36    session = requests.Session()
37    session.headers.update(headers)
38    all_items = []
39    for page in range(num_pages):
40        offset = page * 30
41        if page == 0:
42            url = first_page_patterns[category].format(geo=location_id, name=location_name)
43        else:
44            url = url_patterns[category].format(geo=location_id, offset=offset, name=location_name)
45        response = session.get(url)
46        if response.status_code != 200:
47            print(f"  Página {page + 1}: status {response.status_code}, encerrando.")
48            break
49        soup = BeautifulSoup(response.text, "html.parser")
50        cards = soup.select('div[data-test-attribute="location-results-card"]')
51        for card in cards:
52            item = {"category": category}
53            title_el = card.select_one('div[data-automation="hotel-card-title"]') or card.select_one('a[data-automation]')
54            item["name"] = title_el.get_text(strip=True) if title_el else None
55            rating_el = card.select_one('[data-automation="bubbleRatingValue"]')
56            item["rating"] = rating_el.get_text(strip=True) if rating_el else None
57            review_el = card.select_one('[data-automation="bubbleReviewCount"]')
58            item["review_count"] = review_el.get_text(strip=True) if review_el else None
59            all_items.append(item)
60        print(f"  Página {page + 1}: {len(cards)} itens encontrados")
61        time.sleep(random.uniform(3, 7))
62    return pd.DataFrame(all_items)
63# Exemplos de uso
64print("=== Hotéis em Paris ===")
65hotels_df = scrape_tripadvisor("hotels", "187147", "Paris_Ile_de_France", num_pages=2)
66print(hotels_df.head())
67print("\n=== Restaurantes em Roma ===")
68restaurants_df = scrape_tripadvisor("restaurants", "187791", "Rome_Lazio", num_pages=2)
69print(restaurants_df.head())
70print("\n=== Atrações em Barcelona ===")
71attractions_df = scrape_tripadvisor("attractions", "187497", "Barcelona_Catalonia", num_pages=2)
72print(attractions_df.head())

Uma função, três categorias, zero duplicação de código. Se o TripAdvisor mudar um seletor, você corrige em um lugar só.

O Que Fazer Quando o TripAdvisor Bloquear Você (Troubleshooting Anti-Bot)

Esta é a seção de que eu mais precisei quando comecei a raspar o TripAdvisor, e também a seção que nenhum guia concorrente apresenta de forma estruturada. O TripAdvisor usa DataDome (analisando mais de por dia) e Cloudflare WAF em conjunto. Aqui está uma tabela de diagnóstico para os modos de falha mais comuns:

SintomaCausa provávelCorreção
Resposta HTTP 403Headers ausentes ou suspeitos; desafio JS do CloudflareConfigure headers realistas como User-Agent, Accept-Language, Referer e Sec-CH-UA. Garanta consistência entre eles.
Página de CAPTCHA em vez de dadosRate limiting ou fingerprinting do navegadorRotacione proxies residenciais, adicione atrasos aleatórios (2–7 segundos entre requisições)
HTML vazio ou corpo em brancoJavaScript não renderizado pelo requestsTroque para Selenium ou extraia do JSON oculto no código-fonte
Reviews incompletas / "Read more" não expandeConteúdo carregado por evento de cliqueUse .click() no Selenium ou extraia do blob JSON embutido
Reviews apenas em um idiomaParâmetro de idioma ausenteAdicione ?filterLang=ALL à URL da review
Os dados param de carregar após N páginasLimite baseado em sessãoRotacione sessões, limpe cookies entre lotes
HTTP 1020 Access DeniedIP/ASN banido pelo CloudflareTroque proxies de datacenter por proxies residenciais
Loop de desafio (CAPTCHA infinito)Persistência de cookie quebradaAqueça as sessões visitando a homepage primeiro; mantenha a cookie jar

Lógica de Retry com Exponential Backoff

Nenhum artigo concorrente realmente mostra este código. Aqui vai uma função reutilizável de retry:

1import time
2import random
3import requests
4def fetch_with_retry(session, url, max_retries=4, base_delay=2, max_delay=60):
5    """
6    Faz fetch de uma URL com exponential backoff e jitter.
7    Rotaciona o User-Agent a cada retry.
8    """
9    user_agents = [
10        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
11        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
12        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
13        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
14    ]
15    for attempt in range(max_retries):
16        # Rotaciona o User-Agent nos retries
17        if attempt &gt; 0:
18            session.headers["User-Agent"] = random.choice(user_agents)
19        try:
20            response = session.get(url, timeout=30)
21            if response.status_code == 200:
22                return response
23            if response.status_code == 429:
24                # Respeite o header Retry-After, se existir
25                retry_after = int(response.headers.get("Retry-After", base_delay * (2 ** attempt)))
26                print(f"  Rate limited (429). Aguardando {retry_after}s...")
27                time.sleep(retry_after)
28                continue
29            if response.status_code in (403, 503):
30                wait = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
31                print(f"  Recebido {response.status_code}. Retry {attempt + 1}/{max_retries} em {wait:.1f}s...")
32                time.sleep(wait)
33                continue
34            # Outros códigos de erro — não tenta de novo
35            print(f"  Status inesperado {response.status_code} para {url}")
36            return response
37        except requests.exceptions.Timeout:
38            wait = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
39            print(f"  Timeout. Retry {attempt + 1}/{max_retries} em {wait:.1f}s...")
40            time.sleep(wait)
41    print(f"  Todas as {max_retries} tentativas esgotadas para {url}")
42    return None

Rotacionando Headers, Proxies e Sessões

Para scraping contínuo, mantenha um conjunto de headers e rotacione-os juntos:

1import random
2HEADER_SETS = [
3    {
4        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
5        "Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
6        "Sec-CH-UA-Platform": '"Windows"',
7    },
8    {
9        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
10        "Sec-CH-UA": '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
11        "Sec-CH-UA-Platform": '"macOS"',
12    },
13    {
14        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
15        "Sec-CH-UA": '"Google Chrome";v="134", "Not-A.Brand";v="8", "Chromium";v="134"',
16        "Sec-CH-UA-Platform": '"Windows"',
17    },
18]
19PROXY_LIST = [
20    "http://user:pass@residential-proxy-1:port",
21    "http://user:pass@residential-proxy-2:port",
22    # Adicione mais proxies residenciais
23]
24def get_rotated_session():
25    """Cria uma nova sessão com headers e proxy rotacionados."""
26    session = requests.Session()
27    # Escolhe um conjunto aleatório de headers
28    header_set = random.choice(HEADER_SETS)
29    base_headers = {
30        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
31        "Accept-Language": "en-US,en;q=0.9",
32        "Accept-Encoding": "gzip, deflate, br",
33        "Referer": "https://www.tripadvisor.com/",
34        "Sec-Fetch-Dest": "document",
35        "Sec-Fetch-Mode": "navigate",
36        "Sec-CH-UA-Mobile": "?0",
37    }
38    base_headers.update(header_set)
39    session.headers.update(base_headers)
40    # Escolhe um proxy aleatório
41    if PROXY_LIST:
42        proxy = random.choice(PROXY_LIST)
43        session.proxies = {"http": proxy, "https": proxy}
44    return session

O tipo de proxy importa. Proxies de datacenter são bloqueados quase imediatamente pelo TripAdvisor (HTTP 1020 Access Denied). Proxies residenciais são obrigatórios para scraping contínuo — eles passam por ISPs de consumidores e são indistinguíveis de usuários reais. Espere pagar de US$ 2,50 a US$ 8,40/GB, dependendo do fornecedor.

Exportando e Armazenando os Dados Extraídos do TripAdvisor

Depois de obter os dados, colocá-los em um formato útil é simples.

Exportação para CSV (A Mais Comum)

1import pandas as pd
2df = pd.DataFrame(all_hotels)
3df.to_csv("tripadvisor_hotels_paris.csv", index=False, encoding="utf-8-sig")
4print(f"Exportadas {len(df)} linhas para CSV")

O encoding='utf-8-sig' é importante — ele garante que o Excel exiba corretamente caracteres não latinos (acentos em francês, caracteres chineses etc.) ao abrir o CSV.

Exportação para JSON (Para Dados Aninhados)

Quando você tem reviews aninhadas dentro de hotéis, o JSON preserva a hierarquia:

1# Estrutura hierárquica
2hotel_data = {
3    "property_id": "d194317",
4    "name": "NH City Centre Amsterdam",
5    "rating": 4.0,
6    "reviews": [
7        {"title": "Great location", "rating": 5, "date": "2025-03-15", "text": "..."},
8        {"title": "Average stay", "rating": 3, "date": "2025-03-10", "text": "..."},
9    ]
10}
11# Para análise plana, use json_normalize
12flat_reviews = pd.json_normalize(
13    hotel_data,
14    record_path="reviews",
15    meta=["property_id", "name"]
16)
17flat_reviews.to_csv("reviews_flat.csv", index=False)

Abordagem em Dois Arquivos para Dados Relacionais

Para datasets grandes, eu uso dois arquivos CSV:

  • hotels.csv — Uma linha por propriedade (plano)
  • reviews.csv — Uma linha por review, com property_id como chave estrangeira

Isso facilita fazer join no pandas, carregar em banco de dados ou importar para ferramentas de BI.

Se você não quiser lidar com nenhuma dessa lógica de exportação, o Thunderbit permite para Excel, Google Sheets, Airtable ou Notion — tudo grátis, tudo sem código. Útil quando você precisa compartilhar os resultados com colegas sem conhecimento técnico.

Dicas para Raspar o TripAdvisor de Forma Responsável e Eficiente

Scraping responsável em seis pontos:

  • Verifique o robots.txt: O robots.txt do TripAdvisor bloqueia completamente bots de treinamento de IA (GPTBot, ClaudeBot etc.). Crawlers padrão enfrentam restrições seletivas de caminho. Consulte em tripadvisor.com/robots.txt.
  • Adicione atrasos: Intervalos de 3 a 7 segundos entre requisições são uma faixa segura. Passar de 10–15 requisições por minuto por IP ativa rate limiting.
  • Extraia apenas dados públicos. Não faça login para acessar conteúdo restrito.
  • Armazene os dados com segurança e cumpra GDPR/CCPA se estiver lidando com informações pessoais (nomes de avaliadores etc.).
  • Considere a API oficial do TripAdvisor se você precisar de dados em escala comercial. O oferece acesso a detalhes de empresas, além de até 5 reviews e 5 fotos por local — limitado, mas legal e estável.
  • Esteja atento ao contexto jurídico: A reforçou proibições de scraping baseadas em ToS em toda a União Europeia. Os Termos de Serviço do TripAdvisor proíbem explicitamente o scraping. Raspe com responsabilidade e por sua conta e risco.

Conclusão

Esse é o panorama completo.

  • Requests + BeautifulSoup é o caminho mais simples. Funciona para páginas estáticas de listagem, exige pouca configuração e é rápido. Comece por aqui se estiver raspando menos de 100 páginas e não precisar de conteúdo renderizado em JavaScript.
  • Selenium lida com tudo o que requests não consegue: conteúdo dinâmico, botões "Read more", banners de cookies. É 5x mais lento e pesado em recursos, mas é a única opção quando você precisa interagir com a página.
  • JSON oculto / GraphQL é a abordagem mais limpa e rápida. Ele fornece dados estruturados sem parsear HTML, reduz o número de requisições (e, portanto, a necessidade de proxies) e devolve dados em formatos prontos para análise. Exige mais engenharia reversa no início e manutenção ocasional quando o TripAdvisor muda sua estrutura de dados.

A função reutilizável scrape_tripadvisor() cobre hotéis, restaurantes e atrações. Você não deveria precisar de outro tutorial.

E se no meio do caminho você decidir que programação não é para você — ou simplesmente precisar de 50 hotéis em uma planilha até o fim do dia — a faz isso em dois cliques, com detecção de campos por IA, paginação automática e exportação gratuita para Excel ou Google Sheets. Sem precisar de Python.

Se quiser ir mais fundo, temos mais tutoriais de scraping no e no nosso .

FAQs

1. É legal raspar dados do TripAdvisor?

Os Termos de Serviço do TripAdvisor proíbem explicitamente o scraping. No entanto, os tribunais geralmente entendem que raspar dados publicamente disponíveis (não protegidos por login) não viola o Computer Fraud and Abuse Act nos EUA. Ainda assim, a decisão europeia Ryanair de 2025 reforçou restrições baseadas em ToS na Europa. Raspe apenas dados públicos, respeite o robots.txt, não republique conteúdo protegido por direitos autorais e consulte assessoria jurídica se for usar os dados comercialmente.

2. Posso raspar o TripAdvisor sem Python?

Sim. Ferramentas sem código como conseguem raspar o TripAdvisor diretamente do navegador, com detecção de campos por IA e paginação automática. Você também pode usar extensões de navegador, complementos do Google Sheets ou APIs comerciais de scraping. Python oferece mais controle e flexibilidade, mas não é a única opção.

3. Como evitar ser bloqueado ao raspar o TripAdvisor?

As táticas principais: use headers realistas e consistentes (especialmente User-Agent e Sec-CH-UA), rotacione proxies residenciais (IPs de datacenter são bloqueados rapidamente), adicione atrasos aleatórios de 3 a 7 segundos entre requisições, use o método de JSON oculto para minimizar o total de requisições, implemente retries com exponential backoff e aqueça as sessões visitando a homepage antes de raspar páginas profundas.

4. Que dados posso raspar do TripAdvisor?

Hotéis, restaurantes e atrações — incluindo nomes, avaliações, quantidade de reviews, faixas de preço, endereços, coordenadas, comodidades (hotéis), tipo de culinária (restaurantes), duração dos passeios (atrações) e o texto completo das reviews, com avaliações individuais e datas. As abordagens com JSON oculto e GraphQL retornam os dados mais ricos por requisição.

5. Quantas páginas consigo raspar do TripAdvisor por dia?

Com um único IP e atrasos sensatos: cerca de 600 a 1.000 páginas por dia. Com 20 proxies residenciais rotativos: aproximadamente 200.000 a 300.000 páginas por dia usando abordagens baseadas em requests. Selenium é mais lento — espere algo como 8.000 a 12.000 páginas por dia por proxy. O método com JSON oculto/GraphQL traz mais dados por requisição, então você pode precisar de bem menos páginas no total para obter a mesma quantidade de informação.

Saiba mais

Ke
Ke
CTO @ Thunderbit. Ke is the person everyone pings when data gets messy. He's spent his career turning tedious, repetitive work into quiet little automations that just run. If you've ever wished a spreadsheet could fill itself in, Ke has probably already built the thing that does it.
Sumário

Experimente a Thunderbit

Extraia leads e outros dados em apenas 2 cliques. Com tecnologia de IA.

Obter Thunderbit É grátis
Extraia dados usando IA
Transfira dados facilmente para Google Sheets, Airtable ou Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week