Ce que vous pouvez extraire d'Expedia et pourquoi c'est important
Lorsque vous effectuez un scraping des résultats de recherche d'hôtels sur Expedia, un scraper bien conçu peut extraire les champs suivants de chaque fiche de listing :
- Nom de l'hôtel — le nom d'affichage de l'établissement tel qu'il apparaît dans les résultats de recherche
- Prix par nuit — le tarif pour les dates sélectionnées, y compris les éventuelles offres promotionnelles
- Classement par étoiles — la classification officielle par étoiles (1 à 5)
- Note des avis clients — note globale attribuée par les utilisateurs (par exemple, 8,4/10)
- Nombre d'avis — le nombre d'avis à l'origine de la note
- Emplacement / quartier — utile pour le filtrage géographique et la cartographie
L'ensemble de données peut être enrichi pour inclure des badges promotionnels ou des miniatures afin de permettre une analyse plus approfondie en aval.
Parmi les cas d'utilisation concrets, on peut citer la surveillance des prix (suivi des variations tarifaires selon les dates et les destinations), les applications de comparaison de voyages (agrégation des offres provenant de plusieurs agences de voyages en ligne) et l'analyse comparative de la concurrence (compréhension de la position des tarifs d'un établissement par rapport aux hôtels voisins). Les données extraites d'Expedia peuvent également alimenter des moteurs de recommandation et des outils de voyage destinés aux clients.
Considérations juridiques et éthiques avant de commencer
Avant d'écrire la moindre ligne de code, passez en revue cette liste de contrôle :
- Vérifiez le fichier robots.txt — Rendez-vous sur https://www.expedia.com/robots.txt et respectez les chemins d'accès interdits. (Vérifiez au moment de la publication — les directives peuvent changer.)
- Consultez les conditions d'utilisation — Les conditions d'utilisation d'Expedia restreignent l'accès automatisé. La recherche à titre personnel relève d'une catégorie de risque différente de la revente commerciale. Consultez un avocat en cas de doute.
- N'extrayez que des données publiques — Les listes d'hôtels visibles par tout visiteur anonyme sont publiques. N'essayez pas d'accéder à du contenu réservé aux titulaires d'un compte ni de soumettre des formulaires automatiquement.
- Limitez la fréquence de vos requêtes — Ajoutez des délais délibérés (minimum 2 à 5 secondes entre chaque requête). Surcharger un serveur est à la fois contraire à l'éthique et le moyen le plus rapide de se faire bloquer.
- Conservez les données de manière responsable — Ne conservez que ce dont vous avez besoin et évitez de republier le contenu extrait d'une manière qui entre en concurrence directe avec les propres produits d'Expedia.
Pourquoi Expedia est difficile à scraper
Expedia charge dynamiquement ses listes d'hôtels à l'aide de JavaScript, ce qui signifie qu'un scraper statique récupérant du HTML brut passera à côté du contenu réel. Le serveur envoie une page presque vide ; le navigateur exécute le JavaScript pour récupérer et afficher les fiches d'hôtels. Si vous n'exécutez pas ce JavaScript, vous ne voyez pas les données. Le rendu JavaScript est un défi fondamental pour le web scraping, et Expedia en est l'un des exemples les plus complexes.
Au-delà du rendu, Expedia déploie le blocage d'IP (les requêtes répétées provenant de la même IP déclenchent des interdictions), l'empreinte digitale du navigateur (les navigateurs sans interface utilisateur sont détectables via des API manquantes et des anomalies de synchronisation) et des noms de classes dynamiques (les classes CSS sont générées au moment de la compilation et changent à chaque déploiement, rendant inopérants les sélecteurs codés en dur sans avertissement).
Une simple requête renvoie la navigation et les métadonnées, mais aucune liste d'hôtels. C'est cette lacune qui explique pourquoi vous avez besoin soit d'un navigateur sans interface graphique, soit d'une API de scraping.
Choisir votre approche : navigateur sans interface utilisateur DIY vs API de scraping
L'approche DIY vous offre une flexibilité maximale, mais vous oblige à configurer un navigateur sans interface graphique, à gérer un pool de proxys et à maintenir l'environnement à mesure que les versions des navigateurs évoluent. Une API de scraping se charge de tout cela : vous envoyez une requête avec votre URL cible et vos règles d'extraction ; l'API gère le rendu, la rotation des proxys et les tentatives de reconnexion.
Pour la plupart des cas d'utilisation du scraping d'Expedia (surveillance des prix, extraction périodique de données, recherche), l'approche par API est plus rapide à mettre en place et moins coûteuse à maintenir à long terme. Vous évitez ainsi la charge liée à la mise à jour des binaires de navigateur, à la recherche de proxys résidentiels fiables et au débogage des erreurs de rendu spécifiques à l'environnement. Le compromis est que vous dépendez d'un service externe ; évaluez donc les garanties de disponibilité et les niveaux de tarification avant de vous engager dans l'une ou l'autre voie.
Configuration de l'environnement et prérequis
Vous aurez besoin de Python 3.8 ou une version ultérieure (utilisez la commande python --version pour vérifier). Installez les bibliothèques requises :
pip install webscrapingapi pandas
webscrapingapi est le client Python officiel de WebScrapingAPI — il encapsule la couche de requêtes HTTP et gère l'authentification. pandas gère le nettoyage des données et l'exportation au format CSV.
Récupérez votre clé API depuis le tableau de bord WebScrapingAPI et stockez-la en tant que variable d'environnement plutôt que de l'intégrer en dur dans votre script :
export WSAPI_KEY="your_api_key_here"
Chargez-la ensuite dans Python avec os.environ.get("WSAPI_KEY"). Conservez votre fichier de script (par exemple, expedia.py) dans un dossier de projet dédié afin que les chemins relatifs pour l'exportation CSV fonctionnent de manière cohérente d'une exécution à l'autre. Le module os intégré à Python est tout ce dont vous avez besoin — aucune installation supplémentaire n'est requise. Pour une introduction plus complète aux modèles de scraping basés sur Python, consultez notre guide de scraping web en Python.
Comment identifier les bons sélecteurs CSS sur Expedia
C'est l'étape que la plupart des tutoriels ignorent. Voici un guide pratique concret sur DevTools.
- Ouvrez une page de recherche Expedia et laissez-la se charger complètement.
- Cliquez avec le bouton droit sur une fiche d'hôtel → « Inspecter » pour ouvrir DevTools avec l'élément mis en surbrillance.
- Identifiez le conteneur de la fiche de l'établissement — les balises répétitives <div> ou <article> qui encadrent chaque résultat d'hôtel. Il s'agit de votre sélecteur racine ; il devrait apparaître une fois par fiche.
- Explorez les éléments enfants — trouvez les éléments contenant le nom de l'hôtel, le prix, la note et le nombre d'avis. Cliquez avec le bouton droit sur chacun d'eux → « Copier > Copier le sélecteur ».
- Vérifiez l'unicité — exécutez document.querySelectorAll("VOTRE_SÉLECTEUR") dans la console DevTools et assurez-vous que le nombre correspond au nombre de fiches d'hôtels.
- Utilisez des sélecteurs relatifs — les sélecteurs enfants doivent être relatifs au conteneur de la fiche, et non absolus par rapport à la racine du document.
Important : les noms de classes CSS sur Expedia sont générés dynamiquement et changent lors des déploiements du site. Vérifiez toujours les sélecteurs sur une page en ligne avant une mise en production. Notre aide-mémoire sur les sélecteurs CSS couvre en détail la syntaxe et la spécificité des sélecteurs.
Création du scraper de recherche d'hôtels Expedia
Le cœur du scraper repose sur deux dictionnaires — extract_rules et js_scenario — transmis en tant que paramètres au client API. Ensemble, ils indiquent à l'API ce qu'il faut extraire et comment afficher la page avant que l'extraction ne commence. La configuration correcte de ces deux objets constitue l'étape la plus importante de l'ensemble du workflow Python de scraping d'Expedia, car tous les résultats en aval en dépendent.
Définition des règles d'extraction et des instructions de rendu JS
extract_rules indique à l'API quels sélecteurs CSS utiliser et quoi renvoyer. js_scenario fournit des instructions au navigateur headless intégré : wait met l'exécution en pause pendant un nombre donné de millisecondes ; evaluate exécute du code JavaScript personnalisé dans le contexte de la page (pour le défilement, les clics, 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}
]}
Le schéma d'attente en deux phases — pause avant le défilement, puis nouvelle pause après — est délibéré. Expedia utilise le rendu JavaScript pour charger les fiches d'hôtels de manière différée à mesure que la fenêtre d'affichage descend sur la page. Sauter l'une ou l'autre de ces pauses risque de renvoyer une liste incomplète d'établissements, en particulier sur des connexions lentes ou lorsque la destination comporte de nombreux résultats.
Effectuer la requête API et traiter les réponses
Paramètres clés : wait_for attend qu'un sélecteur CSS apparaisse avant l'extraction ; country_code définit le pays de sortie du proxy pour la localisation des prix ; premium_proxy active la rotation des proxys résidentiels.
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
Une liste d'hôtels vide avec un statut 200 signifie presque toujours que vos sélecteurs CSS ont dérivé. Le statut HTTP 500 d'Expedia est souvent temporaire — construisez une logique de réessai avec un recul exponentiel au niveau du site d'appel. Notez que le paramètre page est déjà intégré à la signature de la fonction, ce qui facilite l'appel de cette fonction à l'intérieur d'une boucle de pagination dans la section suivante.
Extraction de plusieurs pages de résultats d'hôtels
Expedia utilise un paramètre de requête de page prévisible, ce qui rend la pagination simple. La boucle ci-dessous s'itère jusqu'à ce qu'elle obtienne un ensemble de résultats vide ou atteigne la limite de pages :
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)
Le paramètre de délai est important. Les requêtes en rafale déclenchent systématiquement des blocages d'IP. Une pause de 3 secondes est un minimum raisonnable ; aléatorisez dans une plage de 2 à 5 secondes pour les exécutions plus longues afin d'éviter des schémas de timing prévisibles.
La profondeur des résultats de recherche d'Expedia varie selon la destination et la plage de dates. Plutôt que de supposer un nombre de pages fixe, la condition de sortie anticipée de la boucle (if not hotels: break) gère la fin de manière propre : lorsque l'API renvoie une liste vide, vous avez atteint la fin des résultats.
Nettoyage et exportation des données au format CSV
Le texte brut doit être nettoyé avant l'exportation — les prix, les notes et le nombre d'avis sont fournis sous forme de chaînes non typées. Normalisez-les d'abord :
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)
Les colonnes CSV sont typées — price_usd (float), rating (float), review_count (int) — et prêtes à être analysées sans post-traitement manuel.
Référence complète du script
Toutes les fonctions (fetch, scrape, to_csv) ont été définies dans les sections ci-dessus. Regroupez-les dans un seul fichier nommé expedia.py, définissez votre variable d'environnement WSAPI_KEY, puis lancez l'exécution complète à l'aide du point d'entrée ci-dessous.
if __name__ == "__main__":
to_csv(scrape("Rome, Italy", "2026-10-05", "2026-10-10"))
Exécutez avec python expedia.py. Les résultats sont enregistrés dans le fichier expedia_hotels.csv de votre répertoire de travail, propres et prêts à être analysés immédiatement.
Maintenir votre scraper lorsque Expedia modifie sa mise en page
L'une des raisons les plus courantes pour lesquelles un scraper Expedia cesse de fonctionner est la dérive des sélecteurs : Expedia met régulièrement à jour son interface, les noms de classes changent, les hiérarchies d'éléments évoluent, et les sélecteurs qui fonctionnaient le mois dernier cessent subitement de renvoyer des données.
Comment détecter la dérive des sélecteurs : votre scraper s'exécute sans erreur mais renvoie une liste vide. Des valeurs « None » apparaissent simultanément dans tous les champs nettoyés. Ce sont des signes fiables indiquant que les sélecteurs ont changé.
Le processus de réidentification :
- Ouvrez Expedia dans un navigateur et lancez une nouvelle recherche.
- Cliquez avec le bouton droit sur une fiche d'hôtel → Inspecter.
- Comparez le DOM actuel à vos sélecteurs extract_rules. Trouvez l'élément sémantique correspondant (titre du nom de l'hôtel, conteneur de prix) même si les noms de classe ont changé.
- Mettez à jour CARD_SELECTOR et les sélecteurs enfants, puis effectuez un test sur une seule page avant de réactiver la boucle complète.
Surveillance allégée : planifiez une exécution quotidienne de test canary vers une destination fixe. Déclenchez une alerte en cas de résultats nuls pour une visibilité en temps réel sur la dérive des sélecteurs. Pour en savoir plus sur l'impact des sites riches en JavaScript sur la stabilité des sélecteurs, consultez notre guide sur l'impact du JavaScript sur le web scraping.
Mise à l'échelle et bonnes pratiques
- Limitez les requêtes. Un délai de 3 à 5 secondes entre les pages est le minimum requis ; aléatoirez ce délai pour éviter des schémas temporels prévisibles.
- Mettez en place un recul exponentiel. En cas de réponses HTTP 500 ou 429, doublez le délai à chaque nouvelle tentative (5 s, 10 s, 20 s).
- Faites tourner le code pays. Faites correspondre le pays de sortie à votre marché cible pour une localisation précise des prix.
- Planifiez des exécutions récurrentes. Utilisez cron, Airflow ou une fonction cloud pour la surveillance des prix. Enregistrez les résultats avec des horodatages pour suivre les changements au fil du temps.
- Enregistrez les codes d'état et le nombre de résultats par page. En cas de problème, vous voudrez savoir exactement quelle page et quelle destination ont déclenché l'échec.
Pour en savoir plus sur la manière d'éviter les blocages d'IP à grande échelle, consultez notre guide sur la suppression des blocages d'IP lors du web scraping.
Points clés
- Le rendu JavaScript est indispensable pour Expedia. Une requête HTTP statique ne renverra pas les listes d'hôtels — vous avez besoin d'un navigateur headless ou d'une API de scraping qui effectue le rendu JS pour vous.
- Les sélecteurs CSS dérivent. Expedia met régulièrement à jour son front-end. Intégrez la détection de la dérive des sélecteurs dans votre pipeline et apprenez à réidentifier les sélecteurs avec DevTools lorsqu'ils ne fonctionnent plus.
- La pagination nécessite une boucle. Utilisez le paramètre de requête de page d'Expedia et arrêtez-vous lorsque vous obtenez un ensemble de résultats vide — ne partez pas du principe qu'il y a un nombre fixe de pages.
- Nettoyez vos données avant de les exporter. Supprimez les symboles de devise, analysez les notes numériques et convertissez le nombre d'avis en nombres entiers au moment de l'extraction.
- Limitez le débit et réglez la vitesse. Introduire des délais délibérés entre les requêtes est à la fois éthiquement correct et pratiquement nécessaire pour éviter les blocages.
FAQ
Comment savoir si mon scraper Expedia ne fonctionne plus en raison d'un changement de structure HTML ?
Le signe le plus évident est une liste de résultats vide : l'appel API aboutit (HTTP 200) mais ne renvoie aucun enregistrement d'hôtel. Un autre signe est la présence de valeurs « None » dans tous les champs nettoyés. Configurez une exécution « canary » quotidienne vers une destination fixe et activez une alerte en cas de résultats nuls.
Quelle est la différence entre le scraping d'une page de résultats de recherche Expedia et d'une page de détails d'hôtel ?
Une page de résultats de recherche renvoie des données récapitulatives — nom, prix, note, emplacement — sous forme de liste paginée. Une page de détails d'hôtel contient des données plus riches pour un établissement : listes d'équipements, répartition des types de chambres, conditions d'annulation et texte des avis. Les sélecteurs et les exigences de rendu diffèrent entre les deux.
Comment éviter d'atteindre les limites de débit d'Expedia lors du scraping de grands ensembles de données ?
Utilisez des délais aléatoires plutôt que des intervalles fixes — un intervalle uniforme est plus facile à détecter pour les systèmes anti-bot. Répartissez les listes de destinations sur plusieurs heures ou jours, et utilisez un délai d'attente exponentiel pour les réponses 429 et 500.
Puis-je extraire les avis et les notes d'Expedia en même temps que les données tarifaires en une seule requête ?
Oui, si la note et le nombre d'avis apparaissent sur la page de résultats de recherche, ajoutez des sélecteurs pour ces deux champs à votre dictionnaire extract_rules. Le texte complet des avis se trouve sur la page de détails de l'hôtel et nécessite une requête distincte.
Conclusion
Le scraping des données d'hôtels d'Expedia en Python est possible, mais cela nécessite plus qu'une simple requête HTTP. Vous avez besoin d'un rendu JavaScript pour voir les annonces réelles, d'une rotation de proxys fiable pour éviter les blocages d'IP, et d'une stratégie claire pour identifier et maintenir les sélecteurs CSS à mesure que le frontend d'Expedia évolue.
L'approche présentée dans ce guide — qui consiste à utiliser une API de scraping pour gérer la couche d'infrastructure, combinée à des paramètres explicites extract_rules et js_scenario — vous permet d'obtenir un scraper fonctionnel plus rapidement que la mise en place et la maintenance d'une pile de navigateurs headless locaux. La boucle de pagination, les fonctions de nettoyage des données et la stratégie de surveillance de la dérive des sélecteurs en font une solution prête pour la production, et non un simple prototype.
Si vous souhaitez éviter complètement la charge liée à l'infrastructure, l'API de scraping de WebScrapingAPI gère le rendu JavaScript, la rotation des proxys et la résolution des CAPTCHA derrière un seul point de terminaison — vous pouvez ainsi vous concentrer sur les données, et non sur l'infrastructure. Explorez nos cas d'utilisation du scraping dans le secteur du voyage et de l'hôtellerie pour découvrir d'autres modèles de collecte de données OTA, ou consultez nos guides connexes sur le scraping de Booking.com et le scraping des annonces Airbnb.




