# Como fazer scraping de preços de hotéis sem navegador: engenharia reversa do fluxo de solicitações da Radisson

O navegador é o jeito fácil de fazer scraping de um site protegido por detecção de bots. É também o mais lento e o mais caro. Neste post, construímos o outro tipo de scraper para a [Radisson Hotels](https://www.radissonhotels.com): um que envia apenas as solicitações HTTP que retornam os dados e passa pelo Akamai Bot Manager sem nunca executar um navegador.

A Radisson é um bom caso de teste para scraping de preços de hotéis. Os preços não estão no HTML: eles vêm de uma API JSON que a página chama depois de carregar, e o site executa três verificações da Akamai antes de deixar essas chamadas passarem.

Vamos mostrar como chegamos lá. Gravamos uma sessão real de navegador, encontramos as quatro chamadas de API que importam entre 148 solicitações à Radisson, descobrimos quais verificações da Akamai o site executa e onde, e reconstruímos todo o fluxo em Go. Depois, rodamos tudo por proxies residenciais da [Massive](https://joinmassive.com) no Reino Unido.

> **Principais conclusões**
>
> - Os resultados de busca da Radisson vêm de quatro endpoints JSON. Dois deles, a consulta da cidade e a chamada de tarifas, são onde a Akamai bloqueia uma sessão ruim.
> - O site executa três verificações da Akamai ao carregar a página: SBSD, o sensor `_abck` e um desafio de pixel. Um scraper baseado em solicitações precisa responder às três, na mesma ordem que a página.
> - Em 100 sessões por IPs residenciais da Massive no Reino Unido, todas as sessões passaram pela Akamai, e 490 das 500 buscas planejadas retornaram tarifas (98%).
> - Passar pela Akamai custou 520 KB de tráfego de proxy por sessão, e três quartos disso foram dois arquivos JavaScript. Depois disso, cada busca custou 12,5 KB.
> - Uma sessão que passa pela Akamai continua funcionando. Cinco sessões fizeram 100 buscas cada uma sem serem barradas nenhuma vez, a 18 KB por busca, com tudo incluído.

## Por que um scraper baseado em solicitações

Um scraper baseado em solicitações pula tudo o que um navegador faz e de que o scraper não precisa: renderização, imagens, fontes, mapas, analytics e o próprio JavaScript da página. O que sobra é um punhado de solicitações HTTP; por isso, ele é mais rápido, roda centenas de sessões em uma única máquina e passa muito menos tráfego pelos seus proxies.

O porém é que você precisa saber exatamente quais solicitações enviar. A detecção de bots não olha só para o que você solicita. Ela olha para o como: o handshake TLS, a ordem dos headers HTTP/2, os cookies e os desafios JavaScript que um navegador real responde em segundo plano. Então, o trabalho começa pela engenharia reversa do fluxo de solicitações.

## Passo 1: grave uma sessão real

Começamos fazendo uma busca na Radisson pelo Chrome, com o tráfego passando pelo [powhttp](https://hypersolutions.co/blog/how-to-use-powhttp-web-scraping), um proxy de depuração HTTP. Abrimos a página inicial, digitamos "Amsterdam", escolhemos as datas e buscamos.

Usamos o powhttp em vez do DevTools do navegador por um motivo. O DevTools mostra uma versão limpa de cada solicitação. O powhttp mostra o que realmente passou pela rede: a ordem real dos headers, os pseudo-headers HTTP/2, o fingerprint TLS e cada header de cookie que o navegador enviou. Esses detalhes são exatamente o que a detecção de bots verifica e o que um scraper precisa copiar.

![Lista de solicitações do powhttp para a busca gravada na Radisson, filtrada para hosts que terminam em radissonhotels.com](https://cdn.sanity.io/images/6hf2slow/production/bdd569f92c8c3614f766f6f5d35e8a570f104f74-2316x1224.png "A busca gravada, filtrada para os domínios da própria Radisson: a página, seus scripts e imagens, os endpoints da Akamai e as chamadas de API.")

Essa única busca gerou 568 solicitações para 46 hosts. A maioria era ruído: Google Maps, analytics, um banner de consentimento, imagens. Filtrar pelos domínios da própria Radisson deixou 148 solicitações: bundles de JavaScript, imagens, os scripts da Akamai e 38 chamadas a APIs JSON.

## Passo 2: encontre as solicitações que retornam os preços dos hotéis

Os preços dos hotéis não estavam no HTML. A página de resultados é uma aplicação JavaScript que os carrega depois de renderizar, então percorremos as 38 chamadas de API e vimos o que cada uma retornava. Quatro delas trazem tudo de que uma busca precisa:

- `GET /zimba-api/autocomplete/amsterdam` retorna o lugar que você digitou, como um Google place ID.
- `GET /zimba-api/search-result/hotels/search?placeId=…` retorna os códigos dos hotéis Radisson desse lugar, com as distâncias.
- `POST /availability-api/multiple-hotels-rates` retorna a tarifa mais barata de cada hotel para as datas e os hóspedes.
- `GET /zimba-api/search-result/hotels?code=…` retorna o nome, o endereço, as estrelas e a nota de avaliação de um hotel.

A chamada de tarifas é a que importa. Ela recebe as datas, os hóspedes e a lista de códigos de hotel como um corpo JSON e retorna um preço por hotel, o total com impostos e quantos quartos ainda restam.

![powhttp mostrando o corpo da solicitação multiple-hotels-rates e sua resposta JSON](https://cdn.sanity.io/images/6hf2slow/production/44408488a04a170bf57d722821d932ab49236319-1200x1555.png "A chamada de tarifas: entram datas, hóspedes e códigos de hotel, sai um preço por hotel.")

As outras 34 chamadas carregam rótulos, moedas, taxas de câmbio, números de telefone e conteúdo do programa de fidelidade para a página. Um scraper não precisa de nenhuma delas.

## Passo 3: copie as solicitações exatamente

Em seguida, analisamos os headers das quatro chamadas. A maioria é o que qualquer fetch do Chrome envia. Alguns são específicos da Radisson:

- `x-source-of-business: desktop` em todas as chamadas, e `x-version: V2` e `x-currency` na chamada de tarifas.
- `transactionid`, um valor de 40 caracteres que é o mesmo em todas as chamadas de uma visita.

O `transactionid` parecia ser um token vindo do servidor. Não é. Uma busca por `transactionId` no JavaScript do site, dentro do powhttp, encontrou a função que o gera: 40 dígitos aleatórios em base 15, criados no primeiro carregamento da página e guardados no session storage do navegador. Então, nosso scraper gera um por sessão, do mesmo jeito.

![Resultados da busca por StoreTransactionId no powhttp, abertos em home.js na função que gera o transactionid](https://cdn.sanity.io/images/6hf2slow/production/573e831ceba36a2415fef8927409e3124942d812-2881x1550.png "O transactionid tem 40 dígitos aleatórios em base 15, gerados no navegador e guardados no session storage.")

Depois, copiamos a ordem dos headers. O Chrome envia os headers em uma ordem fixa que muda conforme o tipo de solicitação: um carregamento de página, um script, um `fetch()` e um envio de formulário têm, cada um, a sua. Copiamos cada ordem da gravação, incluindo a posição do header de cookie.

Para a conexão em si, usamos o [tls-client](https://github.com/bogdanfinn/tls-client) para Go, que abre conexões com o fingerprint TLS e HTTP/2 do Chrome. O cliente HTTP padrão do Go tem um fingerprint TLS que nenhum navegador tem, e o fingerprint TLS é uma das primeiras coisas que a detecção de bots analisa.

## Passo 4: descubra onde a Akamai atua

Com as solicitações de dados identificadas, a próxima pergunta era o que a Akamai exige antes de deixá-las passar. A resposta está no HTML da página inicial e nas solicitações que a página faz logo depois de carregar.

A página inclui três scripts da Akamai:

- **O script do sensor**, em um caminho longo e de aparência aleatória, como `/hLJOkaPJ/…/XMmkuCU0`. Ele coleta dados sobre o navegador e os envia por POST de volta ao mesmo caminho, três vezes. Cada resposta atualiza o cookie `_abck` e, quando o cookie contém `~0~`, a Akamai considera a sessão válida.
- **O script SBSD**, em um caminho parecido, com um parâmetro `?v=`. SBSD (State Based Scraping Detection) é uma segunda verificação da Akamai. A página envia payloads por POST ao mesmo caminho enquanto roda, três vezes na nossa gravação, e cada resposta atualiza o cookie `bm_s`. Nosso scraper envia dois, o que foi suficiente.
- **O script do pixel**, em `/akam/13/…`, com um número escrito na página. A página responde a ele uma vez, com um POST de formulário para o mesmo caminho com `pixel_` na frente.

![O HTML da página inicial da Radisson com o número do pixel da Akamai no head e as tags de script do sensor e do SBSD no final do body](https://cdn.sanity.io/images/6hf2slow/production/7b219dad0603443a4d9d6a656dd55942b76e1a2d-2015x536.png "A página inicial carrega o script do sensor e o script SBSD da Akamai a partir de caminhos de aparência aleatória e escreve o número do pixel no head.")

![powhttp mostrando um POST do sensor com status 201, uma resposta de sucesso e o cookie _abck que ele define](https://cdn.sanity.io/images/6hf2slow/production/bc4d033eff1c203a0e75bef2d5808e1b304abcfd-2015x783.png "Um POST do sensor: a Akamai responde 201 e define um cookie _abck contendo ~0~.")

Dois detalhes importam aqui. Os caminhos dos scripts mudam, então um scraper precisa lê-los de cada página inicial que carrega, em vez de deixá-los fixos no código. Durante nossos testes, o caminho do SBSD mudou várias vezes. E, em alguns IPs, a página inicial nem é a página inicial. É uma página curta de desafio SBSD, com um parâmetro `t=` no script. O scraper precisa responder a esse desafio e carregar a página de novo antes de poder fazer qualquer outra coisa.

Nenhuma dessas verificações pode ser superada copiando um payload gravado. Cada payload é gerado a partir do script, dos cookies, do user agent e do IP da sessão atual. Nós os geramos com a [API da Hyper Solutions](https://hypersolutions.co/products/akamai): o scraper baixa o script pelo seu proxy, envia-o à API com os cookies e o IP da sessão e faz o POST do payload que a API retorna. Para isso, usamos o nosso [SDK para Go](https://github.com/Hyper-Solutions/hyper-sdk-go).

Como cada uma dessas verificações funciona isoladamente, com código do SDK em Go, Python e JavaScript, é o tema do nosso [guia sobre a Akamai sem navegador headless](https://hypersolutions.co/blog/bypass-akamai-without-headless-browser).

## Passo 5: compare seu scraper com o navegador

Antes de escalar, apontamos o scraper também para o powhttp, para que as solicitações dele aparecessem ao lado das do navegador na mesma ferramenta. Isso torna a comparação direta:

- O fingerprint TLS bateu com o do Chrome. Os hashes JA4 do navegador e do scraper diferiam apenas no valor GREASE aleatório que o Chrome insere de propósito.
- A ordem dos headers bateu em todos os tipos de solicitação.
- O header de cookie foi dividido em um header por cookie, como o Chrome faz no HTTP/2.

![As solicitações de tarifas do navegador e do scraper lado a lado no powhttp, com os headers na mesma ordem](https://cdn.sanity.io/images/6hf2slow/production/25f8a22d0a860b08e005651a8ad47dacc2b1aec6-2881x1240.png "A solicitação do navegador (à esquerda) e a do scraper (à direita): os mesmos headers na mesma ordem, só mudam valores como as datas e a moeda.")

Se algo estiver errado, é aqui que você vai encontrar, e sai muito mais barato encontrar aqui do que numa taxa de bloqueio mais tarde.

## O fluxo completo

Juntando tudo, uma sessão de scraping faz o seguinte:

1. Consulta o próprio IP de saída, porque os payloads da Akamai são gerados para ele.
2. Carrega a página inicial. Se for a página de desafio SBSD, responde ao desafio e a carrega de novo.
3. Baixa o script SBSD e envia dois payloads SBSD.
4. Baixa o script do sensor e envia dados do sensor até que `_abck` contenha `~0~`, respondendo ao desafio do pixel depois do primeiro POST.
5. Busca: consulta a cidade uma vez e, depois, em cada busca, obtém a lista de hotéis e envia a solicitação de tarifas.

Depois disso, a sessão pode continuar buscando. Se a Akamai responder a uma chamada protegida com um desafio, a sessão responde a ele e tenta de novo uma vez.

O código é open source: [github.com/Hyper-Solutions/radisson-scraper](https://github.com/Hyper-Solutions/radisson-scraper). É um pacote Go e uma pequena ferramenta de linha de comando que imprime os hotéis e as tarifas de uma cidade em JSON:

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

## Configurando os proxies da Massive

Cada sessão precisa de um IP residencial próprio e precisa manter esse IP até terminar. Os payloads da Akamai são gerados para um único IP; por isso, se o IP mudar no meio de uma sessão, a sessão deixa de ser válida.

O Painel de Controle da Massive tem um Proxy Generator (gerador de proxies) que monta a string de conexão para você. Escolhemos Residential Proxies (proxies residenciais) via HTTP e definimos o tipo de rotação como Sticky, com um Session TTL de 30 minutos.

![O Proxy Generator da Massive configurado com proxies residenciais via HTTP, rotação Sticky e um Session TTL de 30 minutos](https://cdn.sanity.io/images/6hf2slow/production/50c42e181ca54839cf29788fc38d7390fecdb58d-1403x1167.png "A rotação Sticky mantém um IP por ID de sessão, aqui por 30 minutos.")

Depois, em Geotargeting, escolhemos United Kingdom (Reino Unido). O gerador coloca tudo isso no nome de usuário do proxy:

![O Proxy Generator da Massive com United Kingdom selecionado e o comando cURL gerado mostrando o nome de usuário sticky com GB](https://cdn.sanity.io/images/6hf2slow/production/b762ea2745768cb53b30ef0c29359fe77b35f9b8-921x1200.png "Com United Kingdom selecionado, o nome de usuário passa a ser your-username-country-GB-session-ID-sessionttl-30.")

- `country-GB` seleciona IPs residenciais do Reino Unido.
- `session-<id>` torna a sessão sticky. Toda solicitação com o mesmo ID sai pelo mesmo IP, e um ID novo recebe um IP novo. O gerador preenche um ID aleatório; nosso scraper coloca um novo para cada sessão.
- `sessionttl-30` mantém esse IP na sessão por 30 minutos, mais do que qualquer uma das nossas sessões durou.

## Os resultados

Rodamos 100 sessões por IPs residenciais da Massive no Reino Unido, 10 de cada vez. Cada sessão passou pela Akamai e depois fez cinco buscas, cada uma para uma cidade aleatória de uma lista de 16 e para datas aleatórias entre duas e 12 semanas à frente. Um proxy contador na nossa máquina mediu cada byte entre o scraper e a Massive. Fizemos o teste em 22 de outubro de 2026.

- **Sessões que passaram pela Akamai:** 100 de 100.
- **Sessões que concluíram as cinco buscas:** 98 de 100.
- **Buscas que retornaram tarifas:** 490 de 500 planejadas (98%).
- **Tempo mediano para passar pela Akamai:** 2,6 segundos.
- **Tempo mediano por busca:** 1,5 segundo.

As duas sessões que falharam passaram pela Akamai e depois foram recusadas na primeira chamada de tarifas. As duas ficaram bloqueadas de vez: passar pela Akamai de novo no mesmo IP não adiantou. Esse é um IP em que a Akamai não confia, e a solução é uma nova sessão em um novo IP.

### Quanto isso custa em tráfego de proxy

![Gráfico de barras do tráfego de proxy do scraper baseado em solicitações por etapa: o bootstrap da Akamai, a consulta da cidade, uma busca e a consulta do IP](https://cdn.sanity.io/images/6hf2slow/production/ab3e852b1577a97a6812ec9bd3221de0224e8c21-2400x1164.png "Tráfego médio de proxy por etapa pelos IPs residenciais da Massive no Reino Unido.")

Passar pela Akamai, o que chamamos de bootstrap, é de longe a parte mais cara de uma sessão: 520 KB. Uma busca custa 12,5 KB: 5,2 KB para a lista de hotéis e 7,2 KB para as tarifas. Consultar uma cidade custa 6,4 KB, uma vez por cidade.

![Gráfico de barras do tráfego de proxy do bootstrap por etapa, com os scripts SBSD e do sensor como os dois maiores](https://cdn.sanity.io/images/6hf2slow/production/2e57c4b745fb13ef563d19d5771a422d4fe39187-2400x1764.png "Tráfego médio de proxy por etapa do bootstrap.")

Quase todo o bootstrap são dois downloads: o script SBSD (189 KB) e o script do sensor (195 KB). Juntos, eles representam 74% do total. A página inicial tem 64 KB, e todos os POSTs juntos somam menos de 60 KB.

Com cinco buscas por sessão, isso dá 122 KB de tráfego de proxy por busca, com tudo incluído. Quanto mais tempo uma sessão dura, menos o bootstrap pesa. Num segundo teste, cinco sessões fizeram 100 buscas cada uma com um único bootstrap. Todas as 500 buscas retornaram tarifas, a Akamai não barrou nenhuma delas e o tráfego ficou em 18 KB por busca, com tudo incluído.

## Dicas práticas

- **Grave com uma ferramenta que mostre o que passa pela rede.** A ordem dos headers, os pseudo-headers HTTP/2 e o fingerprint TLS não aparecem no DevTools. São eles que a detecção de bots verifica.
- **Procure o JSON, não o HTML.** Se uma página é uma aplicação JavaScript, os dados dela vêm de uma API. Chamar essa API diretamente é mais barato e mais estável do que fazer o parsing da página.
- **Leia o JavaScript do site antes de tentar adivinhar.** Valores como `transactionid` parecem tokens do servidor e acabam sendo aleatórios. Uma busca no bundle responde isso em minutos.
- **Extraia os caminhos dos scripts da Akamai toda vez.** Eles mudam.
- **Verifique seu scraper na mesma ferramenta que o navegador.** Uma comparação lado a lado encontra uma ordem de headers ou um fingerprint errado antes que isso apareça como taxa de bloqueio.
- **Mantenha um IP por sessão.** Use sessões sticky, confira o IP de saída e inicie uma nova sessão em um novo IP quando uma for bloqueada.
- **Reutilize sessões.** O bootstrap é a parte cara, então faça em uma mesma sessão quantas buscas o site permitir.

## Perguntas frequentes

### É possível fazer scraping de preços de hotéis sem um navegador headless?

Na Radisson, sim. Os preços vêm de uma API JSON de tarifas. Um scraper que envia apenas as solicitações da página, da Akamai e da API, com o fingerprint TLS e a ordem de headers do Chrome, retornou tarifas para 490 das 500 buscas planejadas no nosso teste.

### Por que usar proxies residenciais sticky para scraping de hotéis?

Os payloads da Akamai são gerados para um único IP. Se o IP mudar no meio de uma sessão, a sessão deixa de ser válida; por isso, cada sessão precisa manter o mesmo IP residencial até terminar.

### Quanto tráfego de proxy usa um scraper de hotéis baseado em solicitações?

No nosso teste, passar pela Akamai custou 520 KB por sessão, e cada busca, 12,5 KB. Com cinco buscas por sessão, isso dá 122 KB por busca, com tudo incluído; com 100 buscas em uma mesma sessão, caiu para 18 KB por busca.

## Notas de metodologia

- **Datas e escala.** Fizemos os testes em 22 de outubro de 2026, por IPs residenciais da Massive no Reino Unido: 100 sessões, 10 de cada vez, cinco buscas cada. O teste mais longo rodou mais cinco sessões, cinco de cada vez, com 100 buscas cada.
- **Buscas.** Uma busca é a lista de hotéis de um lugar mais uma solicitação de tarifas para todos os hotéis dele, para dois adultos e de uma a três noites. O conteúdo dos hotéis (nomes e endereços) raramente muda, então o deixamos fora do custo por busca.
- **O que é contado.** O tráfego é o que nosso proxy contador registrou entre o scraper e a Massive, incluindo o estabelecimento das conexões, os handshakes TLS e as tentativas que falharam. As chamadas à API da Hyper Solutions saíram direto da nossa máquina, não pelo proxy, então não entram na conta.
- **Identidade do navegador.** O scraper se apresenta como Chrome 155 no Windows, com os headers e a ordem de headers da sessão gravada.

## Rode você mesmo

- **Código:** o [repositório radisson-scraper](https://github.com/Hyper-Solutions/radisson-scraper) tem o fluxo completo em Go.
- **Proxies:** configure uma sessão residencial sticky no Proxy Generator da [Massive](https://joinmassive.com), como mostrado acima.
- **Payloads da Akamai:** a [API Akamai da Hyper Solutions](https://hypersolutions.co/products/akamai) gera os payloads do sensor, do SBSD e do pixel; comece pela [documentação de primeiros passos da Akamai](https://docs.hypersolutions.co/akamai-web/getting-started). Toda nova conta da Hyper Solutions começa com um teste gratuito de uma semana.
