O que pode extrair da Expedia e por que é importante
Ao extrair os resultados de pesquisa de hotéis da Expedia, um scraper bem construído pode extrair os seguintes campos de cada ficha de listagem:
- Nome do hotel — o nome de exibição da propriedade, tal como aparece nos resultados de pesquisa
- Preço por noite — a tarifa para as datas selecionadas, incluindo quaisquer preços promocionais
- Classificação por estrelas — a classificação oficial por estrelas (1–5)
- Pontuação das avaliações dos hóspedes — classificação agregada dos utilizadores (por exemplo, 8,4/10)
- Número de avaliações — o número de avaliações que compõem a pontuação
- Localização / bairro — útil para filtragem geográfica e mapeamento
O conjunto de dados pode ser alargado para incluir emblemas promocionais ou miniaturas de fotos, para uma análise posterior mais detalhada.
Casos de utilização no mundo real incluem monitorização de preços (acompanhamento de alterações nas tarifas ao longo de datas e destinos), aplicações de comparação de viagens (agregação de listagens de várias OTA) e benchmarking da concorrência (compreender como os preços de um estabelecimento se comparam aos hotéis nas proximidades). Os dados extraídos da Expedia também podem alimentar motores de recomendação e ferramentas de viagem direcionadas para o cliente.
Considerações legais e éticas antes de começar
Antes de escrever uma única linha de código, verifique esta lista de verificação:
- Verifique o ficheiro robots.txt — Visite https://www.expedia.com/robots.txt e respeite os caminhos não permitidos. (Verifique no momento da publicação — as diretivas podem mudar.)
- Analise os Termos de Serviço — Os TOS da Expedia restringem o acesso automatizado. A pesquisa pessoal encontra-se numa categoria de risco diferente da revenda comercial. Consulte um advogado em caso de dúvida.
- Extraia apenas dados públicos — As listagens de hotéis apresentadas a qualquer visitante anónimo são públicas. Não tente aceder a conteúdos protegidos por conta nem enviar formulários automaticamente.
- Limite a frequência das suas solicitações — Adicione atrasos deliberados (mínimo de 2 a 5 segundos entre solicitações). Sobrecarregar um servidor é eticamente questionável e um caminho rápido para ser bloqueado.
- Armazene os dados de forma responsável — Guarde apenas o que precisa e evite republicar conteúdo extraído de formas que concorram diretamente com os próprios produtos da Expedia.
Por que é difícil fazer scraping da Expedia
A Expedia carrega dinamicamente as suas listas de hotéis utilizando JavaScript, o que significa que um scraper estático que recupere HTML bruto irá perder o conteúdo real. O servidor envia uma estrutura praticamente vazia; o navegador executa JavaScript para recuperar e renderizar os cartões dos hotéis. Se não estiver a renderizar esse JavaScript, não está a ver os dados. A renderização de JavaScript é um desafio fundamental para o web scraping, e a Expedia é um dos exemplos mais agressivos.
Para além da renderização, a Expedia utiliza bloqueio de IP (pedidos repetidos a partir do mesmo IP desencadeiam bloqueios), impressão digital do navegador (os navegadores headless são detetáveis através da ausência de APIs e anomalias de temporização) e nomes de classes dinâmicos (as classes CSS são geradas no momento da compilação e alteram-se a cada implementação, invalidando seletores codificados sem aviso prévio).
Uma simples solicitação de fetch retorna navegação e metadados, mas zero listagens de hotéis. Essa lacuna é a razão pela qual precisa de um navegador headless ou de uma API de scraping.
Escolher a sua abordagem: navegador headless DIY vs. API de scraping
A opção «faça você mesmo» oferece-lhe a máxima flexibilidade, mas exige que configure um navegador headless, gerencie um conjunto de proxies e mantenha o ambiente à medida que as versões do navegador mudam. Uma API de scraping abstrai tudo isso: você envia uma solicitação com a sua URL de destino e regras de extração; a API lida com a renderização, a rotação de proxies e as tentativas de repetição.
Para a maioria dos casos de utilização de scraping da Expedia — monitorização de preços, extração periódica de dados, pesquisa — a abordagem da API é mais rápida de implementar e mais barata de manter ao longo do tempo. Evita o trabalho de manter os binários do navegador atualizados, procurar proxies residenciais fiáveis e depurar falhas de renderização específicas do ambiente. A desvantagem é que depende de um serviço externo, por isso avalie as garantias de tempo de atividade e os níveis de preços antes de se comprometer com qualquer uma das opções.
Configuração do ambiente e pré-requisitos
Vai precisar do Python 3.8 ou posterior (utilize python --version para verificar). Instale as bibliotecas necessárias:
pip install webscrapingapi pandas
webscrapingapi é o cliente Python oficial para a WebScrapingAPI — envolve a camada de pedidos HTTP e gere a autenticação. pandas gere a limpeza de dados e a exportação para CSV.
Obtenha a sua chave API no painel de controlo da WebScrapingAPI e guarde-a como uma variável de ambiente, em vez de a codificar diretamente no seu script:
export WSAPI_KEY="your_api_key_here"
Em seguida, carregue-a no Python com os.environ.get("WSAPI_KEY"). Mantenha o seu ficheiro de script (por exemplo, expedia.py) numa pasta de projeto dedicada para que os caminhos relativos para a exportação CSV funcionem de forma consistente em todas as execuções. O módulo os integrado do Python é tudo o que precisa — não é necessária nenhuma instalação adicional. Para uma introdução mais abrangente aos padrões de scraping baseados em Python, consulte o nosso guia de web scraping em Python.
Como identificar os seletores CSS corretos na Expedia
Este é o passo que a maioria dos tutoriais ignora. Aqui está um passo a passo concreto do DevTools.
- Abra uma página de pesquisa da Expedia e deixe-a carregar completamente.
- Clique com o botão direito do rato num cartão de hotel → «Inspecionar» para abrir o DevTools com o elemento destacado.
- Identifique o contêiner do cartão de listagem — o <div> ou <article> repetido que envolve cada resultado de hotel. Este é o seu seletor raiz; ele deve aparecer uma vez por listagem.
- Analise os elementos filhos — encontre os elementos que contêm o nome do hotel, o preço, a classificação e o número de avaliações. Clique com o botão direito do rato em cada um → «Copiar > Copiar seletor».
- Verifique a exclusividade — execute document.querySelectorAll("YOUR_SELECTOR") na consola do DevTools e confirme se a contagem corresponde ao número de cartões de hotéis.
- Use seletores relativos — os seletores filhos devem ser relativos ao contêiner do cartão, não absolutos a partir da raiz do documento.
Importante: os nomes das classes CSS na Expedia são gerados dinamicamente e alteram-se com as implementações do site. Verifique sempre os seletores numa página ativa antes de uma execução em produção. A nossa ficha de referência de seletores CSS aborda a sintaxe e a especificidade dos seletores em detalhe.
Criação do Scraper de Pesquisa de Hotéis da Expedia
O núcleo do scraper são dois dicionários — extract_rules e js_scenario — passados como parâmetros para o cliente da API. Juntos, eles indicam à API o que extrair e como renderizar a página antes do início da extração. Definir corretamente estes dois objetos é o passo mais importante em todo o fluxo de trabalho de scraping da Expedia em Python, porque todos os resultados a jusante dependem deles.
Definir regras de extração e instruções de renderização JS
O extract_rules indica à API quais os seletores CSS a utilizar e o que deve ser devolvido. O js_scenario fornece instruções ao navegador headless integrado: wait pausa a execução durante um determinado número de milissegundos; evaluate executa JavaScript personalizado no contexto da página (para deslocamento, cliques, etc.).
import os, json
import pandas as pd
import webscrapingapi
API_KEY = os.environ.get("WSAPI_KEY")
client = webscrapingapi.WebScrapingAPIClient(API_KEY)
# Verify these selectors against a live Expedia page before use
CARD_SELECTOR = "[data-stid='lodging-card-responsive']"
extract_rules = {
"hotels": {
"selector": CARD_SELECTOR,
"type": "list",
"output": {
"name": {"selector": "[data-stid='content-hotel-title']", "output": "text"},
"price": {"selector": "[data-stid='price-summary']", "output": "text"},
"rating": {"selector": ".uitk-rating-medium", "output": "text"},
"reviews": {"selector": "[data-stid='reviews-summary']", "output": "text"},
"location": {"selector": "[data-stid='content-hotel-neighborhood']", "output": "text"},
}
}
}
# Wait 2 s → scroll to bottom → wait 2 s to trigger lazy-loaded cards
js_scenario = {"instructions": [
{"wait": 2000},
{"evaluate": "window.scrollTo(0, document.body.scrollHeight)"},
{"wait": 2000}
]}
O padrão de espera em duas fases — pausar antes de rolar e, em seguida, pausar novamente depois — é deliberado. A Expedia usa a renderização JavaScript para carregar os cartões de hotéis de forma diferida à medida que a janela de visualização desce pela página. Ignorar qualquer uma das pausas arrisca o retorno de uma lista incompleta de propriedades, especialmente em ligações mais lentas ou quando o destino tem muitos resultados.
Fazer a solicitação da API e lidar com as respostas
Parâmetros-chave: wait_for aguarda até que um seletor CSS apareça antes de extrair; country_code define o país de saída do proxy para a localização de preços; premium_proxy ativa a rotação de proxies residenciais.
def scrape_expedia_hotels(destination, check_in, check_out, page=1):
q = destination.replace(" ", "+")
url = (f"https://www.expedia.com/Hotel-Search"
f"?destination={q}&startDate={check_in}&endDate={check_out}&page={page}")
try:
response = client.get(url, params={
"wait_for": CARD_SELECTOR,
"extract_rules": json.dumps(extract_rules),
"js_scenario": json.dumps(js_scenario),
"country_code": "us",
"premium_proxy": "true",
})
except Exception as e:
print(f"Request failed: {e}"); return []
if response.status_code == 401:
print("Invalid API key."); return []
if response.status_code == 500:
print(f"HTTP 500 on page {page} — retry with backoff."); return []
if response.status_code != 200:
print(f"Unexpected status {response.status_code}."); return []
try:
hotels = response.json().get("hotels", [])
except ValueError:
return []
if not hotels:
print(f"No results on page {page}. CSS selectors may have drifted.")
return hotels
Uma lista de hotéis vazia com um status 200 quase sempre significa que seus seletores CSS se deslocaram. O HTTP 500 da Expedia costuma ser transitório — crie uma lógica de repetição com recuo exponencial no local da chamada. Observe que o parâmetro da página já está incorporado na assinatura da função, tornando simples chamar essa função dentro de um loop de paginação na próxima seção.
Extracção de várias páginas de resultados de hotéis
A Expedia utiliza um parâmetro de consulta de página previsível, tornando a paginação simples. O ciclo abaixo itera até obter um conjunto de resultados vazio ou atingir o limite de páginas:
import time
def scrape_all_pages(destination, check_in, check_out, max_pages=5, delay=3):
all_hotels = []
for page in range(1, max_pages + 1):
hotels = scrape_expedia_hotels(destination, check_in, check_out, page=page)
if not hotels:
print(f"No results on page {page}. Stopping."); break
all_hotels.extend(hotels)
print(f"Page {page}: {len(hotels)} hotels (total: {len(all_hotels)})")
if page < max_pages:
time.sleep(delay) # Respect rate limits
return all_hotels
results = scrape_all_pages("Rome, Italy", "2026-10-05", "2026-10-10", max_pages=5, delay=3)
O parâmetro de atraso é importante. Pedidos em sequência rápida desencadeiam, de forma fiável, bloqueios de IP. Uma pausa de 3 segundos é um mínimo razoável; randomize dentro de um intervalo de 2 a 5 segundos para execuções maiores, a fim de evitar padrões de temporização previsíveis.
Os resultados de pesquisa da Expedia variam em profundidade consoante o destino e o intervalo de datas. Em vez de assumir um número fixo de páginas, a condição de saída antecipada do ciclo (if not hotels: break) gere a terminação de forma clara — quando a API devolve uma lista vazia, chegou ao fim dos resultados.
Limpeza e exportação de dados para CSV
O texto bruto precisa de ser limpo antes da exportação — preços, classificações e contagens de avaliações chegam como cadeias de caracteres sem tipo. Normalize-os primeiro:
import re
def clean_price(raw):
if not raw: return None
try: return float(re.sub(r"[^\d.]", "", raw.split()[0]))
except ValueError: return None
def clean_rating(raw):
if not raw: return None
m = re.search(r"(\d+\.?\d*)", raw)
return float(m.group(1)) if m else None
def clean_review_count(raw):
if not raw: return None
d = re.sub(r"[^\d]", "", raw)
return int(d) if d else None
def clean_and_export(hotels, filename="expedia_hotels.csv"):
df = pd.DataFrame([{
"name": h.get("name", "").strip(),
"price_usd": clean_price(h.get("price")),
"rating": clean_rating(h.get("rating")),
"review_count": clean_review_count(h.get("reviews")),
"location": h.get("location", "").strip(),
} for h in hotels])
df.dropna(subset=["name"], inplace=True)
df.to_csv(filename, index=False, encoding="utf-8")
print(f"Exported {len(df)} hotels to {filename}")
return df
df = clean_and_export(results)
As colunas CSV são tipadas — price_usd (float), rating (float), review_count (int) — prontas para análise sem pós-processamento manual.
Referência completa do script
Todas as funções (fetch, scrape, to_csv) foram definidas nas secções acima. Combine-as num único ficheiro chamado expedia.py, defina a sua variável de ambiente WSAPI_KEY e inicie a execução completa com o ponto de entrada abaixo.
if __name__ == "__main__":
to_csv(scrape("Rome, Italy", "2026-10-05", "2026-10-10"))
Execute com python expedia.py. Os resultados são gravados em expedia_hotels.csv no seu diretório de trabalho, limpos e prontos para análise imediata.
Manutenção do seu scraper quando a Expedia altera o seu layout
Uma das razões mais comuns pelas quais um scraper da Expedia deixa de funcionar é o desvio do seletor — a Expedia atualiza regularmente o seu frontend, os nomes das classes mudam, as hierarquias dos elementos alteram-se e os seletores que funcionavam no mês passado deixam silenciosamente de devolver dados.
Como detetar o desvio do seletor: o seu scraper executa-se sem erros, mas devolve uma lista vazia. Valores nulos aparecem em todos os campos limpos simultaneamente. Estes são sinais fiáveis de que os seletores mudaram.
O fluxo de trabalho de reidentificação:
- Abra a Expedia num navegador e faça uma nova pesquisa.
- Clique com o botão direito do rato num cartão de hotel → Inspecionar.
- Compare o DOM atual com os seus seletores extract_rules. Encontre o mesmo elemento semântico (título do nome do hotel, contêiner de preço), mesmo que os nomes das classes tenham mudado.
- Atualize o CARD_SELECTOR e os seletores filhos e, em seguida, execute um teste de página única antes de reativar o ciclo completo.
Monitorização leve: Agende uma execução canary diária para um destino fixo. Receba um alerta em caso de resultados nulos para obter visibilidade no mesmo dia sobre desvios nos seletores. Para saber mais sobre como sites com uso intensivo de JavaScript afetam a estabilidade dos seletores, consulte o nosso guia sobre como o JavaScript afeta a extração de dados da web.
Escalabilidade e melhores práticas
- Limite as solicitações. Uma pausa de 3 a 5 segundos entre páginas é o mínimo; randomize o atraso para evitar padrões de tempo previsíveis.
- Implemente o backoff exponencial. Em respostas HTTP 500 ou 429, duplique o atraso em cada nova tentativa (5s, 10s, 20s).
- Alterne o código do país. Faça corresponder o país de saída ao seu mercado-alvo para uma localização precisa dos preços.
- Agende execuções recorrentes. Use cron, Airflow ou uma função na nuvem para monitorizar preços. Armazene resultados com carimbos de data/hora para acompanhar as alterações ao longo do tempo.
- Registe os códigos de estado por página e as contagens de resultados. Quando algo falhar, vai querer saber exatamente qual a página e o destino que provocaram a falha.
Para mais informações sobre como evitar bloqueios de IP em grande escala, consulte o nosso guia sobre como eliminar bloqueios de IP durante o web scraping.
Pontos-chave
- A renderização JavaScript é imprescindível para a Expedia. Uma solicitação HTTP estática não retornará listas de hotéis — é necessário um navegador headless ou uma API de scraping que renderize JS para si.
- Os seletores CSS sofrem alterações. A Expedia atualiza regularmente o seu front-end. Incorpora a deteção de alterações nos seletores no teu pipeline e aprende a reidentificar seletores com o DevTools quando estes falham.
- A paginação requer um loop. Use o parâmetro de consulta de página da Expedia e pare quando obtiver um conjunto de resultados vazio — não presuma um número fixo de páginas.
- Limpe os seus dados antes de exportar. Remova símbolos de moeda, analise classificações numéricas e converta contagens de avaliações em números inteiros no momento da extração.
- Limite de taxa e moderação. Atrasos deliberados entre pedidos são eticamente corretos e praticamente necessários para evitar bloqueios.
Perguntas frequentes
Como sei quando o meu scraper da Expedia deixou de funcionar devido a uma alteração na estrutura HTML?
O sinal mais claro é uma lista de resultados vazia — a chamada à API é bem-sucedida (HTTP 200), mas devolve zero registos de hotéis. Um sinal secundário são valores «None» em todos os campos limpos. Configure uma execução diária de teste contra um destino fixo e receba um alerta em caso de resultados nulos.
Qual é a diferença entre fazer scraping de uma página de resultados de pesquisa da Expedia e de uma página de detalhes de um hotel?
Uma página de resultados de pesquisa devolve dados resumidos — nome, preço, classificação, localização — numa lista paginada. Uma página de detalhes de um hotel contém dados mais detalhados sobre uma propriedade: listas de comodidades, descrições dos tipos de quartos, políticas de cancelamento e texto de avaliações. Os seletores e os requisitos de renderização diferem entre as duas.
Como posso evitar atingir os limites de taxa da Expedia ao extrair grandes conjuntos de dados?
Utilize atrasos aleatórios em vez de intervalos fixos — um intervalo uniforme é mais fácil de ser identificado pelos sistemas anti-bot. Distribua as listas de destinos por várias horas ou dias e utilize o backoff exponencial em respostas 429 e 500.
Posso extrair comentários e classificações da Expedia juntamente com dados de preços numa única solicitação?
Sim, se a pontuação e o número de avaliações aparecerem na página de resultados da pesquisa, adicione seletores para ambos os campos ao seu dicionário extract_rules. O texto completo das avaliações encontra-se na página de detalhes do hotel e requer uma solicitação separada.
Conclusão
Extrair dados de hotéis da Expedia em Python é possível, mas requer mais do que uma simples solicitação HTTP. É necessário renderização em JavaScript para visualizar as listagens reais, rotação de proxies confiável para evitar bloqueios de IP e uma estratégia clara para identificar e manter seletores CSS à medida que o front-end da Expedia evolui.
A abordagem deste guia — utilizar uma API de scraping para gerir a camada de infraestrutura, combinada com parâmetros explícitos extract_rules e js_scenario — permite-lhe obter um scraper funcional mais rapidamente do que configurar e manter uma pilha de navegadores headless locais. O ciclo de paginação, as funções de limpeza de dados e a estratégia de monitorização da deriva dos seletores tornam-no pronto para produção, em vez de ser apenas uma prova de conceito.
Se quiser evitar completamente a sobrecarga da infraestrutura, a API de scraper da WebScrapingAPI lida com a renderização de JavaScript, a rotação de proxies e a resolução de CAPTCHAs por trás de um único ponto de extremidade — para que se possa concentrar nos dados, e não na parte técnica. Explore os nossos casos de uso de scraping para viagens e hotelaria para conhecer mais padrões de recolha de dados de OTA, ou consulte os nossos guias relacionados sobre scraping do Booking.com e scraping de listagens do Airbnb.




