Volver al blog

Escaneo Web en Scala: Recuperar, Analizar y Desbloquear Páginas Protegidas

Ava Wilson
Ava Wilson

Expert in Web Scraping Technologies

23-Jun-2026

TL;DR:

  • Scala raspa la web con dos componentes: un cliente HTTP de JVM para obtener y un analizador basado en jsoup para extraer. requests-scala hace la solicitud; scala-scraper convierte el HTML en nodos seleccionables por CSS.
  • Todo el proyecto son tres dependencias de sbt. requests-scala 0.9.0 para HTTP, scala-scraper 3.2.0 para el análisis, y ujson 4.1.0 para el único sobre de JSON que decodificas más tarde — sin framework que aprender.
  • La paginación es un bucle sobre el enlace "siguiente". scala-scraper lee el href de la siguiente página con un selector opcional, por lo que un recorrido de catálogo es una función recursiva de cola, no una cola.
  • La obtención estática tiene un techo duro: no puede ejecutar JavaScript ni pasar un desafío de bot. Un requests.get simple devuelve la vacía concha de una página renderizada por el cliente y obtiene una intersticial de desafío en sitios protegidos.
  • La API Universal de Scraping Sin Scrapeless cierra esa brecha con un simple POST HTTP. js_render: true ejecuta la página del lado del servidor y devuelve el DOM terminado; el mismo cliente requests-scala que habla con un sitio puede hablar con la API.
  • La llamada de desbloqueo se ejecutó en vivo contra el endpoint: HTTP 200, 51,275 bytes de HTML renderizado, 20 títulos de productos. La forma de la solicitud y respuesta en esta guía provienen directamente de esa ejecución en vivo.
  • Gratis para comenzar. Las nuevas cuentas de Scrapeless incluyen tiempo de ejecución gratis — regístrate en app.scrapeless.com.

Introducción: dónde encaja Scala en el scraping

Scala se ejecuta en la JVM, lo que significa que un scraper escrito en ella hereda jsoup, Akka y un ecosistema HTTP maduro de forma gratuita. El lenguaje se adapta de forma natural cuando el scraping alimenta algo que ya está en la JVM — un trabajo de Spark, un productor de Kafka, un servicio de datos — y deseas la extracción en la misma base de código, con los mismos tipos, que el pipeline que lo consume.

La mitad de obtener y analizar ese trabajo es breve. Un puñado de líneas extrae una página y lee valores de ella utilizando selectores CSS. La fricción comienza donde la de cada scraper: una parte creciente de la web construye su contenido con JavaScript que debe ejecutarse antes de que los datos existan, y los sitios protegidos bloquean el acceso detrás de la huella digital TLS y páginas de desafío que un cliente HTTP en crudo nunca supera.

Esta guía construye primero el scraper estático — proyecto sbt, una obtención HTTP, extracción basada en selectores, paginación — luego dibuja la línea honesta donde ese enfoque se detiene y entrega las páginas difíciles a la API Universal de Scraping Sin Scrapeless. La llamada a la API al final se realizó en vivo; sus números son una captura real.

Lo que puedes hacer con este stack

  • Obtener y analizar en la JVM — requests-scala para la solicitud, scala-scraper (un envoltorio de jsoup) para la extracción por selector CSS.
  • Mantener la extracción en tu base de código de datos — leer valores en tipos de Scala justo al lado del trabajo de Spark o Kafka que los utiliza.
  • Recorrer listados paginados — seguir el enlace de la siguiente página en un bucle recursivo de cola hasta que se agote.
  • Acceder a páginas renderizadas por JavaScript y protegidas — envíalos a la API Universal de Scraping y analiza el HTML renderizado de la misma manera.
  • Saltar la pila anti-bot — la huella digital TLS, IPs residenciales y resolución de desafíos viven en la API, no en tu código Scala.

Por qué la API Universal de Scraping Sin Scrapeless

La API Universal de Scraping Sin Scrapeless toma una URL de destino y devuelve el HTML renderizado y desbloqueado. Para un cliente Scala específicamente, ofrece:

  • Renderizado de JavaScript del lado del servidorjs_render: true devuelve el DOM terminado, por lo que scala-scraper ve contenido real en lugar de una concha vacía.
  • Proxies residenciales en más de 195 países — la obtención proviene de IPs de confianza; nunca construyes ni rotas un grupo en Scala.
  • Manejo de anti-bot — la huella digital TLS y la resolución de desafíos ocurren del lado de la API, fuera de tu proceso JVM.
  • Un simple POST HTTPS — sin SDK que agregar a build.sbt; el cliente requests-scala que ya tienes es suficiente.
  • Un pequeño sobre{"code":200,"data":"<html>…"}, decodificado con el mismo ujson que usas en otros lugares.

Obtén tu clave API en el plan gratuito en app.scrapeless.com.

Requisitos previos

  • Un JDK (11 o más reciente) y sbt instalados
  • Scala 2.13 (las versiones de dependencia a continuación son las compilaciones de 2.13)
  • Una cuenta de Scrapeless y clave API — regístrate en app.scrapeless.com
  • Familiaridad básica con la terminal

Nota: El código Scala en las secciones de creación y paso a continuación es un vacío de requisitos previos en la verificación de esta guía — no había un tiempo de ejecución JVM/sbt disponible en la máquina de verificación, por lo que esos bloques fueron compuestos y verificados contra las API actuales de las bibliotecas y las versiones de Maven Central en lugar de ejecutarse. La llamada de desbloqueo de Scrapeless que soporta fue ejecutada en vivo contra el endpoint; su solicitud y respuesta son una captura real.

Instalar

Crea un directorio de proyecto con dos archivos. build.sbt fija el lenguaje y las tres dependencias:

scala Copy
ThisBuild / scalaVersion := "2.13.16"

lazy val scraper = (project in file("."))
  .settings(
    name := "scala-scraper-demo",
```es
libraryDependencies ++= Seq(
      "com.lihaoyi"      %% "requests"      % "0.9.0",
      "net.ruippeixotog" %% "scala-scraper" % "3.2.0",
      "com.lihaoyi"      %% "ujson"         % "4.1.0"
    )
  )

project/build.properties fija la versión de sbt:

text Copy
sbt.version=1.12.13

scala-scraper incluye jsoup de forma transitiva, por lo que puedes analizar con el motor de jsoup a través de un DSL tipado en Scala sin depender directamente de jsoup. Ejecuta sbt update una vez para resolver todo, luego sbt console para un REPL o sbt run para un main.


Paso 1 — Obtener una página

requests-scala es un cliente HTTP delgado y sincrónico. Una llamada obtiene el cuerpo de la página como una cadena:

scala Copy
val res = requests.get(
  "https://books.toscrape.com/",
  headers = Map("User-Agent" -> "Mozilla/5.0 (compatible; scala-scraper-demo)")
)

println(res.statusCode)   // 200
val html: String = res.text()

res.text() es el HTML en bruto. Para una página renderizada por el servidor como esta, esa cadena ya contiene los datos; para una página renderizada por el cliente contendría una estructura vacía, que es el límite que aborda el Paso 4.


Paso 2 — Analizar con scala-scraper

scala-scraper analiza la cadena en un documento y selecciona nodos con selectores CSS a través de su DSL. El operador >> extrae; elementList, attr y texts dan forma al resultado en valores de Scala:

scala Copy
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 es el selector duradero aquí: la clase de la tarjeta del producto más el enlace dentro de su encabezado, y title lleva el nombre completo incluso cuando el texto visible está truncado. Sacar el valor de un atributo en lugar del texto renderizado es la lectura más estable siempre que el sitio lo ofrezca.


Paso 3 — Seguir la paginación

El catálogo continúa a través de páginas, cada una enlazando a la siguiente a través de un elemento li.next a. El selector opcional de scala-scraper >?> devuelve None cuando ese enlace está ausente, que es exactamente la condición de parada del bucle:

scala Copy
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)

Mantén el bucle cortés: un host a la vez, un pequeño retraso entre páginas, y trata los campos ausentes como Option, nunca como un valor que asumes que está presente.

Obtén tu clave API en el plan gratuito: app.scrapeless.com


Donde termina la obtención estática

requests.get hace una cosa: devuelve los bytes que el servidor envía a un cliente anónimo. Eso es suficiente para un catálogo renderizado por el servidor y nada más. Dos casos lo rompen, y ambos son comunes:

  • Páginas renderizadas por el cliente. Cuando un sitio construye su contenido con JavaScript, el HTML que obtienes es una estructura vacía con los datos todavía bloqueados en scripts. scala-scraper no tiene nada que seleccionar porque el contenido nunca estuvo en los bytes.
  • Páginas protegidas. Los sitios con defensas activas contra bots responden a una solicitud anónima con un intersticial de desafío, no la página. Un cliente HTTP simple no tiene forma de superarlo.

Reproducir la solución en Scala — un navegador sin cabeza para ejecutar el JavaScript, un grupo de proxies residenciales, un solucionador de desafíos — es un proyecto mucho más grande que la obtención en sí. El movimiento pragmático es dejar de hacer que Scala haga esa parte y entregar esas URL a una API de renderizado.

El giro en la nube: renderizar del lado del servidor, analizar en Scala

La API de raspado universal Scrapeless toma una URL objetivo, la ejecuta del lado del servidor a través de un navegador real y egress residencial, y devuelve el HTML terminado. Desde Scala es un POST con el mismo cliente requests-scala, y ujson decodifica la respuesta:

scala Copy
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",
Copy
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 una cadena

js_render: true es la bandera que soporta la carga: le dice a la API que ejecute el JavaScript de la página y devuelva el DOM terminado, así que un sitio que construye su contenido del lado del cliente regresa como un marcado real. Desde aquí, html va directamente al mismo JsoupBrowser().parseString(html) y a los mismos selectores del Paso 2; la parte de análisis de tu scraper no cambia, solo la obtención.

Lo que obtienes de vuelta

La respuesta de la API es un pequeño y predecible sobre:

json Copy
{
  "code": 200,
  "data": "<html>...DOM renderizado...</html>"
}
// muestra ilustrativa: el esquema es la forma real de una llamada en vivo; la cadena "data" está truncada aquí. En la ejecución verificada "data" contenía 51,275 bytes de HTML renderizado escapado en JSON.

Una llamada en vivo al punto final para la página de catálogo anterior devolvió HTTP 200 con 51,275 bytes de HTML renderizado; ejecutar los selectores del Paso 2 sobre ese HTML produce 20 títulos de productos, siendo el primero "A Light in the Attic" a £51.77. Algunas notas de la ejecución:

  • js_render: true cuesta latencia pero compra contenido. Apágalo para páginas estáticas para ir más rápido; enciéndelo cuando la página esté en blanco sin él.
  • ujson lee el único campo que necesitas. env("data").str es la decodificación completa; el resto del sobre es solo el estado code.
  • Los selectores se mantienen en Scala. La API devuelve HTML, por lo que la lógica de extracción, los tipos y las pruebas viven en tu base de código, no detrás de un esquema gestionado.
  • Trata los campos ausentes como Opción. Un selector nullable con >?> es la lectura correcta siempre que una tarjeta pueda omitir un precio o un encabezado.

Conclusión: Scala para el análisis, una API para la obtención difícil

Un scraper en Scala es breve donde la JVM es fuerte: requests-scala para la solicitud, scala-scraper para la extracción de selectores CSS, un recorrido recursivo sobre el enlace de la siguiente página. Se encuentra con la misma pared que todo scraper estático enfrenta: páginas renderizadas por el cliente y defensas activas contra bots que un cliente HTTP simple no puede superar. Enrutar esas URL a través de la API Universal de Scraping mantiene la solución a un solo POST y deja tu análisis intacto. Para la misma división de obtención y análisis en otro lenguaje, consulta la guía de scraping de JavaScript y Node.js; la documentación cubre la API completa y sus parámetros. Fija js_render a lo que la página necesita, guarda los selectores en Scala y trata cada campo como opcional.

¿Listo para construir tu pipeline de datos impulsado por IA?

Únete a nuestra comunidad para reclamar un plan gratuito y conectarte con desarrolladores que construyen scrapers en JVM: Discord · Telegram.

Regístrate en app.scrapeless.com para obtener tiempo de ejecución gratuito y adapta el programa anterior a los sitios y selectores que tu pipeline de Scala necesita. Consulta precios para escalar.

FAQ

P: ¿Es legal el scraping con Scala?
Raspar datos públicamente visibles es generalmente permisible, pero las reglas varían según la jurisdicción y el sitio. Revisa los términos de servicio del objetivo, respeta las directivas de robots, evita datos personales o restringidos, y consulta a un abogado para cualquier cosa comercial.

P: ¿Necesito un proxy?
Para raspar ligeramente un sitio renderizado por el servidor, no. Para páginas protegidas o renderizadas por el cliente, la solicitud sale a través de los proxies residenciales de la API Universal de Scraping en más de 195 países, por lo que no necesitas construir un pool en Scala.

P: ¿Cómo es un desafío para bots y cómo consigo un render limpio?
En lugar de la página, una solicitud anónima recibe un intersticial de desafío. Enruta esa URL a través de la API Universal de Scraping con js_render: true; ejecuta la página del lado del servidor desde una IP residencial de confianza y devuelve el HTML terminado.

P: ¿Por qué usar scala-scraper en lugar de llamar a jsoup directamente?
scala-scraper envuelve jsoup en un DSL tipado de Scala, por lo que los selectores devuelven List[String] o Option[Element] en lugar de colecciones de Java. Obtienes el analizador de jsoup con resultados que se ajustan a la coincidencia de patrones de Scala.

P: Mis selectores dejaron de funcionar después de que el sitio cambió. ¿Qué hago ahora?
El marcado rota. Vuelve a inspeccionar la página y ajusta el selector; prefiera una clase contenedora estable más una lectura de atributo (article.product_pod h3 atitle) en lugar de una clase CSS hasheada que cambie en el próximo rediseño.

P: ¿Puedo ejecutar muchas páginas en paralelo?

Copy
Sí, pero mantén aproximadamente tres trabajadores por host para que te mantengas cortés y evites los límites de tasa. Un recorrido en un solo host mediante recursión final con un pequeño retraso es la opción segura por defecto.

En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.

Artículos más populares

Catalogar