Sur Radisson, oui. Les prix proviennent d'une API de tarifs en JSON. Un scraper qui n'envoie que les requêtes de la page, d'Akamai et de l'API, avec l'empreinte TLS et l'ordre des en-têtes de Chrome, a obtenu des tarifs pour 490 des 500 recherches prévues lors de notre test.
Comment scraper les prix d'hôtels sans navigateur : rétro-ingénierie du flux de requêtes de Radisson
Un navigateur est la solution de facilité pour scraper un site protégé par un système de détection de bots. C'est aussi la plus lente et la plus coûteuse. Dans cet article, nous construisons l'autre type de scraper pour Radisson Hotels : un scraper qui n'envoie que les requêtes HTTP qui renvoient les données, et qui franchit Akamai Bot Manager sans jamais lancer de navigateur.
Radisson est un bon cas d'école pour le scraping de prix d'hôtels. Les prix ne figurent pas dans le HTML : ils proviennent d'une API JSON que la page appelle après son chargement, et le site exécute trois contrôles Akamai avant de laisser passer ces appels.
Nous allons montrer comment nous y sommes parvenus. Nous avons enregistré une vraie session de navigateur, repéré les quatre appels d'API qui comptent parmi 148 requêtes vers Radisson, déterminé quels contrôles Akamai le site exécute et à quel endroit, puis reconstruit tout le flux en Go. Nous l'avons ensuite fait passer par les proxies résidentiels de Massive au Royaume-Uni.
Points clés à retenir
- Les résultats de recherche de Radisson proviennent de quatre endpoints JSON. Deux d'entre eux, la recherche de ville et l'appel des tarifs, sont ceux où Akamai bloque une session suspecte.
- Le site exécute trois contrôles Akamai au chargement de la page : SBSD, le sensor
_abcket un challenge pixel. Un scraper basé sur les requêtes doit répondre aux trois, dans le même ordre que la page. - Sur 100 sessions passant par des IP résidentielles britanniques de Massive, toutes ont franchi Akamai, et 490 des 500 recherches prévues ont renvoyé des tarifs (98 %).
- Franchir Akamai a coûté 520 KB de trafic proxy par session, dont les trois quarts pour deux fichiers JavaScript. Une recherche coûtait ensuite 12,5 KB.
- Une session qui a franchi Akamai continue de fonctionner. Cinq sessions ont effectué 100 recherches chacune sans être arrêtées une seule fois, pour 18 KB par recherche, tout compris.
Pourquoi un scraper basé sur les requêtes
Un scraper basé sur les requêtes se passe de tout ce que fait un navigateur et dont le scraper n'a pas besoin : le rendu, les images, les polices, les cartes, les outils d'analyse et le JavaScript propre à la page. Il ne reste qu'une poignée de requêtes HTTP : il est donc plus rapide, il fait tourner des centaines de sessions sur une seule machine et il fait transiter beaucoup moins de trafic par vos proxies.
Le revers de la médaille, c'est qu'il faut savoir exactement quelles requêtes envoyer. La détection de bots ne regarde pas seulement ce que vous demandez. Elle regarde comment vous le demandez : le handshake TLS, l'ordre des en-têtes HTTP/2, les cookies et les challenges JavaScript auxquels un vrai navigateur répond en arrière-plan. Le travail consiste donc d'abord à faire la rétro-ingénierie du flux de requêtes.
Étape 1 : enregistrer une vraie session
Nous avons commencé par faire une recherche sur Radisson dans Chrome, en faisant passer le trafic par powhttp, un proxy de débogage HTTP. Nous avons ouvert la page d'accueil, tapé « Amsterdam », choisi des dates et lancé la recherche.
Nous utilisons powhttp plutôt que les DevTools du navigateur pour une raison précise. Les DevTools vous montrent une version nettoyée de chaque requête. powhttp montre ce qui a réellement transité sur le réseau : l'ordre réel des en-têtes, les pseudo-en-têtes HTTP/2, l'empreinte TLS et chaque en-tête cookie envoyé par le navigateur. Ce sont précisément ces détails que vérifie la détection de bots, et qu'un scraper doit reproduire.
Cette seule recherche a généré 568 requêtes vers 46 hôtes. La plupart n'étaient que du bruit : Google Maps, outils d'analyse, bannière de consentement, images. En filtrant sur les domaines de Radisson, il restait 148 requêtes : des bundles JavaScript, des images, les scripts d'Akamai et 38 appels à des API JSON.
Étape 2 : trouver les requêtes qui renvoient les prix des hôtels
Les prix des hôtels ne figuraient pas dans le HTML. La page de résultats est une application JavaScript qui les charge après son rendu : nous avons donc passé en revue les 38 appels d'API et regardé ce que chacun renvoyait. Quatre d'entre eux contiennent tout ce dont une recherche a besoin :
GET /zimba-api/autocomplete/amsterdamrenvoie le lieu saisi, sous la forme d'un place ID Google.GET /zimba-api/search-result/hotels/search?placeId=…renvoie les codes des hôtels Radisson de ce lieu, avec leurs distances.POST /availability-api/multiple-hotels-ratesrenvoie le tarif le plus bas de chaque hôtel pour les dates et le nombre de voyageurs donnés.GET /zimba-api/search-result/hotels?code=…renvoie le nom, l'adresse, le nombre d'étoiles et la note des avis d'un hôtel.
L'appel des tarifs est celui qui compte. Il prend les dates, les voyageurs et la liste des codes d'hôtels dans un corps JSON, et renvoie un prix par hôtel, le total taxes comprises et le nombre de chambres restantes.
Les 34 autres appels chargent des libellés, des devises, des taux de change, des numéros de téléphone et du contenu lié au programme de fidélité pour la page. Un scraper n'a besoin d'aucun d'entre eux.
Étape 3 : reproduire les requêtes à l'identique
Nous avons ensuite examiné les en-têtes des quatre appels. La plupart sont ceux qu'envoie n'importe quel fetch de Chrome. Quelques-uns sont propres à Radisson :
x-source-of-business: desktopsur chaque appel, ainsi quex-version: V2etx-currencysur l'appel des tarifs.transactionid, une valeur de 40 caractères identique sur tous les appels d'une même visite.
Le transactionid ressemblait à un jeton émis par le serveur. Ce n'en est pas un. En cherchant transactionId dans le JavaScript du site avec powhttp, nous avons trouvé la fonction qui le génère : 40 chiffres aléatoires en base 15, créés au premier chargement de page et conservés dans le stockage de session du navigateur. Notre scraper en génère donc un par session, de la même façon.
Nous avons ensuite reproduit l'ordre des en-têtes. Chrome envoie les en-têtes dans un ordre fixe qui varie selon le type de requête : un chargement de page, un script, un fetch() et un envoi de formulaire ont chacun le leur. Nous avons recopié chaque ordre depuis l'enregistrement, y compris la position de l'en-tête cookie.
Pour la connexion elle-même, nous avons utilisé tls-client pour Go, qui ouvre les connexions avec l'empreinte TLS et HTTP/2 de Chrome. Le client HTTP standard de Go a une empreinte TLS qu'aucun navigateur ne possède, et l'empreinte TLS est l'une des premières choses qu'examine la détection de bots.
Étape 4 : trouver où intervient Akamai
Une fois les requêtes de données identifiées, restait à savoir ce qu'exige Akamai avant de les laisser passer. La réponse se trouve dans le HTML de la page d'accueil et dans les requêtes que la page envoie juste après son chargement.
La page inclut trois scripts Akamai :
- Le script sensor, sur un long chemin d'apparence aléatoire comme
/hLJOkaPJ/…/XMmkuCU0. Il collecte des données sur le navigateur et les renvoie en POST sur ce même chemin, à trois reprises. Chaque réponse met à jour le cookie_abck, et dès que le cookie contient~0~, Akamai considère la session comme valide. - Le script SBSD, sur un chemin similaire avec un paramètre
?v=. SBSD (State Based Scraping Detection) est un deuxième contrôle d'Akamai. La page envoie des payloads en POST sur ce même chemin pendant son exécution, trois fois dans notre enregistrement, et chaque réponse met à jour le cookiebm_s. Notre scraper en envoie deux, ce qui a suffi. - Le script pixel, sur
/akam/13/…, avec un nombre inscrit dans la page. La page y répond une seule fois par un envoi de formulaire sur le même chemin, préfixé depixel_.
Deux détails comptent ici. Les chemins des scripts changent : un scraper doit donc les lire dans chaque page d'accueil qu'il charge plutôt que de les coder en dur. Pendant nos tests, le chemin SBSD a changé plusieurs fois. Et sur certaines IP, la page d'accueil n'est pas du tout la page d'accueil. C'est une courte page de challenge SBSD, avec un paramètre t= sur le script. Le scraper doit répondre à ce challenge et recharger la page avant de pouvoir faire quoi que ce soit d'autre.
Aucun de ces contrôles ne peut être passé en recopiant un payload enregistré. Chaque payload est généré à partir du script, des cookies, du user agent et de l'IP de la session en cours. Nous les générons avec l'API Hyper Solutions : le scraper télécharge le script via son proxy, l'envoie à l'API avec les cookies et l'IP de la session, puis envoie en POST le payload renvoyé par l'API. Nous avons utilisé pour cela notre SDK Go.
Le fonctionnement de chacun de ces contrôles pris isolément, avec du code SDK en Go, Python et JavaScript, est détaillé dans notre guide pour passer Akamai sans navigateur headless.
Étape 5 : comparer votre scraper au navigateur
Avant de passer à l'échelle, nous avons aussi fait passer le trafic du scraper par powhttp, pour que ses requêtes arrivent à côté de celles du navigateur dans le même outil. La comparaison devient alors directe :
- L'empreinte TLS correspondait à celle de Chrome. Les hashes JA4 du navigateur et du scraper ne différaient que par la valeur GREASE aléatoire que Chrome insère volontairement.
- L'ordre des en-têtes correspondait pour chaque type de requête.
- L'en-tête cookie était scindé en un en-tête par cookie, comme le fait Chrome en HTTP/2.
Si quelque chose cloche, c'est ici que vous le verrez, et il est bien moins coûteux de le repérer à ce stade que plus tard, sous la forme d'un taux de blocage.
Le flux complet
Une fois tout assemblé, une session de scraping procède ainsi :
- Récupérer sa propre IP de sortie, car les payloads Akamai sont générés pour celle-ci.
- Charger la page d'accueil. S'il s'agit de la page de challenge SBSD, répondre au challenge et la recharger.
- Télécharger le script SBSD et envoyer deux payloads SBSD en POST.
- Télécharger le script sensor et envoyer les données du sensor jusqu'à ce que
_abckcontienne~0~, en répondant au challenge pixel après le premier envoi. - Rechercher : chercher la ville une seule fois, puis, pour chaque recherche, récupérer la liste des hôtels et envoyer la requête de tarifs.
Une session peut ensuite continuer à effectuer des recherches. Si Akamai répond à un appel protégé par un challenge, la session y répond et réessaie une fois.
Le code est open source : github.com/Hyper-Solutions/radisson-scraper. Il s'agit d'un package Go et d'un petit outil en ligne de commande qui affiche en JSON les hôtels et les tarifs d'une ville :
Configurer les proxies Massive
Chaque session a besoin de sa propre IP résidentielle, et elle doit la conserver jusqu'à la fin. Les payloads d'Akamai sont générés pour une IP donnée : si l'IP change en cours de session, la session n'est plus valide.
Le tableau de bord de Massive propose un Proxy Generator (générateur de proxies) qui construit la chaîne de connexion pour vous. Nous avons choisi Residential Proxies en HTTP et réglé le type de rotation sur Sticky, avec un Session TTL (durée de vie de la session) de 30 minutes.
Ensuite, sous Geotargeting (ciblage géographique), nous avons sélectionné United Kingdom. Le générateur intègre tous ces réglages dans le nom d'utilisateur du proxy :
country-GBsélectionne des IP résidentielles britanniques.session-<id>rend la session persistante. Toutes les requêtes portant le même ID sortent par la même IP, et un nouvel ID obtient une nouvelle IP. Le générateur insère un ID aléatoire ; notre scraper en met un nouveau pour chaque session.sessionttl-30conserve cette IP pour la session pendant 30 minutes, soit plus longtemps que n'a duré la plus longue de nos sessions.
Les résultats
Nous avons lancé 100 sessions via des IP résidentielles britanniques de Massive, 10 à la fois. Chaque session a franchi Akamai, puis effectué cinq recherches, chacune pour une ville tirée au hasard dans une liste de 16 et des dates aléatoires entre deux et 12 semaines à l'avance. Un proxy de comptage sur notre machine a mesuré chaque octet échangé entre le scraper et Massive. Nous avons mené le test le 22 octobre 2026.
- Sessions ayant franchi Akamai : 100 sur 100.
- Sessions ayant terminé les cinq recherches : 98 sur 100.
- Recherches ayant renvoyé des tarifs : 490 sur 500 prévues (98 %).
- Temps médian pour franchir Akamai : 2,6 secondes.
- Temps médian par recherche : 1,5 seconde.
Les deux sessions en échec avaient toutes deux franchi Akamai, puis ont été refusées dès leur premier appel de tarifs. Toutes deux ont été bloquées définitivement : franchir à nouveau Akamai sur la même IP n'a rien changé. Il s'agit d'une IP à laquelle Akamai ne fait pas confiance, et la solution est une nouvelle session sur une nouvelle IP.
Ce que cela coûte en trafic proxy
Franchir Akamai, étape que nous appelons le bootstrap, est de loin la partie la plus coûteuse d'une session : 520 KB. Une recherche coûte 12,5 KB : 5,2 KB pour la liste des hôtels et 7,2 KB pour les tarifs. La recherche d'une ville coûte 6,4 KB, une fois par ville.
Le bootstrap se résume presque entièrement à deux téléchargements : le script SBSD (189 KB) et le script sensor (195 KB). À eux deux, ils en représentent 74 %. La page d'accueil pèse 64 KB, et l'ensemble des POST reste sous les 60 KB.
Avec cinq recherches par session, cela revient à 122 KB de trafic proxy par recherche, tout compris. Plus une session dure, moins le bootstrap pèse. Lors d'un second test, cinq sessions ont chacune effectué 100 recherches sur un seul bootstrap. Les 500 recherches ont toutes renvoyé des tarifs, Akamai n'en a arrêté aucune, et le trafic s'est élevé à 18 KB par recherche, tout compris.
Conseils pratiques
- Enregistrez avec un outil qui montre ce qui passe sur le réseau. L'ordre des en-têtes, les pseudo-en-têtes HTTP/2 et l'empreinte TLS ne sont pas visibles dans les DevTools. C'est précisément ce que vérifie la détection de bots.
- Cherchez le JSON, pas le HTML. Si une page est une application JavaScript, ses données proviennent d'une API. Appeler directement cette API est moins coûteux et plus stable que de parser la page.
- Lisez le JavaScript du site avant de faire des suppositions. Des valeurs comme
transactionidressemblent à des jetons serveur et se révèlent aléatoires. Une recherche dans le bundle tranche la question en quelques minutes. - Parsez les chemins des scripts Akamai à chaque fois. Ils changent.
- Vérifiez votre scraper dans le même outil que le navigateur. Une comparaison côte à côte révèle un mauvais ordre d'en-têtes ou une mauvaise empreinte avant qu'ils ne se traduisent par un taux de blocage.
- Gardez une IP par session. Utilisez des sessions sticky, vérifiez l'IP de sortie et démarrez une nouvelle session sur une nouvelle IP quand l'une d'elles est bloquée.
- Réutilisez les sessions. Le bootstrap est la partie coûteuse : lancez donc autant de recherches sur une même session que le site vous le permet.
Notes méthodologiques
- Dates et échelle. Nous avons mené les tests le 22 octobre 2026, via des IP résidentielles britanniques de Massive : 100 sessions, 10 à la fois, cinq recherches chacune. Le test plus long a porté sur cinq sessions supplémentaires, cinq à la fois, avec 100 recherches chacune.
- Recherches. Une recherche correspond à la liste des hôtels d'un lieu plus une requête de tarifs pour l'ensemble de ses hôtels, pour deux adultes et une à trois nuits. Le contenu des hôtels (noms et adresses) change rarement : nous l'avons donc exclu du coût par recherche.
- Ce qui est comptabilisé. Le trafic correspond à ce que notre proxy de comptage a vu passer entre le scraper et Massive, y compris l'établissement des connexions, les handshakes TLS et les tentatives échouées. Les appels à l'API Hyper Solutions partaient directement de notre machine, sans passer par le proxy : ils ne sont donc pas comptabilisés.
- Identité du navigateur. Le scraper se présente comme Chrome 155 sous Windows, avec les en-têtes et l'ordre des en-têtes de la session enregistrée.
Lancez-le vous-même
- Code : le dépôt radisson-scraper contient le flux complet en Go.
- Proxies : configurez une session résidentielle sticky dans le Proxy Generator de Massive, comme indiqué plus haut.
- Payloads Akamai : l'API Akamai d'Hyper Solutions génère les payloads sensor, SBSD et pixel ; commencez par la documentation de démarrage Akamai. Chaque nouveau compte Hyper Solutions bénéficie d'un essai gratuit d'une semaine.
Foire aux questions
Les payloads d'Akamai sont générés pour une IP donnée. Si l'IP change en cours de session, la session n'est plus valide : chaque session doit donc conserver une même IP résidentielle jusqu'à la fin.
Lors de notre test, franchir Akamai a coûté 520 KB par session, et chaque recherche 12,5 KB. Avec cinq recherches par session, cela fait 122 KB par recherche, tout compris ; avec 100 recherches sur une seule session, ce chiffre est tombé à 18 KB par recherche.
