Expedia est l'une des plus grandes marketplaces de voyage en ligne, et les données d'hôtels, de vols et d'activités qu'elle affiche sont genuinement utiles pour faire de la recherche tarifaire, surveiller un marché ou construire un produit de voyage. Le problème est qu'Expedia rend ses résultats côté client et se défend activement contre les bots, de sorte qu'une simple requête HTTP vous renvoie une coquille vide. Ce guide vous montre comment scraper Expedia avec JavaScript : une petite application Node fonctionnelle qui rend la page, analyse les annonces d'hôtels avec Cheerio, gère la pagination et écrit les résultats en CSV.
Pour rester honnête et défendable, l'ensemble du tutoriel se limite aux données publiques : les annonces d'hôtels et de vols, les prix par nuit, la disponibilité, les notes et le nombre d'avis que tout le monde peut voir sans se connecter. Il ne touche pas aux comptes utilisateurs, aux contenus réservés à une connexion, aux actions de réservation ni aux données personnelles. La section éthique et CGU près de la fin n'est pas du remplissage, lisez-la avant de pointer ceci sur un volume de production.
Pourquoi scraper Expedia pour des données de voyage
Les prix publics de voyage bougent en permanence, et une seule page vue vous dit seulement ce qu'un tarif affiche en ce moment. Scraper les annonces publiques d'Expedia vous permet de suivre les tarifs par nuit et la disponibilité dans le temps, ce dont dépendent la surveillance des prix, l'analyse concurrentielle et la recherche sur la demande. Pour un ingénieur, la valeur est dans les données structurées : transformer une page de recherche rendue en lignes propres que vous pouvez interroger, visualiser ou alimenter dans un modèle.
C'est la même forme de problème que n'importe quel travail de scraping web e-commerce. La différence est que la pile anti-bot d'Expedia est plus agressive que celle d'une boutique typique, donc l'approche doit en tenir compte dès la première requête.
Comprendre la cible : l'URL de recherche d'hôtels Expedia
Expedia expose plusieurs surfaces de recherche (vols, voitures, croisières, activités), mais la recherche d'hôtels est la plus propre à travailler et celle que ce guide utilise. Ses résultats se trouvent à une URL prévisible dont les paramètres de requête correspondent directement au formulaire de recherche, vous pouvez donc construire n'importe quelle recherche par programmation sans piloter l'interface utilisateur.
Voici un exemple concret : deux adultes, une chambre, à Dubaï, pour un séjour de quatre nuits.
https://www.expedia.com/Hotel-Search?adults=2&rooms=1&destination=Dubai&startDate=2026-07-10&endDate=2026-07-14
Les paramètres importants :
- destination le lieu de recherche, encodé en URL (une ville, une région ou un nom de propriété).
- adults le nombre d'adultes.
- rooms le nombre de chambres à tarifer.
-
startDate et endDate la fenêtre de séjour, au format
YYYY-MM-DD.
Construisez l'URL avec les paramètres qui vous intéressent et vous avez une cible répétable. Variez la destination et les dates dans une boucle et vous avez un job de surveillance des prix.
Pourquoi une simple requête échoue ici
Si vous demandez cette URL avec un client HTTP brut, vous obtenez une réponse avec le statut 200 et presque aucune donnée d'hôtel dans le corps. Deux choses jouent contre vous. Premièrement, Expedia rend ses annonces dans le navigateur avec JavaScript, donc le HTML initial est une coquille qui n'est remplie qu'après l'exécution des scripts de la page dans un navigateur. Deuxièmement, Expedia détecte rapidement le trafic automatisé : les IP de centres de données et les schémas de requêtes qui ne ressemblent pas à un vrai navigateur sont challengés ou bloqués avant d'accéder au contenu rendu.
Un scraper Expedia fonctionnel a donc besoin de deux choses en une seule requête : un navigateur qui rend réellement la page, et une IP que la plateforme considère comme un vrai visiteur. Vous pouvez assembler cela vous-même avec un navigateur sans interface graphique et un pool de proxies résidentiels rotatifs, mais les assembler et les maintenir en bon état représente l'essentiel du travail. La Crawling API Crawlbase regroupe les deux en un seul appel : vous lui envoyez l'URL avec un token JavaScript, elle rend la page derrière une IP de confiance, et vous retourne le HTML final prêt à analyser.
Crawlbase propose deux types de tokens. Le token normal récupère le HTML statique ; le token JavaScript (JS) rend d'abord la page dans un vrai navigateur. Expedia est rendu côté client, donc vous avez besoin du token JS ici. Utiliser le token normal renvoie la même coquille vide qu'une simple requête.
Configurer le projet
Vous avez besoin de Node.js et npm installés. Confirmez les deux, puis créez un projet et installez les bibliothèques.
node --version npm --version mkdir expedia-scraper && cd expedia-scraper npm init -y npm install cheerio crawlbase csv-writer
Trois dépendances font le travail : crawlbase est le client pour la Crawling API, cheerio analyse le HTML renvoyé avec une API similaire à jQuery côté serveur, et csv-writer sérialise vos résultats. Si vous souhaitez un stockage interrogeable plutôt que des fichiers plats, ajoutez sqlite3 et écrivez les lignes dans une table ; le chemin CSV ci-dessous couvre le cas courant.
Vous avez également besoin d'un compte Crawlbase et d'un token JS, que vous obtenez depuis le tableau de bord après l'inscription. Insérez-le dans le code partout où vous voyez YOUR_CRAWLBASE_JS_TOKEN.
Récupérer le HTML rendu
Commencez par obtenir la page finale. Vous passez deux options importantes pour un site comme Expedia : ajax_wait indique à l'API d'attendre le chargement du contenu asynchrone, et page_wait maintient l'attente un nombre fixe de millisecondes après le chargement pour que les annonces à rendu tardif aient le temps d'apparaître. Cinq secondes est un bon point de départ ; augmentez si les résultats reviennent maigres.
const { CrawlingAPI } = require('crawlbase') const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_JS_TOKEN' }) const options = { ajax_wait: true, page_wait: 5000, } const expediaURL = 'https://www.expedia.com/Hotel-Search?adults=2&rooms=1&destination=Paris&startDate=2026-07-10&endDate=2026-07-14' async function fetchExpediaHTML(url) { const response = await api.get(url, options) return response.body } fetchExpediaHTML(expediaURL).then((html) => console.log(html))
Exécutez avec node expedia-scraper.js et vous devriez voir le vrai balisage avec des cartes d'hôtels, pas la coquille vide qu'une simple requête renvoie. Cela confirme que le rendu fonctionne avant d'écrire un seul sélecteur.
Expedia nécessite une page rendue derrière une IP de confiance, en un seul appel. La Crawling API prend un token JS, exécute la page dans un vrai navigateur, fait tourner les IP résidentielles côté serveur, et vous remet le HTML final, pour que vous évitiez de gérer une flotte sans interface et un pool de proxies. Testez-la sur une recherche publique d'hôtels avec le niveau gratuit.
Analyser les annonces d'hôtels avec Cheerio
Le HTML en main, chargez-le dans Cheerio et parcourez les cartes de propriétés. Chaque carte porte les champs souhaités : nom de l'hôtel, prix par nuit, prix total, note et nombre d'avis. Inspectez la page en direct dans les outils de développement de votre navigateur pour trouver les sélecteurs actuels, puis associez chaque champ à l'un d'eux.
const cheerio = require('cheerio') function extractHotelDetails(html) { const $ = cheerio.load(html) const hotels = [] $('div[data-stid="property-listing-results"] .uitk-card').each((i, el) => { hotels.push({ name: $(el).find('h3.uitk-heading').text().trim(), pricePerNight: $(el).find('[data-test-id="price-summary"] .uitk-text').first().text().trim(), totalPrice: $(el).find('[data-test-id="price-summary"] .uitk-text').last().text().trim(), rating: $(el).find('.uitk-badge .uitk-badge-base-text').text().trim(), reviews: $(el).find('.uitk-text[aria-hidden="false"]').last().text().trim(), }) }) return hotels }
Les noms de classes d'Expedia (les préfixes uitk-* et les attributs data-stid) changent sans préavis. Traitez les sélecteurs ci-dessus comme un modèle de départ, non comme un contrat. Quand l'extraction renvoie des chaînes vides, ré-inspectez la page en direct et mettez à jour les sélecteurs. C'est une maintenance normale pour tout scraper en production, pas le signe que quelque chose est cassé.
Reliez la récupération et l'analyse dans une fonction main pour avoir un script exécutable unique.
async function main() { const html = await fetchExpediaHTML(expediaURL) const hotels = extractHotelDetails(html) console.log(hotels) } main().catch((err) => console.error('Scrape failed:', err))
À quoi ressemble la sortie
Exécutez le script complet et vous obtenez un tableau d'objets d'hôtels structurés. Un échantillon réduit :
[ { "name": "OKKO Hotels Paris Rueil-Malmaison", "pricePerNight": "$118", "totalPrice": "$542 total", "rating": "9.0", "reviews": "286 reviews" }, { "name": "Grand Hotel Leveque", "pricePerNight": "$174", "totalPrice": "$782 total", "rating": "8.4", "reviews": "1,004 reviews" }, { "name": "citizenM Paris Opera", "pricePerNight": "$192", "totalPrice": "$871 total", "rating": "9.6", "reviews": "76 reviews" } ]
Gérer la pagination
Expedia charge son premier lot de résultats puis en révèle davantage derrière un bouton "Afficher plus de résultats" plutôt qu'une liste de pages numérotées. La Crawling API peut cliquer ce bouton pour vous avec l'option css_click_selector : passez un sélecteur CSS valide et encodé en URL, et l'API le clique après le rendu de la page, de sorte que les annonces supplémentaires se chargent avant que le HTML ne soit renvoyé.
const options = { ajax_wait: true, page_wait: 10000, css_click_selector: encodeURIComponent('button[data-stid="show-more-results"]'), }
Substituez cela dans votre objet options et la prochaine exécution retournera plus d'hôtels qu'auparavant. Accordez un peu plus de marge à page_wait lors du clic, puisque les résultats supplémentaires ont besoin de temps pour se rendre après le déclenchement du clic.
Stocker les résultats en CSV
Loguer dans la console convient pendant l'itération, mais vous souhaitez les données sur disque. La bibliothèque csv-writer mappe chaque clé d'objet sur une colonne et ajoute vos lignes en quelques lignes de code.
const createCsvWriter = require('csv-writer').createObjectCsvWriter function saveToCSV(data) { const writer = createCsvWriter({ path: 'hotels.csv', header: [ { id: 'name', title: 'Name' }, { id: 'pricePerNight', title: 'Price Per Night' }, { id: 'totalPrice', title: 'Total Price' }, { id: 'rating', title: 'Rating' }, { id: 'reviews', title: 'Reviews' }, ], }) return writer.writeRecords(data).then(() => console.log('Saved hotels.csv')) }
Appelez saveToCSV(hotels) depuis main à la place du log, et chaque exécution écrit un fichier hotels.csv soigné que vous pouvez ouvrir dans n'importe quel tableur ou charger dans un pipeline. Si vous préférez interroger les données avec SQL, écrivez les mêmes lignes dans une table SQLite avec sqlite3 ; l'analyse reste identique.
Rester non bloqué
Même avec le rendu géré, Expedia surveille le trafic ressemblant à un scraper. Quelques habitudes maintiennent une exécution saine, et elles s'appliquent à toute cible commerciale difficile.
- Pacez vos requêtes. Bombarder la même recherche dans une boucle serrée est le moyen le plus rapide d'être limité. Répartissez les requêtes et variez vos paramètres de recherche.
- Misez sur la rotation. Un pool de proxies résidentiels répartit les requêtes sur de nombreuses IP d'utilisateurs réels, de sorte qu'aucune adresse ne déclenche une limite de débit. La Crawling API gère cela pour vous ; si vous construisez votre propre infrastructure, c'est la partie à bien faire.
- Lisez les codes de statut. Une exécution qui commence à renvoyer des challenges ou des erreurs vous indique que le taux actuel ou le niveau d'IP n'est plus suffisant. Traitez les codes d'erreur de statut de proxy comme un signal, pas comme du bruit.
Pour le guide plus complet à ce sujet, voir comment scraper des sites web sans être bloqué. Si vous souhaitez comparer cette approche avec une construction basée sur un navigateur sans interface, le scraping web avec Python et Selenium décrit cette pile.
La partie honnête : CGU et légalité
Scraper un grand site de voyage commercial se situe dans une zone grise juridique, et la réponse à "est-ce autorisé" dépend des conditions d'utilisation de la plateforme, de votre juridiction et de ce que vous faites des données. Les conditions d'Expedia restreignent l'accès automatisé, donc le scraping peut aller à l'encontre de ces conditions quelle que soit la prudence de vos outils. Aucun code ici ne change cela ; il ne fait que rendre la partie technique fonctionnelle.
Quelques lignes à respecter. Collectez uniquement des données publiques : les annonces, les prix, la disponibilité et les notes que tout le monde peut voir sans compte. Respectez le robots.txt du site et ses attentes en matière de débit déclarées, et gardez votre volume de requêtes assez bas pour ne pas surcharger les serveurs de qui que ce soit. Si vous prévoyez de réutiliser les données commercialement, obtenez une autorisation ou un accord de données officiel plutôt que de supposer que le silence est un consentement. Et ne collectez jamais de données personnelles, y compris tout ce qui est lié à des comptes utilisateurs individuels.
Ce guide est délibérément limité aux données d'annonces publiques parce que c'est la ligne qui rend le travail défendable. Il ne couvre rien derrière une connexion, ni les données de compte ou de profil, ni les avis liés à des personnes identifiables, ni les actions de réservation d'aucune sorte. Si votre projet nécessite plus que des annonces publiques, la bonne démarche est une API officielle ou un accord de données avec Expedia, pas un scraper plus ingénieux. Pour en savoir plus sur la façon dont l'accès géré diffère du scraping brut, qu'est-ce qu'un proxy API est une lecture utile.
Points clés
- Expedia est rendu côté client. Une simple requête renvoie une coquille vide, vous devez donc rendre la page avant de l'analyser.
-
Vous avez besoin du rendu et d'une IP de confiance ensemble. La Crawling API avec un token JS fait les deux en un seul appel ;
ajax_waitetpage_waitcontrôlent la durée d'attente du contenu. - Cheerio effectue l'extraction. Associez le nom, le prix, la note et les avis aux sélecteurs actuels, et anticipez leur dérive dans le temps.
-
La pagination est un clic. Utilisez
css_click_selectorpour déclencher "afficher plus de résultats" avant que le HTML ne soit renvoyé. - Restez sur des données publiques. Respectez les CGU et le robots.txt d'Expedia ; pas de comptes, pas de données personnelles, pas d'actions de réservation.
Foire aux questions
Pourquoi une simple requête ne renvoie-t-elle aucune donnée d'hôtel depuis Expedia ?
Parce qu'Expedia rend ses annonces côté client avec JavaScript. Le HTML initial est une coquille qui n'est remplie qu'après l'exécution des scripts de la page dans un navigateur, donc une requête HTTP brute renvoie le statut 200 avec les champs d'hôtels vides. Pour obtenir de vraies données, vous devez d'abord rendre la page, ce que le token JS de la Crawling API gère pour vous.
Ai-je besoin du token normal ou du token JS pour Expedia ?
Le token JS. Le token normal récupère le HTML statique, qui sur Expedia est la même coquille vide qu'une simple requête renvoie. Le token JS rend la page dans un vrai navigateur avant de retourner le HTML, de sorte que les annonces sont présentes lorsque Cheerio les analyse.
Mes sélecteurs Cheerio renvoient des chaînes vides. Qu'est-ce qui a changé ?
Presque certainement le balisage d'Expedia. Ses noms de classes uitk-* et ses attributs data-stid changent sans préavis, donc des sélecteurs qui fonctionnaient le mois dernier peuvent se casser. Ré-inspectez une page de recherche en direct dans les outils de développement de votre navigateur et mettez à jour les sélecteurs. La maintenance périodique des sélecteurs est normale pour tout scraper en production.
Comment obtenir plus que la première page de résultats ?
Expedia révèle des hôtels supplémentaires derrière un bouton "Afficher plus de résultats" plutôt que des pages numérotées. Passez le sélecteur CSS de ce bouton à l'option css_click_selector de la Crawling API, encodé en URL, et l'API le clique après le rendu pour que les annonces supplémentaires se chargent avant le retour du HTML. Accordez un peu plus de temps à page_wait quand vous faites cela.
Comment éviter d'être bloqué lors du scraping d'Expedia ?
Gardez votre taux de requêtes par IP bas, variez vos paramètres de recherche plutôt que de boucler sur la même URL, et routez via des IP résidentielles rotatives pour qu'aucune adresse ne déclenche une limite de débit. La Crawling API gère la rotation et un pool d'IP de confiance pour vous ; si vous construisez votre propre infrastructure, c'est la partie dans laquelle investir. Surveillez les codes de statut et reculez quand vous commencez à voir des challenges.
Est-il légal de scraper Expedia ?
Cela dépend des conditions d'utilisation d'Expedia, de votre juridiction et de votre objectif, et leurs conditions restreignent l'accès automatisé. Limitez-vous strictement aux données d'annonces publiques, respectez robots.txt et les attentes de débit, et ne touchez jamais aux comptes, aux données personnelles ni aux actions de réservation. Pour une réutilisation commerciale, obtenez une autorisation ou un accord de données officiel plutôt que de vous fier à un scraper.
Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.
Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.
