Qué se puede extraer de Expedia y por qué es importante
Al extraer los resultados de búsqueda de hoteles de Expedia, un scraper bien construido puede extraer los siguientes campos de cada ficha de listado:
- Nombre del hotel: el nombre que aparece en los resultados de búsqueda
- Precio por noche: la tarifa para las fechas seleccionadas, incluyendo cualquier precio promocional
- Clasificación por estrellas: la clasificación oficial por estrellas (1-5)
- Puntuación de las opiniones de los huéspedes: valoración agregada de los usuarios (p. ej., 8,4/10)
- Número de opiniones: el número de opiniones que hay detrás de la puntuación
- Ubicación/barrio: útil para el filtrado geográfico y la visualización en mapas
El conjunto de datos se puede ampliar para incluir insignias promocionales o fotos en miniatura, lo que permite un análisis posterior más detallado.
Entre los casos de uso en el mundo real se incluyen el seguimiento de precios (seguimiento de los cambios de tarifas según fechas y destinos), las aplicaciones de comparación de viajes (que agregan listados de múltiples OTA) y la evaluación comparativa de la competencia (para comprender cómo se comparan los precios de un establecimiento con los de los hoteles cercanos). Los datos extraídos de Expedia también pueden alimentar motores de recomendación y herramientas de viaje dirigidas al cliente.
Consideraciones legales y éticas antes de empezar
Antes de escribir una sola línea de código, revisa esta lista de verificación:
- Comprueba el archivo robots.txt: visita https://www.expedia.com/robots.txt y respeta las rutas no permitidas. (Verifica en el momento de la publicación, ya que las directivas cambian).
- Revisa los Términos de servicio: los Términos de servicio de Expedia restringen el acceso automatizado. La investigación personal se encuentra en una categoría de riesgo diferente a la reventa comercial. Consulta a un abogado si tienes dudas.
- Extrae solo datos públicos: los listados de hoteles que se muestran a cualquier visitante anónimo son públicos. No intentes acceder a contenido restringido a cuentas registradas ni envíes formularios automáticamente.
- Limita la frecuencia de tus solicitudes: añade retrasos deliberados (mínimo de 2 a 5 segundos entre solicitudes). Sobrecargar un servidor es poco ético y el camino más rápido para que te bloqueen.
- Almacena los datos de forma responsable: conserva solo lo que necesites y evita volver a publicar el contenido extraído de formas que compitan directamente con los propios productos de Expedia.
Por qué es difícil extraer datos de Expedia
Expedia carga dinámicamente sus listados de hoteles utilizando JavaScript, lo que significa que un rastreador estático que recupere HTML sin procesar no obtendrá el contenido real. El servidor envía una estructura prácticamente vacía; el navegador ejecuta JavaScript para recuperar y renderizar las fichas de los hoteles. Si no renderizas ese JavaScript, no verás los datos. El renderizado de JavaScript es un reto fundamental para el rastreo web, y Expedia es uno de los ejemplos más agresivos.
Más allá de la visualización, Expedia utiliza el bloqueo de IP (las solicitudes repetidas desde la misma IP activan bloqueos), la huella digital del navegador (los navegadores sin interfaz gráfica son detectables por la falta de API y anomalías de sincronización) y nombres de clase dinámicos (las clases CSS se generan en el momento de la compilación y cambian con cada implementación, rompiendo los selectores codificados sin previo aviso).
Una simple solicitud de datos devuelve la navegación y los metadatos, pero ningún listado de hoteles. Esa brecha es la razón por la que necesitas un navegador sin interfaz gráfica o una API de scraping.
Elegir tu enfoque: navegador sin interfaz gráfica DIY frente a API de scraping
La opción de hacerlo tú mismo te ofrece la máxima flexibilidad, pero requiere que configures un navegador sin interfaz gráfica, gestiones un conjunto de proxies y mantengas el entorno a medida que cambian las versiones del navegador. Una API de scraping se encarga de todo eso: envías una solicitud con tu URL de destino y las reglas de extracción; la API se ocupa de la representación, la rotación de proxies y los reintentos.
Para la mayoría de los casos de uso de scraping de Expedia —seguimiento de precios, extracción periódica de datos, investigación— el enfoque de la API es más rápido de implementar y más barato de mantener a largo plazo. Evitas la carga de mantener actualizados los binarios del navegador, buscar proxies residenciales fiables y depurar fallos de renderización específicos del entorno. La contrapartida es que dependes de un servicio externo, así que evalúa las garantías de tiempo de actividad y los niveles de precios antes de comprometerte con cualquiera de las dos opciones.
Configuración del entorno y requisitos previos
Necesitarás Python 3.8 o posterior (utiliza python --version para comprobarlo). Instala las bibliotecas necesarias:
pip install webscrapingapi pandas
webscrapingapi es el cliente oficial de Python para WebScrapingAPI: envuelve la capa de solicitudes HTTP y gestiona la autenticación. pandas se encarga de la limpieza de datos y la exportación a CSV.
Obtén tu clave API desde el panel de control de WebScrapingAPI y guárdala como variable de entorno en lugar de codificarla directamente en tu script:
export WSAPI_KEY="your_api_key_here"
A continuación, cárgala en Python con os.environ.get("WSAPI_KEY"). Guarda tu archivo de script (por ejemplo, expedia.py) en una carpeta de proyecto dedicada para que las rutas relativas para la exportación a CSV funcionen de forma coherente en todas las ejecuciones. El módulo os integrado en Python es todo lo que necesitas; no se requiere ninguna instalación adicional. Para una introducción más amplia a los patrones de scraping basados en Python, consulta nuestra guía de scraping web con Python.
Cómo identificar los selectores CSS adecuados en Expedia
Este es el paso que la mayoría de los tutoriales se saltan. A continuación, te ofrecemos un tutorial concreto con DevTools.
- Abre una página de búsqueda de Expedia y deja que se cargue por completo.
- Haz clic con el botón derecho en una ficha de hotel → «Inspeccionar» para abrir DevTools con el elemento resaltado.
- Identifica el contenedor de la ficha del anuncio: el elemento repetido <div> o <article> que envuelve cada resultado de hotel. Este es tu selector raíz; debería aparecer una vez por anuncio.
- Profundiza en los elementos secundarios: busca los elementos que contienen el nombre del hotel, el precio, la valoración y el número de reseñas. Haz clic con el botón derecho en cada uno → «Copiar > Copiar selector».
- Verifica la unicidad: ejecuta document.querySelectorAll("TU_SELECTOR") en la consola de DevTools y confirma que el recuento coincide con el número de tarjetas de hotel.
- Utiliza selectores relativos: los selectores secundarios deben ser relativos al contenedor de la ficha, no absolutos desde la raíz del documento.
Importante: los nombres de las clases CSS en Expedia se generan dinámicamente y cambian con las implementaciones del sitio. Comprueba siempre los selectores en una página en vivo antes de una ejecución en producción. Nuestra hoja de referencia de selectores CSS cubre la sintaxis y la especificidad de los selectores en detalle.
Creación del rastreador de búsqueda de hoteles de Expedia
El núcleo del scraper son dos diccionarios — extract_rules y js_scenario — que se pasan como parámetros al cliente de la API. Juntos le indican a la API qué extraer y cómo renderizar la página antes de que comience la extracción. Configurar correctamente estos dos objetos es el paso más importante de todo el flujo de trabajo de Python para el scraping de Expedia, ya que todos los resultados posteriores dependen de ellos.
Definición de reglas de extracción e instrucciones de renderizado JS
extract_rules indica a la API qué selectores CSS debe utilizar y qué debe devolver. js_scenario proporciona instrucciones al navegador sin interfaz integrado: wait detiene la ejecución durante un número determinado de milisegundos; evaluate ejecuta JavaScript personalizado en el contexto de la página (para desplazarse, hacer clic, 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}
]}
El patrón de espera en dos fases —pausa antes de desplazarse y luego otra pausa después— es deliberado. Expedia utiliza la renderización de JavaScript para cargar las fichas de los hoteles de forma diferida a medida que la ventana de visualización se desplaza hacia abajo en la página. Saltarse cualquiera de las dos esperas conlleva el riesgo de obtener una lista incompleta de propiedades, especialmente en conexiones lentas o cuando el destino tiene muchos resultados.
Realización de la solicitud de API y gestión de respuestas
Parámetros clave: wait_for espera hasta que aparezca un selector CSS antes de extraer; country_code establece el país de salida del proxy para la localización de precios; premium_proxy habilita la rotación de proxies residenciales.
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
Una lista de hoteles vacía con un estado 200 casi siempre significa que tus selectores CSS se han desviado. El error HTTP 500 de Expedia suele ser transitorio: crea una lógica de reintento con retroceso exponencial en el punto de llamada. Ten en cuenta que el parámetro de la página ya está integrado en la firma de la función, lo que facilita llamar a esta función dentro de un bucle de paginación en la siguiente sección.
Extracción de múltiples páginas de resultados de hoteles
Expedia utiliza un parámetro de consulta de página predecible, lo que facilita la paginación. El bucle siguiente se repite hasta obtener un conjunto de resultados vacío o alcanzar el límite 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)
El parámetro de retraso es importante. Las solicitudes en ráfaga activan de forma fiable bloqueos de IP. Una pausa de 3 segundos es un mínimo razonable; aleatoriza dentro de un rango de 2 a 5 segundos para ejecuciones más largas a fin de evitar patrones de temporización predecibles.
Los resultados de búsqueda de Expedia varían en profundidad según el destino y el intervalo de fechas. En lugar de asumir un número fijo de páginas, la condición de salida anticipada del bucle (if not hotels: break) gestiona la finalización de forma limpia: cuando la API devuelve una lista vacía, has llegado al final de los resultados.
Limpieza y exportación de datos a CSV
El texto sin procesar necesita limpieza antes de la exportación: los precios, las valoraciones y el recuento de reseñas llegan como cadenas sin tipo. Normalícelos primero:
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)
Las columnas del CSV están tipadas —price_usd (float), rating (float), review_count (int)—, listas para su análisis sin necesidad de procesamiento manual posterior.
Referencia completa del script
Todas las funciones (fetch, scrape, to_csv) se han definido en las secciones anteriores. Combínalas en un único archivo llamado expedia.py, configura tu variable de entorno WSAPI_KEY y ejecuta el proceso completo con el punto de entrada que se indica a continuación.
if __name__ == "__main__":
to_csv(scrape("Rome, Italy", "2026-10-05", "2026-10-10"))
Ejecútalo con python expedia.py. Los resultados se guardan en expedia_hotels.csv en tu directorio de trabajo, limpios y listos para su análisis inmediato.
Mantenimiento de tu scraper cuando Expedia cambia su diseño
Una de las razones más comunes por las que un scraper de Expedia deja de funcionar es la deriva de los selectores: Expedia actualiza regularmente su interfaz, los nombres de las clases cambian, las jerarquías de elementos se modifican y los selectores que funcionaban el mes pasado dejan de devolver datos sin previo aviso.
Cómo detectar la deriva de los selectores: tu scraper se ejecuta sin errores, pero devuelve una lista vacía. Aparecen valores nulos en todos los campos limpios simultáneamente. Estas son señales fiables de que los selectores han cambiado.
El flujo de trabajo de reidentificación:
- Abre Expedia en un navegador y realiza una nueva búsqueda.
- Haz clic con el botón derecho en la ficha de un hotel → Inspeccionar.
- Compara el DOM actual con tus selectores de extract_rules. Busca el mismo elemento semántico (encabezado del nombre del hotel, contenedor del precio) aunque los nombres de las clases hayan cambiado.
- Actualiza CARD_SELECTOR y los selectores secundarios, y luego ejecuta una prueba de una sola página antes de volver a habilitar el bucle completo.
Supervisión ligera: programa una ejecución diaria de prueba (canary) con un destino fijo. Activa una alerta si no hay resultados para detectar en el mismo día cualquier desviación de los selectores. Para obtener más información sobre cómo los sitios con mucho JavaScript afectan a la estabilidad de los selectores, consulta nuestra guía sobre cómo JavaScript afecta al web scraping.
Escalabilidad y mejores prácticas
- Limita las solicitudes. Un intervalo de 3 a 5 segundos entre páginas es el mínimo; aleatoriza el retraso para evitar patrones de tiempo predecibles.
- Implementa un retroceso exponencial. Ante respuestas HTTP 500 o 429, duplica el retraso en cada reintento (5 s, 10 s, 20 s).
- Rote el código de país. Haga coincidir el país de salida con su mercado objetivo para una localización precisa de los precios.
- Programa ejecuciones periódicas. Utiliza cron, Airflow o una función en la nube para la monitorización de precios. Almacena los resultados con marcas de tiempo para realizar un seguimiento de los cambios a lo largo del tiempo.
- Registra los códigos de estado y el recuento de resultados por página. Cuando algo falle, querrás saber exactamente qué página y qué destino provocaron el error.
Para obtener más información sobre cómo evitar bloqueos de IP a gran escala, consulta nuestra guía sobre cómo eliminar los bloqueos de IP al realizar web scraping.
Puntos clave
- La renderización de JavaScript es imprescindible para Expedia. Una solicitud HTTP estática no devolverá listados de hoteles: necesitas un navegador sin interfaz gráfica o una API de scraping que renderice JS por ti.
- Los selectores CSS varían. Expedia actualiza su interfaz de usuario con regularidad. Incorpora la detección de cambios en los selectores a tu proceso y aprende a volver a identificar los selectores con DevTools cuando dejen de funcionar.
- La paginación requiere un bucle. Utiliza el parámetro de consulta de página de Expedia y detente cuando obtengas un conjunto de resultados vacío; no des por sentado un número fijo de páginas.
- Limpia tus datos antes de exportarlos. Elimina los símbolos de moneda, analiza las valoraciones numéricas y convierte el recuento de reseñas a números enteros en el momento de la extracción.
- Límite de velocidad y regulación. Los retrasos deliberados entre solicitudes son éticamente correctos y prácticamente necesarios para evitar bloqueos.
Preguntas frecuentes
¿Cómo sé cuándo mi rastreador de Expedia ha dejado de funcionar debido a un cambio en la estructura HTML?
La señal más clara es una lista de resultados vacía: la llamada a la API se realiza correctamente (HTTP 200), pero devuelve cero registros de hoteles. Una señal secundaria son los valores «None» en todos los campos limpios. Configura una ejecución diaria de prueba con un destino fijo y activa una alerta si no hay resultados.
¿Cuál es la diferencia entre extraer datos de una página de resultados de búsqueda de Expedia y de una página de detalles de un hotel?
Una página de resultados de búsqueda devuelve datos resumidos —nombre, precio, valoración, ubicación— en una lista paginada. Una página de detalles del hotel contiene datos más completos sobre un establecimiento: listas de servicios, desglose de tipos de habitaciones, políticas de cancelación y texto de las reseñas. Los selectores y los requisitos de renderizado difieren entre ambos.
¿Cómo evito alcanzar los límites de frecuencia de Expedia al extraer grandes conjuntos de datos?
Utiliza retrasos aleatorios en lugar de intervalos fijos: un intervalo uniforme es más fácil de detectar para los sistemas antibots. Distribuye las listas de destinos a lo largo de varias horas o días, y utiliza un retroceso exponencial ante respuestas 429 y 500.
¿Puedo extraer reseñas y valoraciones de Expedia junto con los datos de precios en una sola solicitud?
Sí, si la puntuación y el recuento de reseñas aparecen en la página de resultados de búsqueda, añade selectores para ambos campos a tu diccionario extract_rules. El texto completo de la reseña se encuentra en la página de detalles del hotel y requiere una solicitud independiente.
Conclusión
Extraer datos de hoteles de Expedia en Python es posible, pero requiere más que una simple solicitud HTTP. Necesitas renderización JavaScript para ver los listados reales, una rotación de proxies fiable para evitar bloqueos de IP y una estrategia clara para identificar y mantener los selectores CSS a medida que evoluciona la interfaz de Expedia.
El enfoque de esta guía —utilizar una API de scraping para gestionar la capa de infraestructura, combinado con parámetros explícitos de extract_rules y js_scenario— te permite obtener un scraper operativo más rápido que configurar y mantener una pila de navegadores headless locales. El bucle de paginación, las funciones de limpieza de datos y la estrategia de monitorización de la deriva de los selectores lo hacen apto para producción, en lugar de ser solo una prueba de concepto.
Si quieres evitar por completo la sobrecarga de la infraestructura, la API de scraping de WebScrapingAPI se encarga del renderizado de JavaScript, la rotación de proxies y la resolución de CAPTCHA detrás de un único punto final, para que puedas centrarte en los datos, no en la infraestructura. Explora nuestros casos de uso de scraping en el sector de viajes y hostelería para conocer más patrones de recopilación de datos de OTA, o consulta nuestras guías relacionadas sobre el scraping de Booking.com y el scraping de anuncios de Airbnb.




