Scala Web Scraping: Buscar, Analisar e Desbloquear Páginas Protegidas
Expert in Web Scraping Technologies
TL;DR:
- Scala faz scraping da web com duas partes: um cliente HTTP JVM para buscar e um parser baseado em jsoup para extrair. requests-scala faz a requisição; scala-scraper transforma o HTML em nós selecionáveis por CSS.
- Todo o projeto é composto por três dependências do sbt. requests-scala 0.9.0 para HTTP, scala-scraper 3.2.0 para parsing, e ujson 4.1.0 para o envelope JSON que você decodifica depois — nenhum framework para aprender.
- A paginação é um loop sobre o link "próxima". scala-scraper lê o href da próxima página com um seletor opcional, então um percurso de catálogo é uma função de recursão de cauda, não uma fila.
- A busca estática tem um teto rígido: não pode executar JavaScript nem passar um desafio de bot. Um simples
requests.getretorna a estrutura vazia de uma página renderizada pelo cliente e recebe uma página de desafio em sites protegidos. - A API de Scraping Universal Scrapeless fecha essa lacuna com um simples POST HTTP.
js_render: trueexecuta a página do lado do servidor e retorna o DOM finalizado; o mesmo cliente requests-scala que conversa com um site pode conversar com a API. - A chamada de desbloqueio foi executada ao vivo contra o endpoint: HTTP 200, 51.275 bytes de HTML renderizado, 20 títulos de produtos. A forma da requisição e resposta neste guia vem diretamente dessa execução ao vivo.
- Grátis para começar. Novas contas Scrapeless incluem tempo de execução gratuito — inscreva-se em app.scrapeless.com.
Introdução: onde Scala se encaixa no scraping
Scala roda na JVM, o que significa que um scraper escrito nela herda jsoup, Akka e um ecossistema HTTP maduro de graça. A linguagem é uma combinação natural quando scraping faz algo que já está na JVM — um trabalho Spark, um produtor Kafka, um serviço de dados — e você quer a extração no mesmo código, com os mesmos tipos, que o pipeline que o consome.
A parte de buscar e parser desse trabalho é curta. Algumas linhas puxam uma página e leem valores dela com seletores CSS. A fricção começa onde todos os scrapers enfrentam: uma parte crescente da web constrói seu conteúdo com JavaScript que precisa ser realmente executado antes que os dados existam, e os sites protegidos barram o acesso atrás de impressão digital TLS e páginas de desafio que um cliente HTTP bruto nunca consegue superar.
Este guia constrói primeiro o scraper estático — projeto sbt, uma busca HTTP, extração baseada em seletores, paginação — e então traça a linha honesta onde essa abordagem para. A chamada da API no final foi executada ao vivo; seus números são uma captura real.
O que você pode fazer com esta pilha
- Buscar e analisar na JVM — requests-scala para a requisição, scala-scraper (uma camada jsoup) para extração por seletores CSS.
- Manter a extração no seu código de dados — ler valores em tipos Scala bem ao lado do trabalho Spark ou Kafka que os utiliza.
- Percorrer listagens paginadas — seguir o link da próxima página em um loop de recursão de cauda até que acabe.
- Acessar páginas renderizadas em JavaScript e protegidas — envie um POST para a API de Scraping Universal e analise o HTML renderizado da mesma forma.
- Evitar a pilha anti-bot — impressão digital TLS, IPs residenciais e resolução de desafios acontecem do lado da API, não no seu código Scala.
Por que a API de Scraping Universal Scrapeless
A API de Scraping Universal Scrapeless recebe uma URL de destino e retorna o HTML renderizado e desbloqueado. Para um cliente Scala especificamente, ela oferece:
- Renderização de JavaScript do lado do servidor —
js_render: trueretorna o DOM finalizado, então scala-scraper vê conteúdo real em vez de uma estrutura vazia. - Proxies residenciais em mais de 195 países — a busca sai de IPs confiáveis; você nunca constrói ou rotaciona um pool em Scala.
- Tratamento contra bots — impressão digital TLS e resolução de desafios acontecem do lado da API, fora do seu processo JVM.
- Um simples POST HTTPS — sem SDK para adicionar ao
build.sbt; o cliente requests-scala que você já tem é suficiente. - Um envelope pequeno —
{"code":200,"data":"<html>…"}, decodificado com o mesmo ujson que você usa em outros lugares.
Obtenha sua chave da API no plano gratuito em app.scrapeless.com.
Pré-requisitos
- Um JDK (11 ou mais recente) e sbt instalados
- Scala 2.13 (as versões de dependência abaixo são as builds 2.13)
- Uma conta Scrapeless e chave da API — inscreva-se em app.scrapeless.com
- Noções básicas de familiaridade com o terminal
Nota: O código Scala nas seções de construção e etapas abaixo é uma lacuna de pré-requisitos na verificação deste guia — nenhum tempo de execução JVM/sbt estava disponível na máquina de verificação, então aqueles blocos foram compostos e verificados contra as APIs atuais das bibliotecas e versões do Maven Central em vez de executados. A chamada de desbloqueio do Scrapeless foi executada ao vivo contra o endpoint; sua requisição e resposta são uma captura real.
Instalar
Crie um diretório de projeto com dois arquivos. build.sbt fixa a linguagem e as três dependências:
scala
ThisBuild / scalaVersion := "2.13.16"
lazy val scraper = (project in file("."))
.settings(
name := "scala-scraper-demo",
```pt
libraryDependencies ++= Seq(
"com.lihaoyi" %% "requests" % "0.9.0",
"net.ruippeixotog" %% "scala-scraper" % "3.2.0",
"com.lihaoyi" %% "ujson" % "4.1.0"
)
)
project/build.properties fixa o sbt em si:
text
sbt.version=1.12.13
scala-scraper puxa jsoup de forma transitiva, então você analisa com o mecanismo do jsoup através de uma DSL Scala tipada sem depender diretamente do jsoup. Execute sbt update uma vez para resolver tudo, depois sbt console para um REPL ou sbt run para um main.
Passo 1 — Buscar uma página
requests-scala é um cliente HTTP fino e síncrono. Uma chamada obtém o corpo da página como uma string:
scala
val res = requests.get(
"https://books.toscrape.com/",
headers = Map("User-Agent" -> "Mozilla/5.0 (compatível; scala-scraper-demo)")
)
println(res.statusCode) // 200
val html: String = res.text()
res.text() é o HTML bruto. Para uma página renderizada pelo servidor como esta, a string já contém os dados; para uma página renderizada pelo cliente, conteria uma estrutura vazia, que é o limite que o Passo 4 aborda.
Passo 2 — Analisar com scala-scraper
scala-scraper analisa a string em um documento e seleciona nós com seletores CSS através de sua DSL. O operador >> extrai; elementList, attr e texts moldam o resultado em valores Scala:
scala
import net.ruippeixotog.scalascraper.browser.JsoupBrowser
import net.ruippeixotog.scalascraper.dsl.DSL._
import net.ruippeixotog.scalascraper.dsl.DSL.Extract._
val doc = JsoupBrowser().parseString(html)
val titles: List[String] = doc >> elementList("article.product_pod h3 a") >> attr("title")
val prices: List[String] = doc >> texts("p.price_color")
val books = titles.zip(prices)
books.foreach { case (t, p) => println(s"$p $t") }
article.product_pod h3 a é o seletor durável aqui — a classe do cartão do produto mais o link dentro do seu cabeçalho — e title carrega o nome completo mesmo quando o texto visível está truncado. Extrair o valor de um atributo em vez do texto renderizado é a leitura mais estável sempre que o site o oferecer.
Passo 3 — Seguir a paginação
O catálogo continua através de páginas, cada uma ligando à próxima através de um elemento li.next a. O seletor opcional de scala-scraper >?> retorna None quando esse link está ausente, que é exatamente a condição de parada do loop:
scala
import net.ruippeixotog.scalascraper.model.Document
def nextUrl(doc: Document, base: String): Option[String] =
(doc >?> element("li.next a")).map(a => base + a.attr("href"))
@annotation.tailrec
def crawl(url: String, base: String, acc: List[String]): List[String] = {
val doc = JsoupBrowser().parseString(requests.get(url).text())
val names = doc >> elementList("article.product_pod h3 a") >> attr("title")
nextUrl(doc, base) match {
case Some(next) => crawl(next, base, acc ++ names)
case None => acc ++ names
}
}
val all = crawl("https://books.toscrape.com/catalogue/page-1.html",
"https://books.toscrape.com/catalogue/", Nil)
println(all.size)
Mantenha o loop educado — um host por vez, um pequeno atraso entre as páginas — e trate campos ausentes como Option, nunca como um valor que você presume estar presente.
Obtenha sua chave de API no plano gratuito: app.scrapeless.com
Onde a busca estática para
requests.get faz uma coisa: retorna os bytes que o servidor envia a um cliente anônimo. Isso é suficiente para um catálogo renderizado pelo servidor e nada mais. Dois casos quebram isso, e ambos são comuns:
- Páginas renderizadas pelo cliente. Quando um site constrói seu conteúdo com JavaScript, o HTML que você busca é uma estrutura vazia com os dados ainda bloqueados em scripts. scala-scraper não tem nada para selecionar porque o conteúdo nunca esteve nos bytes.
- Páginas protegidas. Sites com defesas anti-bot ativas respondem a um pedido anônimo com um desafio intersticial, não com a página. Um cliente HTTP simples não tem como superá-lo.
Reproduzir a correção em Scala — um navegador sem cabeça para executar o JavaScript, um pool de proxies residenciais, um solucionador de desafios — é um projeto muito maior do que a raspagem em si. A movimentação pragmática é parar de fazer Scala realizar essa parte e entregar esses URLs a uma API de renderização.
A reviravolta na nuvem: renderizar no lado do servidor, analisar em Scala
A Scrapeless Universal Scraping API pega uma URL de destino, executa-a no lado do servidor através de um navegador real e saída residencial, e retorna o HTML final. A partir do Scala, é um POST com o mesmo cliente requests-scala, e ujson decodifica a resposta:
scala
val apiKey = sys.env("SCRAPELESS_API_KEY")
val payload = ujson.Obj(
"actor" -> "unlocker.webunlocker",
"input" -> ujson.Obj(
"url" -> "https://books.toscrape.com/",
"method" -> "GET",
"redirect" -> true,
"js_render" -> true
)
)
val res = requests.post(
"https://api.scrapeless.com/api/v1/unlocker/request",
headers = Map("Content-Type" -> "application/json", "x-api-token" -> apiKey),
data = ujson.write(payload),
readTimeout = 120000
)
val env = ujson.read(res.text())
val html = env("data").str // DOM renderizado como uma String
js_render: true é a bandeira que suporta a carga: isso informa à API para executar o JavaScript da página e retornar o DOM finalizado, então um site que constrói seu conteúdo do lado do cliente volta como uma marcação real. A partir daqui, html vai direto para o mesmo JsoupBrowser().parseString(html) e os mesmos seletores da Etapa 2 — a parte de parsing do seu scraper não muda, apenas a busca.
O Que Você Recebe de Volta
A resposta da API é um envelope pequeno e previsível:
json
{
"code": 200,
"data": "<html>...DOM renderizado...</html>"
}
// amostra ilustrativa: o esquema é a real forma de uma chamada ao vivo; a string "data" é truncada aqui. Na execução verificada, "data" continha 51.275 bytes de HTML renderizado escapado em JSON.
Uma chamada ao vivo para o endpoint da página de catálogo acima retornou HTTP 200 com 51.275 bytes de HTML renderizado; executar os seletores da Etapa 2 sobre esse HTML resulta em 20 títulos de produtos, sendo o primeiro "A Light in the Attic" a £51,77. Algumas observações da execução:
js_render: truecusta latência, mas compra conteúdo. Desative para páginas estáticas para ir mais rápido; ative quando a página estiver em branco sem isso.ujsonlê o único campo que você precisa.env("data").stré toda a decodificação; o restante do envelope é apenas o statuscode.- Seletores ficam em Scala. A API retorna HTML, então a lógica de extração, tipos e testes vivem no seu código, não atrás de um esquema gerenciado.
- Trate campos ausentes como
Option. Um seletor anulável com>?>é a leitura correta sempre que um cartão pode omitir um preço ou um título.
Conclusão: Scala para o parsing, uma API para a busca difícil
Um scraper em Scala é curto onde a JVM é forte — requests-scala para a requisição, scala-scraper para a extração de seletores CSS, um percurso recursivo de cauda sobre o link da próxima página. Ele encontra a mesma parede que todo scraper estático encontra: páginas renderizadas pelo cliente e defesas anti-bot ativas que um cliente HTTP simples não consegue derrubar. Roteando essas URLs através da API Universal de Scraping mantém a solução em um único POST e deixa seu parsing intocado. Para a mesma divisão de buscar-para-depois-parsing em outra linguagem, veja o guia de scraping com JavaScript e Node.js; a documentação cobre toda a API e seus parâmetros. Fixe js_render no que a página precisa, mantenha seletores em Scala e trate cada campo como opcional.
Pronto para Construir Seu Pipeline de Dados Impulsionado por IA?
Junte-se à nossa comunidade para reivindicar um plano gratuito e conectar-se com desenvolvedores que estão construindo scrapers em JVM: Discord · Telegram.
Inscreva-se em app.scrapeless.com para tempo de execução gratuito e adapte o programa acima aos sites e seletores que seu pipeline Scala precisa. Veja preços para escalas.
FAQ
P: O scraping com Scala é legal?
O scraping de dados visíveis publicamente é geralmente permitido, mas as regras variam de acordo com a jurisdição e o site. Revise os termos de serviço do alvo, respeite as diretivas de robôs, evite dados pessoais ou restritos e consulte um advogado para qualquer atividade comercial.
P: Eu preciso de um proxy?
Para scraping leve de um site renderizado no servidor, não. Para páginas protegidas ou renderizadas pelo cliente, a requisição passa através dos proxies residenciais da API Universal de Scraping em mais de 195 países, então você não precisa montar um pool em Scala.
P: Como é um desafio de bot e como eu consigo uma renderização limpa?
Em vez da página, uma requisição anônima recebe um intersticial de desafio. Roteie essa URL através da API Universal de Scraping com js_render: true; ele executa a página do lado do servidor a partir de um IP residencial confiável e retorna o HTML finalizado.
P: Por que scala-scraper em vez de chamar jsoup diretamente?
O scala-scraper envolve jsoup em um DSL Scala tipado, então os seletores retornam List[String] ou Option[Element] em vez de coleções Java. Você obtém o parser jsoup com resultados que se encaixam na correspondência de padrão Scala.
P: Meus seletores quebraram após a mudança do site. E agora?
A marcação muda. Re-inspecione a página e aperte o seletor — prefira uma classe de contêiner estável mais uma leitura de atributo (article.product_pod h3 a → title) em vez de uma classe CSS hashed que muda no próximo redesign.
P: Posso executar muitas páginas em paralelo?
Sim, mas mantenha em cerca de três trabalhadores por host para permanecer educado e evitar limites de taxa. Um passeio em única hospedagem com recursão de cauda e um pequeno atraso é o padrão seguro.
Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.



