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.
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: 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 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
_abcke 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, 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.
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/amsterdamretorna 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-ratesretorna 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.
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: desktopem todas as chamadas, ex-version: V2ex-currencyna 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.
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 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_abcke, 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 cookiebm_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 compixel_na frente.
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: 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.
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.
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.
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:
- Consulta o próprio IP de saída, porque os payloads da Akamai são gerados para ele.
- Carrega a página inicial. Se for a página de desafio SBSD, responde ao desafio e a carrega de novo.
- Baixa o script SBSD e envia dois payloads SBSD.
- Baixa o script do sensor e envia dados do sensor até que
_abckcontenha~0~, respondendo ao desafio do pixel depois do primeiro POST. - 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. É um pacote Go e uma pequena ferramenta de linha de comando que imprime os hotéis e as tarifas de uma cidade em JSON:
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.
Depois, em Geotargeting, escolhemos United Kingdom (Reino Unido). O gerador coloca tudo isso no nome de usuário do proxy:
country-GBseleciona 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-30manté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
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.
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
transactionidparecem 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.
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 tem o fluxo completo em Go.
- Proxies: configure uma sessão residencial sticky no Proxy Generator da Massive, como mostrado acima.
- Payloads da Akamai: a API Akamai da Hyper Solutions gera os payloads do sensor, do SBSD e do pixel; comece pela documentação de primeiros passos da Akamai. Toda nova conta da Hyper Solutions começa com um teste gratuito de uma semana.
Perguntas frequentes
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.
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.
