# Cómo hacer scraping de precios de hoteles sin navegador: ingeniería inversa del flujo de solicitudes de Radisson

Un navegador es la forma fácil de hacer scraping de un sitio protegido con detección de bots. También es la más lenta y la más cara. En este artículo construimos el otro tipo de scraper para [Radisson Hotels](https://www.radissonhotels.com): uno que envía solo las solicitudes HTTP que devuelven los datos y supera Akamai Bot Manager sin ejecutar nunca un navegador.

Radisson es un buen caso de prueba para el scraping de precios de hoteles. Los precios no están en el HTML: vienen de una API JSON a la que la página llama después de cargarse, y el sitio ejecuta tres de las comprobaciones de Akamai antes de dejar pasar esas llamadas.

A continuación le mostramos cómo lo conseguimos. Grabamos una sesión real de navegador, encontramos las cuatro llamadas a la API que importan entre 148 solicitudes a Radisson, averiguamos qué comprobaciones de Akamai ejecuta el sitio y dónde, y reconstruimos todo el flujo en Go. Después lo ejecutamos a través de proxies residenciales de [Massive](https://joinmassive.com) en el Reino Unido.

> **Puntos clave**
>
> - Los resultados de búsqueda de Radisson vienen de cuatro endpoints JSON. Dos de ellos, la consulta de la ciudad y la llamada de tarifas, son donde Akamai bloquea una sesión no válida.
> - El sitio ejecuta tres comprobaciones de Akamai al cargar la página: SBSD, el sensor `_abck` y un desafío de píxel. Un scraper basado en solicitudes tiene que responder a las tres, en el mismo orden que la página.
> - En 100 sesiones a través de IP residenciales de Massive en el Reino Unido, todas las sesiones superaron Akamai y 490 de las 500 búsquedas previstas devolvieron tarifas (98 %).
> - Superar Akamai costó 520 KB de tráfico de proxy por sesión, y tres cuartas partes de eso fueron dos archivos JavaScript. Después, cada búsqueda costó 12,5 KB.
> - Una sesión que supera Akamai sigue funcionando. Cinco sesiones hicieron 100 búsquedas cada una sin que las detuvieran ni una sola vez, a 18 KB por búsqueda, todo incluido.

## Por qué un scraper basado en solicitudes

Un scraper basado en solicitudes se salta todo lo que hace un navegador y que el scraper no necesita: el renderizado, las imágenes, las fuentes, los mapas, la analítica y el propio JavaScript de la página. Lo que queda es un puñado de solicitudes HTTP, así que es más rápido, ejecuta cientos de sesiones en una sola máquina y mueve mucho menos tráfico a través de sus proxies.

El inconveniente es que hay que saber exactamente qué solicitudes enviar. La detección de bots no solo mira lo que usted solicita. Mira cómo lo hace: el handshake TLS, el orden de los encabezados HTTP/2, las cookies y los desafíos de JavaScript que un navegador real responde en segundo plano. Así que el trabajo consiste, primero, en hacer ingeniería inversa del flujo de solicitudes.

## Paso 1: Grabar una sesión real

Empezamos haciendo una búsqueda en Radisson con Chrome, con el tráfico pasando por [powhttp](https://hypersolutions.co/blog/how-to-use-powhttp-web-scraping), un proxy de depuración HTTP. Abrimos la página de inicio, escribimos «Amsterdam», elegimos las fechas y buscamos.

Usamos powhttp en lugar de las DevTools del navegador por una razón. DevTools le muestra una versión limpia de cada solicitud. powhttp muestra lo que realmente pasó por la red: el orden real de los encabezados, los pseudoencabezados HTTP/2, la huella digital TLS y cada encabezado de cookie que envió el navegador. Esos detalles son exactamente lo que comprueba la detección de bots, y lo que un scraper tiene que copiar.

![Lista de solicitudes de powhttp de la búsqueda grabada en Radisson, filtrada a los hosts que terminan en radissonhotels.com](https://cdn.sanity.io/images/6hf2slow/production/bdd569f92c8c3614f766f6f5d35e8a570f104f74-2316x1224.png "La búsqueda grabada, filtrada a los dominios propios de Radisson: la página, sus scripts e imágenes, los endpoints de Akamai y las llamadas a la API.")

Esa única búsqueda generó 568 solicitudes a 46 hosts. La mayoría eran ruido: Google Maps, analítica, un banner de consentimiento, imágenes. Al filtrar por los dominios propios de Radisson quedaron 148 solicitudes: bundles de JavaScript, imágenes, los scripts de Akamai y 38 llamadas a API JSON.

## Paso 2: Encontrar las solicitudes que devuelven los precios de los hoteles

Los precios de los hoteles no estaban en el HTML. La página de resultados es una aplicación JavaScript que los carga después de renderizarse, así que revisamos las 38 llamadas a la API y miramos qué devolvía cada una. Cuatro de ellas contienen todo lo que necesita una búsqueda:

- `GET /zimba-api/autocomplete/amsterdam` devuelve el lugar que escribió, como un place ID de Google.
- `GET /zimba-api/search-result/hotels/search?placeId=…` devuelve los códigos de los hoteles Radisson de ese lugar, con sus distancias.
- `POST /availability-api/multiple-hotels-rates` devuelve la tarifa más barata de cada hotel para las fechas y los huéspedes indicados.
- `GET /zimba-api/search-result/hotels?code=…` devuelve el nombre, la dirección, las estrellas y la puntuación de reseñas de un hotel.

La llamada de tarifas es la que importa. Recibe las fechas, los huéspedes y la lista de códigos de hotel como cuerpo JSON, y devuelve un precio por hotel, el total con impuestos y cuántas habitaciones quedan.

![powhttp mostrando el cuerpo de la solicitud multiple-hotels-rates y su respuesta JSON](https://cdn.sanity.io/images/6hf2slow/production/44408488a04a170bf57d722821d932ab49236319-1200x1555.png "La llamada de tarifas: entran fechas, huéspedes y códigos de hotel; sale un precio por hotel.")

Las otras 34 llamadas cargan etiquetas, monedas, tipos de cambio, números de teléfono y contenido del programa de fidelización para la página. Un scraper no necesita ninguna de ellas.

## Paso 3: Copiar las solicitudes con exactitud

Después revisamos los encabezados de las cuatro llamadas. La mayoría son los que envía cualquier fetch de Chrome. Unos pocos son específicos de Radisson:

- `x-source-of-business: desktop` en todas las llamadas, y `x-version: V2` y `x-currency` en la llamada de tarifas.
- `transactionid`, un valor de 40 caracteres que es el mismo en todas las llamadas de una visita.

El `transactionid` parecía un token emitido por el servidor. No lo es. Al buscar `transactionId` en el JavaScript del sitio con powhttp, encontramos la función que lo genera: 40 dígitos aleatorios en base 15, creados en la primera carga de página y guardados en el almacenamiento de sesión del navegador. Así que nuestro scraper genera uno por sesión de la misma forma.

![Resultados de búsqueda de StoreTransactionId en powhttp, abiertos en home.js en la función que genera el transactionid](https://cdn.sanity.io/images/6hf2slow/production/573e831ceba36a2415fef8927409e3124942d812-2881x1550.png "El transactionid son 40 dígitos aleatorios en base 15, generados en el navegador y guardados en el almacenamiento de sesión.")

Luego copiamos el orden de los encabezados. Chrome envía los encabezados en un orden fijo que cambia según el tipo de solicitud: una carga de página, un script, un `fetch()` y el envío de un formulario tienen cada uno el suyo. Copiamos cada orden de la grabación, incluida la posición del encabezado de cookies.

Para la conexión en sí usamos [tls-client](https://github.com/bogdanfinn/tls-client) para Go, que abre conexiones con la huella digital TLS y HTTP/2 de Chrome. El cliente HTTP estándar de Go tiene una huella digital TLS que no tiene ningún navegador, y la huella digital TLS es una de las primeras cosas que mira la detección de bots.

## Paso 4: Averiguar dónde interviene Akamai

Con las solicitudes de datos identificadas, la siguiente pregunta era qué necesita Akamai antes de dejarlas pasar. La respuesta está en el HTML de la página de inicio y en las solicitudes que hace la página justo después de cargarse.

La página incluye tres scripts de Akamai:

- **El script del sensor**, en una ruta larga de aspecto aleatorio como `/hLJOkaPJ/…/XMmkuCU0`. Recopila datos sobre el navegador y los envía por POST a la misma ruta, tres veces. Cada respuesta actualiza la cookie `_abck` y, en cuanto la cookie contiene `~0~`, Akamai considera válida la sesión.
- **El script SBSD**, en una ruta similar con un parámetro `?v=`. SBSD (State Based Scraping Detection) es una segunda comprobación de Akamai. La página envía payloads por POST a la misma ruta mientras se ejecuta, tres veces en nuestra grabación, y cada respuesta actualiza la cookie `bm_s`. Nuestro scraper envía dos, y fue suficiente.
- **El script de píxel**, en `/akam/13/…`, con un número escrito en la página. La página lo responde una vez con un envío de formulario a la misma ruta con `pixel_` delante.

![El HTML de la página de inicio de Radisson con el número de píxel de Akamai en el head y las etiquetas script del sensor y de SBSD al final del body](https://cdn.sanity.io/images/6hf2slow/production/7b219dad0603443a4d9d6a656dd55942b76e1a2d-2015x536.png "La página de inicio carga el script del sensor y el script SBSD de Akamai desde rutas de aspecto aleatorio, y escribe el número de píxel en el head.")

![powhttp mostrando un POST del sensor con estado 201, una respuesta correcta y la cookie _abck que establece](https://cdn.sanity.io/images/6hf2slow/production/bc4d033eff1c203a0e75bef2d5808e1b304abcfd-2015x783.png "Un POST del sensor: Akamai responde 201 y establece una cookie _abck que contiene ~0~.")

Aquí importan dos detalles. Las rutas de los scripts cambian, así que un scraper tiene que leerlas de cada página de inicio que carga en lugar de fijarlas en el código. Durante nuestras pruebas, la ruta de SBSD cambió varias veces. Y en algunas IP, la página de inicio no es la página de inicio en absoluto. Es una breve página de desafío SBSD con un parámetro `t=` en el script. El scraper tiene que responder a ese desafío y volver a cargar la página antes de poder hacer cualquier otra cosa.

Ninguna de estas comprobaciones se supera copiando un payload grabado. Cada payload se genera a partir del script, las cookies, el user agent y la IP de la sesión actual. Los generamos con la [API de Hyper Solutions](https://hypersolutions.co/products/akamai): el scraper descarga el script a través de su proxy, lo envía a la API con las cookies y la IP de la sesión, y envía por POST el payload que devuelve la API. Para ello usamos nuestro [SDK para Go](https://github.com/Hyper-Solutions/hyper-sdk-go).

En nuestra [guía de Akamai sin navegador headless](https://hypersolutions.co/blog/bypass-akamai-without-headless-browser) explicamos cómo funciona cada una de estas comprobaciones por separado, con código del SDK en Go, Python y JavaScript.

## Paso 5: Comparar su scraper con el navegador

Antes de escalar, también pasamos el scraper por powhttp, para que sus solicitudes quedaran junto a las del navegador en la misma herramienta. Así la comparación es directa:

- La huella digital TLS coincidía con la de Chrome. Los hashes JA4 del navegador y del scraper solo se diferenciaban en el valor GREASE aleatorio que Chrome inserta a propósito.
- El orden de los encabezados coincidía en todos los tipos de solicitud.
- El encabezado de cookies estaba dividido en un encabezado por cookie, como hace Chrome sobre HTTP/2.

![Las solicitudes de tarifas del navegador y del scraper, una al lado de la otra en powhttp, con los encabezados en el mismo orden](https://cdn.sanity.io/images/6hf2slow/production/25f8a22d0a860b08e005651a8ad47dacc2b1aec6-2881x1240.png "La solicitud del navegador (izquierda) y la del scraper (derecha): los mismos encabezados en el mismo orden; solo cambian valores como las fechas y la moneda.")

Si algo no cuadra, aquí es donde lo encontrará, y es mucho más barato encontrarlo aquí que más adelante en forma de tasa de bloqueo.

## El flujo completo

En conjunto, una sesión de scraping hace lo siguiente:

1. Consulta su propia IP de salida, porque los payloads de Akamai se generan para ella.
2. Carga la página de inicio. Si es la página de desafío SBSD, responde al desafío y la vuelve a cargar.
3. Descarga el script SBSD y envía dos payloads SBSD.
4. Descarga el script del sensor y envía datos del sensor hasta que `_abck` contenga `~0~`, y responde al desafío de píxel después del primer envío.
5. Busca: consulta la ciudad una vez y, en cada búsqueda, obtiene la lista de hoteles y envía la solicitud de tarifas.

Después, la sesión puede seguir buscando. Si Akamai responde a una llamada protegida con un desafío, la sesión lo resuelve y reintenta una vez.

El código está disponible como código abierto: [github.com/Hyper-Solutions/radisson-scraper](https://github.com/Hyper-Solutions/radisson-scraper). Es un paquete de Go y una pequeña herramienta de línea de comandos que imprime en JSON los hoteles y las tarifas de una ciudad:

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

## Configuración de los proxies de Massive

Cada sesión necesita su propia IP residencial y tiene que conservarla hasta terminar. Los payloads de Akamai se generan para una IP concreta, así que si la IP cambia a mitad de una sesión, la sesión deja de ser válida.

El panel de control de Massive incluye un Proxy Generator (generador de proxies) que crea la cadena de conexión por usted. Elegimos proxies residenciales por HTTP y fijamos el tipo de rotación en Sticky (sesión persistente), con un Session TTL de 30 minutos.

![El Proxy Generator de Massive configurado con proxies residenciales por HTTP, rotación Sticky y un Session TTL de 30 minutos](https://cdn.sanity.io/images/6hf2slow/production/50c42e181ca54839cf29788fc38d7390fecdb58d-1403x1167.png "La rotación Sticky mantiene una IP por ID de sesión, aquí durante 30 minutos.")

Después, en Geotargeting (segmentación geográfica), seleccionamos United Kingdom. El generador lo incluye todo en el nombre de usuario del proxy:

![El Proxy Generator de Massive con United Kingdom seleccionado y el comando cURL generado, que muestra el nombre de usuario persistente para GB](https://cdn.sanity.io/images/6hf2slow/production/b762ea2745768cb53b30ef0c29359fe77b35f9b8-921x1200.png "Con United Kingdom seleccionado, el nombre de usuario pasa a ser your-username-country-GB-session-ID-sessionttl-30.")

- `country-GB` selecciona IP residenciales del Reino Unido.
- `session-<id>` hace que la sesión sea persistente. Todas las solicitudes con el mismo ID salen por la misma IP, y un ID nuevo obtiene una IP nueva. El generador rellena un ID aleatorio; nuestro scraper pone uno nuevo en cada sesión.
- `sessionttl-30` mantiene esa IP para la sesión durante 30 minutos, más de lo que duró cualquiera de nuestras sesiones.

## Los resultados

Ejecutamos 100 sesiones a través de IP residenciales de Massive en el Reino Unido, 10 a la vez. Cada sesión superó Akamai y después hizo cinco búsquedas, cada una de una ciudad aleatoria de una lista de 16 y con fechas aleatorias de entre dos y 12 semanas más adelante. Un proxy de conteo en nuestra máquina midió cada byte entre el scraper y Massive. Hicimos la prueba el 22 de octubre de 2026.

- **Sesiones que superaron Akamai:** 100 de 100.
- **Sesiones que completaron las cinco búsquedas:** 98 de 100.
- **Búsquedas que devolvieron tarifas:** 490 de 500 previstas (98 %).
- **Mediana del tiempo para superar Akamai:** 2,6 segundos.
- **Mediana del tiempo por búsqueda:** 1,5 segundos.

Las dos sesiones que fallaron superaron Akamai y después fueron rechazadas en su primera llamada de tarifas. Ambas quedaron bloqueadas de forma definitiva: volver a superar Akamai en la misma IP no sirvió de nada. Se trata de una IP en la que Akamai no confía, y la solución es una sesión nueva en una IP nueva.

### Lo que cuesta en tráfico de proxy

![Gráfico de barras del tráfico de proxy por paso del scraper basado en solicitudes: el bootstrap de Akamai, la consulta de la ciudad, una búsqueda y la consulta de la IP](https://cdn.sanity.io/images/6hf2slow/production/ab3e852b1577a97a6812ec9bd3221de0224e8c21-2400x1164.png "Tráfico de proxy medio por paso a través de IP residenciales de Massive en el Reino Unido.")

Superar Akamai, lo que llamamos el bootstrap, es con diferencia la parte más cara de una sesión: 520 KB. Una búsqueda cuesta 12,5 KB: 5,2 KB para la lista de hoteles y 7,2 KB para las tarifas. Consultar una ciudad cuesta 6,4 KB, una vez por ciudad.

![Gráfico de barras del tráfico de proxy del bootstrap por paso, con los scripts SBSD y del sensor como los dos mayores](https://cdn.sanity.io/images/6hf2slow/production/2e57c4b745fb13ef563d19d5771a422d4fe39187-2400x1764.png "Tráfico de proxy medio por paso del bootstrap.")

Casi todo el bootstrap son dos descargas: el script SBSD (189 KB) y el script del sensor (195 KB). Juntos suponen el 74 % del total. La página de inicio ocupa 64 KB, y todos los POST juntos, menos de 60 KB.

Con cinco búsquedas por sesión, eso supone 122 KB de tráfico de proxy por búsqueda, todo incluido. Cuanto más dura una sesión, menos pesa el bootstrap. En una segunda prueba, cinco sesiones hicieron 100 búsquedas cada una sobre un único bootstrap. Las 500 búsquedas devolvieron tarifas, Akamai no detuvo ninguna y el tráfico se quedó en 18 KB por búsqueda, todo incluido.

## Consejos prácticos

- **Grabe con una herramienta que muestre lo que pasa por la red.** El orden de los encabezados, los pseudoencabezados HTTP/2 y la huella digital TLS no se ven en DevTools. Son justo lo que comprueba la detección de bots.
- **Busque el JSON, no el HTML.** Si una página es una aplicación JavaScript, sus datos vienen de una API. Llamar directamente a esa API es más barato y más estable que parsear la página.
- **Lea el JavaScript del sitio antes de hacer suposiciones.** Valores como `transactionid` parecen tokens del servidor y resultan ser aleatorios. Una búsqueda en el bundle lo aclara en minutos.
- **Parsee siempre las rutas de los scripts de Akamai.** Cambian.
- **Verifique su scraper en la misma herramienta que el navegador.** Una comparación lado a lado detecta un orden de encabezados o una huella digital incorrectos antes de que aparezcan como tasa de bloqueo.
- **Mantenga una IP por sesión.** Use sesiones persistentes, compruebe la IP de salida e inicie una sesión nueva en una IP nueva cuando una quede bloqueada.
- **Reutilice las sesiones.** El bootstrap es la parte cara, así que haga tantas búsquedas en una sesión como le permita el sitio.

## Preguntas frecuentes

### ¿Se puede hacer scraping de precios de hoteles sin un navegador headless?

En Radisson, sí. Los precios vienen de una API JSON de tarifas. En nuestra prueba, un scraper que envía solo las solicitudes de la página, de Akamai y de la API, con la huella digital TLS y el orden de encabezados de Chrome, devolvió tarifas en 490 de las 500 búsquedas previstas.

### ¿Por qué usar proxies residenciales con sesión persistente para hacer scraping de hoteles?

Los payloads de Akamai se generan para una IP concreta. Si la IP cambia a mitad de una sesión, la sesión deja de ser válida, así que cada sesión tiene que conservar una misma IP residencial hasta terminar.

### ¿Cuánto tráfico de proxy consume un scraper de hoteles basado en solicitudes?

En nuestra prueba, superar Akamai costó 520 KB por sesión, y cada búsqueda, 12,5 KB. Con cinco búsquedas por sesión, son 122 KB por búsqueda, todo incluido; con 100 búsquedas en una sola sesión, bajó a 18 KB por búsqueda.

## Notas sobre la metodología

- **Fechas y escala.** Hicimos las pruebas el 22 de octubre de 2026 a través de IP residenciales de Massive en el Reino Unido: 100 sesiones, 10 a la vez, con cinco búsquedas cada una. La prueba más larga ejecutó otras cinco sesiones, cinco a la vez, con 100 búsquedas cada una.
- **Búsquedas.** Una búsqueda es la lista de hoteles de un lugar más una solicitud de tarifas para todos sus hoteles, para dos adultos y de una a tres noches. El contenido de los hoteles (nombres y direcciones) cambia muy poco, así que lo dejamos fuera del coste por búsqueda.
- **Qué se cuenta.** El tráfico es lo que vio nuestro proxy de conteo entre el scraper y Massive, incluidos el establecimiento de la conexión, los handshakes TLS y los intentos fallidos. Las llamadas a la API de Hyper Solutions salieron directamente desde nuestra máquina, no a través del proxy, así que no se cuentan.
- **Identidad del navegador.** El scraper se presenta como Chrome 155 en Windows, con los encabezados y el orden de encabezados de la sesión grabada.

## Pruébelo usted mismo

- **Código:** el [repositorio radisson-scraper](https://github.com/Hyper-Solutions/radisson-scraper) contiene el flujo completo en Go.
- **Proxies:** configure una sesión residencial persistente en el Proxy Generator de [Massive](https://joinmassive.com), como se muestra más arriba.
- **Payloads de Akamai:** la [API de Akamai de Hyper Solutions](https://hypersolutions.co/products/akamai) genera los payloads del sensor, de SBSD y de píxel; empiece por la [guía de inicio de Akamai](https://docs.hypersolutions.co/akamai-web/getting-started). Todas las cuentas nuevas de Hyper Solutions empiezan con una prueba gratuita de una semana.
