# 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](https://www.radissonhotels.com) : 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](https://joinmassive.com) 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 `_abck` et 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](https://hypersolutions.co/blog/how-to-use-powhttp-web-scraping), 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.

![Liste des requêtes dans powhttp pour la recherche Radisson enregistrée, filtrée sur les hôtes se terminant par radissonhotels.com](https://cdn.sanity.io/images/6hf2slow/production/bdd569f92c8c3614f766f6f5d35e8a570f104f74-2316x1224.png "La recherche enregistrée, filtrée sur les domaines de Radisson : la page, ses scripts et ses images, les endpoints d'Akamai et les appels d'API.")

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/amsterdam` renvoie 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-rates` renvoie 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.

![powhttp affichant le corps de la requête multiple-hotels-rates et sa réponse JSON](https://cdn.sanity.io/images/6hf2slow/production/44408488a04a170bf57d722821d932ab49236319-1200x1555.png "L'appel des tarifs : en entrée, les dates, les voyageurs et les codes d'hôtels ; en sortie, un prix par hôtel.")

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: desktop` sur chaque appel, ainsi que `x-version: V2` et `x-currency` sur 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.

![Résultats de recherche de powhttp pour StoreTransactionId, ouverts sur home.js à la fonction qui génère le transactionid](https://cdn.sanity.io/images/6hf2slow/production/573e831ceba36a2415fef8927409e3124942d812-2881x1550.png "Le transactionid se compose de 40 chiffres aléatoires en base 15, générés dans le navigateur et conservés dans le stockage de session.")

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](https://github.com/bogdanfinn/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 cookie `bm_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é de `pixel_`.

![Le HTML de la page d'accueil de Radisson, avec le nombre du pixel Akamai dans le head et les balises des scripts sensor et SBSD à la fin du body](https://cdn.sanity.io/images/6hf2slow/production/7b219dad0603443a4d9d6a656dd55942b76e1a2d-2015x536.png "La page d'accueil charge le script sensor et le script SBSD d'Akamai depuis des chemins d'apparence aléatoire, et inscrit le nombre du pixel dans le head.")

![powhttp affichant un POST sensor avec le statut 201, une réponse de succès et le cookie _abck qu'il définit](https://cdn.sanity.io/images/6hf2slow/production/bc4d033eff1c203a0e75bef2d5808e1b304abcfd-2015x783.png "Un POST sensor : Akamai répond 201 et définit un cookie _abck contenant ~0~.")

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](https://hypersolutions.co/products/akamai) : 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](https://github.com/Hyper-Solutions/hyper-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](https://hypersolutions.co/blog/bypass-akamai-without-headless-browser).

## É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.

![Les requêtes de tarifs du navigateur et du scraper côte à côte dans powhttp, avec les en-têtes dans le même ordre](https://cdn.sanity.io/images/6hf2slow/production/25f8a22d0a860b08e005651a8ad47dacc2b1aec6-2881x1240.png "La requête du navigateur (à gauche) et celle du scraper (à droite) : les mêmes en-têtes dans le même ordre, seules des valeurs comme les dates et la devise diffèrent.")

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 :

1. Récupérer sa propre IP de sortie, car les payloads Akamai sont générés pour celle-ci.
2. Charger la page d'accueil. S'il s'agit de la page de challenge SBSD, répondre au challenge et la recharger.
3. Télécharger le script SBSD et envoyer deux payloads SBSD en POST.
4. Télécharger le script sensor et envoyer les données du sensor jusqu'à ce que `_abck` contienne `~0~`, en répondant au challenge pixel après le premier envoi.
5. 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](https://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 :

<div data-block="html"><div style="background:#011627;border:1px solid rgba(250,244,236,0.08);border-radius:10px;margin:1.5rem 0;"><div style="padding:10px 18px 0;font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:0.08em;color:rgba(214,222,235,0.6);">BASH</div><pre style="margin:0;padding:10px 18px 16px;background:transparent;color:#d6deeb;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.65;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:visible;"><code>git clone https://github.com/Hyper-Solutions/radisson-scraper.git &amp;&amp; cd radisson-scraper
export HYPER_API_KEY=your-key
export PROXY_URL='http://your-username-country-GB-session-{session}-sessionttl-30:your-password@network.joinmassive.com:65534'
go run ./cmd/radisson -city london -checkin 2026-11-20 -checkout 2026-11-22 -currency GBP</code></pre></div></div>

## 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.

![Le Proxy Generator de Massive réglé sur Residential Proxies en HTTP, avec une rotation Sticky et un Session TTL de 30 minutes](https://cdn.sanity.io/images/6hf2slow/production/50c42e181ca54839cf29788fc38d7390fecdb58d-1403x1167.png "La rotation Sticky conserve une même IP par ID de session, ici pendant 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 :

![Le Proxy Generator de Massive avec United Kingdom sélectionné et la commande cURL générée, qui montre le nom d'utilisateur GB en session sticky](https://cdn.sanity.io/images/6hf2slow/production/b762ea2745768cb53b30ef0c29359fe77b35f9b8-921x1200.png "Avec United Kingdom sélectionné, le nom d'utilisateur devient your-username-country-GB-session-ID-sessionttl-30.")

- `country-GB` sé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-30` conserve 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

![Diagramme en barres du trafic proxy du scraper basé sur les requêtes, par étape : le bootstrap Akamai, la recherche de ville, une recherche et la vérification de l'IP](https://cdn.sanity.io/images/6hf2slow/production/ab3e852b1577a97a6812ec9bd3221de0224e8c21-2400x1164.png "Trafic proxy moyen par étape via des IP résidentielles britanniques de Massive.")

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.

![Diagramme en barres du trafic proxy du bootstrap par étape, où les scripts SBSD et sensor sont les deux postes les plus lourds](https://cdn.sanity.io/images/6hf2slow/production/2e57c4b745fb13ef563d19d5771a422d4fe39187-2400x1764.png "Trafic proxy moyen par étape du bootstrap.")

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 `transactionid` ressemblent à 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.

## Questions fréquentes

### Peut-on scraper des prix d'hôtels sans navigateur headless ?

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.

### Pourquoi utiliser des proxies résidentiels sticky pour scraper des hôtels ?

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.

### Quelle quantité de trafic proxy consomme un scraper d'hôtels basé sur les requêtes ?

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.

## 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](https://github.com/Hyper-Solutions/radisson-scraper) contient le flux complet en Go.
- **Proxies :** configurez une session résidentielle sticky dans le Proxy Generator de [Massive](https://joinmassive.com), comme indiqué plus haut.
- **Payloads Akamai :** l'[API Akamai d'Hyper Solutions](https://hypersolutions.co/products/akamai) génère les payloads sensor, SBSD et pixel ; commencez par la [documentation de démarrage Akamai](https://docs.hypersolutions.co/akamai-web/getting-started). Chaque nouveau compte Hyper Solutions bénéficie d'un essai gratuit d'une semaine.
