Volver al blog
Guías
Mihai MaximLast updated on May 12, 202613 min read

Análisis de HTML en Java con Jsoup

Análisis de HTML en Java con Jsoup
En resumen: Jsoup es la biblioteca predeterminada para el análisis de HTML en Java. Esta guía recorre todo el ciclo de vida (configuración de Maven, carga de un documento, selectores CSS, recorrido del DOM, extracción, modificación y serialización), además de incluir un proyecto de scraping ejecutable, gestión de errores, paginación y las limitaciones que te llevan a optar por un navegador sin interfaz gráfica o una API de scraping.

Si necesitas extraer o reescribir HTML dentro de un servicio JVM, tienes varias opciones, pero para la mayoría de los trabajos reales, el análisis de HTML en Java sigue empezando y terminando con Jsoup. El web scraping es la extracción automatizada de datos del código fuente HTML de un sitio web, y Jsoup es la biblioteca de código abierto que convierte ese código fuente en un DOM navegable que puedes consultar con selectores CSS y modificar in situ.

Este tutorial de Jsoup está pensado para desarrolladores de Java de nivel intermedio (ingenieros de backend, ingenieros de datos, profesionales de SEO y control de calidad, o cualquier persona que realice migraciones de contenido) que busquen una guía práctica en lugar de una visión general de marketing. Cubrimos la configuración de Maven, la carga de un Document desde una String, Fileo una URL, la configuración de la solicitud HTTP, el manejo de errores, el recorrido y la selección de elementos, la extracción de texto y atributos, la modificación de nodos y la serialización del resultado de vuelta a HTML limpio. El artículo concluye con un proyecto de scraping completo y ejecutable, con notas sobre paginación y limitación de velocidad.

También somos sinceros sobre las limitaciones: Jsoup no ejecuta JavaScript, no rota direcciones IP ni elude las defensas antibots. La sección final indica dónde se queda sin recursos y a qué recurrir a continuación.

Por qué Jsoup es la opción predeterminada para el análisis de HTML en Java

Cuando los datos que necesitas se encuentran en una página web pública y el sitio no tiene API, escribes un scraper. Para el análisis de HTML en Java, Jsoup ha sido la opción práctica por defecto durante años: código abierto, lanzamientos constantes, documentación sólida y una API fluida que se adapta perfectamente desde jQuery o JavaScript DOM básico. Lo más importante es que cubre ambas partes del flujo de trabajo: leer HTML y escribir HTML.

De un vistazo: lo que Jsoup puede y no puede hacer

Jsoup implementa la especificación HTML5 de WHATWG, por lo que analiza prácticamente cualquier marcado, desde el más impecable hasta el que está realmente dañado, tal y como lo haría un navegador moderno. Obtienes un árbol DOM, selectores al estilo jQuery y métodos tanto para leer como para escribir. Lo que no hace es ejecutar JavaScript. Cualquier cosa inyectada por un marco de trabajo del lado del cliente tras la respuesta inicial (un almacén de React, filas de carga diferida, contenido con hidratación) es invisible para Jsoup. Esa limitación da lugar a la sección de restricciones que viene a continuación.

Configuración de Jsoup en un proyecto Maven

Inicia un esqueleto de Maven con mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart, y luego añade la dependencia de Jsoup a pom.xml. En el momento de escribir este artículo, la versión actual es la serie 1.17.x, pero comprueba siempre la última versión estable en Maven Central antes de fijar una versión en producción:

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.17.x</version>
</dependency>

Si quieres ejecutar los ejemplos con mvn exec:java, registra el plugin Exec Maven en tu <plugins> . Utiliza la versión que aparezca como actual en la página del plugin; los tutoriales más antiguos citan la 3.0.0, que puede estar ya desactualizada. ¿No utilizas Maven? Jsoup se distribuye como un único archivo JAR que puedes colocar en la ruta de clases, y los usuarios de Gradle pueden declarar implementation 'org.jsoup:jsoup:1.17.x'.

Carga de HTML en un documento Jsoup

El punto de entrada para el análisis de HTML en Java con esta biblioteca es la Jsoup clase. Puedes analizar HTML desde un String, un File, un InputStreamo (lo más habitual) obtenerlo directamente de una URL. Un ejemplo sencillo de conexión con Jsoup tiene este aspecto:

// From a string, great for unit tests
Document fromString = Jsoup.parse("<html><body><p>Hello</p></body></html>");

// From a local file
Document fromFile = Jsoup.parse(new File("page.html"), "UTF-8");

// From a live URL: issues the HTTP request, then parses the response
Document doc = Jsoup.connect("https://example.com").get();

En comparación con crear tu propio HttpURLConnection, la API fluida Jsoup.connect(...) API fluida ahorra mucho código repetitivo: gestiona el socket, lee el cuerpo, decodifica el juego de caracteres y devuelve el contenido analizado Document en una sola llamada. Ese Document es el DOM en memoria con el que trabajas para todo lo demás, desde selectores CSS hasta la modificación del DOM.

Configuración de la conexión Jsoup (encabezados, cookies, tiempos de espera, agente de usuario)

Jsoup.connect(url) devuelve un Connection objeto que puedes configurar antes de enviar la solicitud. Los valores predeterminados están bien para puntos finales amigables, pero la mayoría de los objetivos reales necesitan al menos un User-Agent real y un tiempo de espera razonable:

Document doc = Jsoup.connect("https://example.com/listing")
    .userAgent("Mozilla/5.0 (compatible; MyJavaScraper/1.0; +https://yourdomain.tld/bot)")
    .referrer("https://example.com")
    .header("Accept-Language", "en-US,en;q=0.9")
    .cookie("session", "abc123")
    .timeout(10_000)
    .data("q", "java")
    .method(Connection.Method.GET)
    .get();

Elige un User-Agent que identifique a tu bot de forma honesta. Muchos servidores devuelven una respuesta simplificada, o bloquean directamente, cuando el UA parece un cliente HTTP Java predeterminado.

Gestión de errores HTTP, códigos de estado y tiempos de espera

Aquí hay dos excepciones importantes. HttpStatusException Se lanza cuando el servidor devuelve un 4xx o 5xx y te proporciona tanto la URL problemática como el código de estado. IOException cubre todo lo demás: fallos de DNS, restablecimientos de conexión, tiempos de espera de sockets. Captura ambos:

try {
    Document doc = Jsoup.connect(url).timeout(10_000).get();
} catch (HttpStatusException e) {
    log.warn("Bad status {} for {}", e.getStatusCode(), e.getUrl());
} catch (IOException e) {
    // retry with exponential backoff, then escalate
}

Si realmente necesitas el cuerpo de una página 404 (para la detección de soft-404), encadena .ignoreHttpErrors(true) antes de .get(). Envuelve las llamadas de red en un bucle de reintentos con retroceso exponencial para los rastreadores de producción; los errores 5xx transitorios y los de reinicio son normales a gran escala.

Selección de elementos con selectores CSS de Jsoup

Una vez que tengas un Document, consultarlo es cuestión de una sola línea. Document.select(String cssQuery) acepta la misma sintaxis que usarías en querySelectorAll y devuelve una Elements colección que nunca null, incluso cuando no hay coincidencias. Eso por sí solo elimina toda una clase de excepciones NullPointerException con las que, de otro modo, te encontrarías al usar código DOM ingenuo.

El vocabulario de selectores CSS de Jsoup va mucho más allá de las etiquetas y las clases. Un breve recorrido que vale la pena marcar como favorito junto a cualquier hoja de referencia de selectores CSS:

Selector

Coincide

div.post-card

<div> con clase post-card

article > h2

hijo directo h2 de un article

a[href^=https]

enlaces cuyo href comienzan por https

img[src*=authors]

imágenes cuyo src contiene la subcadena authors

li:nth-child(2)

segundo li en su sección principal

section:has(h2)

que contengan al menos un h2

p:contains(error)

párrafos que contengan el texto literal «error»

Combínalos libremente. Un patrón eficaz consiste en aplicar un selector secundario a un elemento previamente seleccionado Element en lugar de volver a ejecutar consultas desde la raíz del documento.

getElementById, getElementsByClass y select Comparado

Jsoup refleja la API DOM de JavaScript para los lectores que desean getter explícitos. getElementById(id) devuelve el único Element (o null) con ese ID, idéntico a document.getElementById en un navegador. getElementsByClass(name) devuelve todas las coincidencias, igual que document.getElementsByClassName. select(cssQuery) es el equivalente a querySelectorAll y es el más flexible de los tres.

Utiliza los getter explícitos cuando la intención sea obvia (un ID estable o una única clase semántica) y select() cuando necesites composición o filtrado de atributos. Una advertencia del mundo real: evita las clases de utilidad generadas por el framework como selectores de anclaje. Una clase de Tailwind como p-[10px] o text-slate-700 es un detalle de la salida de la compilación que puede desaparecer en la siguiente implementación. Apóyate en ID estables, roles ARIA o etiquetas semánticas, y tus rastreadores envejecerán mucho mejor.

Recorrido del árbol DOM: padres, hermanos, hijos, primero/último/enésimo

Los selectores te abren la puerta; el recorrido te lleva a hermanos y antepasados. La API de documentos de Jsoup expone parent(), parents(), children(), y siblingElements(), además de acceso indexado a través de first(), last(), y get(int n). Cada Element tiene además su propio select() método que limita una consulta a ese subárbol, lo cual es la forma más limpia de escribir selectores resilientes:

Element card = doc.selectFirst("article.post-card");
String title  = card.select("h2 > a").text();
String author = card.parent().select(".byline").text();
Elements tags = card.children().select("span.tag");

Subir hasta un antepasado estable y volver a bajar es mucho más duradero que encadenar selectores de clase frágiles desde la raíz del documento, especialmente en páginas que incluyen CSS en JS o marcos de trabajo de clases de utilidad.

Extraer texto, HTML y atributos de elementos

Una vez seleccionado un Element, cuatro métodos cubren casi todos los casos a la hora de extraer datos de HTML en Java. text() devuelve texto visible con espacios en blanco colapsados (análogo a innerText). html() devuelve el HTML interno como una cadena. outerHtml() incluye las propias etiquetas del elemento. ownText() devuelve solo los nodos de texto directos del elemento, omitiendo los descendientes.

Para los atributos, attr("href") lee un valor y absUrl("href") resuelve las URL relativas con respecto a la URI base del documento, lo cual resulta muy útil al extraer listas de enlaces. La iteración es sencilla, ya que Elements es Iterable:

for (Element link : doc.select("a[href]")) {
    System.out.println(link.text() + " -> " + link.absUrl("href"));
}

También puedes utilizar streaming, forEacho extraer por índice con get(n). Elige la opción que te resulte más natural para tu código.

Modificación y generación de HTML con Jsoup

La mayoría de los tutoriales se limitan a la extracción, pero el análisis de HTML en Java con Jsoup es genuinamente bidireccional. El mismo attr(), text(), y html() métodos funcionan también como setters. Puedes crear nuevos nodos con new Element(Tag.valueOf("...")), adjuntarlos con appendChild() o appendElement(), y eliminarlos con remove(). La interfaz de Jsoup para modificar HTML tiene este aspecto:

Document doc = Jsoup.parse(rawHtml);

// Edit existing nodes
doc.select("a.tracker").forEach(a -> a.attr("rel", "nofollow"));
doc.selectFirst("h1").text("Updated Title");

// Add a new node
Element note = new Element(Tag.valueOf("p"), "")
    .text("Edited by my scraper at " + Instant.now());
doc.body().appendChild(note);

// Remove ad slots
doc.select("div.ad-slot").remove();

// Serialize back to a clean HTML string
String cleaned = doc.html();

Ese ciclo completo (analizar, modificar, serializar) es lo que hace que Jsoup sea útil para migraciones de contenido, limpieza de HTML y normalización de feeds, no solo para el scraping puntual.

Proyecto práctico: extraer datos de un listado de blogs de principio a fin

Para ponerlo todo en práctica, crea un pequeño scraper que extraiga el título, el enlace, la imagen de cabecera y el avatar del autor de cada entrada de un blog público. Abre primero la página en DevTools; el reconocimiento manual siempre es mejor que las conjeturas. Identifica un selector de contenedor estable para cada entrada y, a continuación, escribe selectores campo por campo para él.

Document doc = Jsoup.connect("https://example.com/blog")
    .userAgent("MyJavaScraper/1.0")
    .timeout(10_000)
    .get();

for (Element card : doc.select("article.post-card")) {
    String title   = card.select("h2 > a").text();
    String url     = card.select("h2 > a").absUrl("href");
    String header  = card.selectFirst("img.header-image").absUrl("src");
    String avatar  = card.select("img[src*=authors]").attr("abs:src");

    System.out.printf("%s | %s | %s | %s%n", title, url, header, avatar);
}

Cada campo tiene su propio selector basado en la intención. img[src*=authors] Filtra por subcadena de atributos, lo que funciona mejor que encadenar selectores estructurales cuando cambia el marcado. Ese tipo de scraping web estructurado en Java con Jsoup supera siempre al frágil análisis basado en índices.

Recorrido por páginas paginadas

La mayoría de los listados siguen un esquema de URL predecible, como /blog, /blog/page/2/, /blog/page/3/. Trata la página 1 como un caso especial y recorre en bucle hasta que te encuentres con un conjunto de resultados vacío o un 404 de HttpStatusException. Espera uno o dos segundos entre solicitudes, aleatorízalas ligeramente y respeta el archivo robots.txt del sitio (RFC 9309). La paginación sin limitación de velocidad es la forma más rápida de ser bloqueado y la razón más común por la que la gente acaba leyendo artículos sobre por qué te bloquean.

Limitaciones de Jsoup y cuándo buscar una alternativa

El límite máximo de Jsoup es JavaScript. Analiza lo que el servidor devuelve inicialmente, por lo que cualquier cosa renderizada del lado del cliente (SPA de React, Vue o Angular, desplazamiento infinito con carga diferida, contenido restringido tras hidratación) es invisible. Tampoco ofrece soporte para renderizado sin interfaz gráfica, rotación de proxies o elusión de antibots.

Cuando la página es dinámica, combina Jsoup con un navegador sin interfaz gráfica: Selenium y Playwright controlan un Chromium real; HtmlUnit es una opción nativa de JVM más ligera; Jaunt ofrece una API de Java similar con HTTP integrado. Cuando la página es estática pero hostil (Cloudflare, bloqueos frecuentes de IP, huellas digitales), redirige la solicitud a través de una API de scraping gestionada que se encarga de los proxies y los CAPTCHAs, y luego envía la respuesta HTML directamente a Jsoup. Esto mantiene limpio tu código de análisis y reduce las partes móviles.

Resumen: Creación de analizadores HTML en Java resistentes

El flujo de trabajo completo para el análisis de HTML en Java con Jsoup se resume en cuatro pasos: cargar, seleccionar, extraer o modificar, y luego generar la salida. Para una lectura más profunda, el libro de recetas de Jsoup y Javadocs son las referencias canónicas. Antes de empezar un nuevo scraper, repasa una breve lista de verificación: ¿La página es estática o se renderiza con JS? ¿Es probable que el objetivo me bloquee? ¿Necesito modificar el HTML o solo leerlo? Esas tres respuestas te dirán si Jsoup por sí solo es suficiente.

Conclusiones clave

  • Utiliza Jsoup para cualquier tarea de análisis de HTML en Java en la que el marcado sea renderizado por el servidor. Gestiona el HTML malformado tal y como lo haría un navegador moderno.
  • Jsoup.connect(url).get() Combina la obtención y el análisis en una sola llamada. Establece siempre un User-Agent real y un tiempo de espera no predeterminado, y captura tanto HttpStatusException y IOException.
  • select() devuelve una Elements lista que puede estar vacía, pero nunca null. Da preferencia a los ID estables, los roles ARIA y los selectores semánticos frente a las clases de utilidad generadas por el marco de trabajo.
  • Jsoup es bidireccional: attr, text, y html como setters, además de appendChild y remove, te permiten editar y volver a serializar HTML, no solo leerlo.
  • Jsoup no ejecuta JavaScript. Para las aplicaciones de página única (SPA), combínalo con Selenium, Playwright o HtmlUnit; para los objetivos bloqueados, redirige la solicitud a través de una API de scraping gestionada.

Preguntas frecuentes

¿Puede Jsoup extraer datos de aplicaciones renderizadas en JavaScript o de página única?

No. Jsoup solo analiza el HTML sin procesar que devuelve el servidor, por lo que cualquier cosa generada por un marco de trabajo del lado del cliente tras la carga de la página le resulta invisible. Para extraer datos de aplicaciones de página única (SPA) o páginas que se cargan en el cliente, utiliza un navegador real o sin interfaz gráfica con Selenium, Playwright o HtmlUnit, captura el HTML completamente renderizado y, a continuación, pasa esa cadena a Jsoup.parse(...) para la extracción basada en selectores.

¿En qué se diferencia Jsoup de HtmlUnit, Jaunt o Selenium para el análisis de HTML?

Jsoup es un analizador HTML puro. No ejecuta JavaScript, no ejecuta un motor JS ni simula un navegador. Tanto HtmlUnit como Selenium renderizan páginas con un motor JS (HtmlUnit dentro de la JVM, Selenium a través de un controlador de navegador real). Jaunt se acerca más a Jsoup, ya que es un analizador más un cliente HTTP sencillo. Utiliza Jsoup cuando la página sea estática; utiliza los demás cuando necesites renderización o interacción.

¿Cómo evito que me bloqueen o me limiten la velocidad al analizar páginas con Jsoup?

Identifica tu bot de forma honesta en el User-Agent, limita las solicitudes a unas pocas por segundo por host, aleatoriza los retrasos y reutiliza las cookies cuando sea apropiado. Lee y respeta robots.txt. Para trabajos de mayor volumen o objetivos hostiles, redirige las solicitudes a través de un grupo de proxies residenciales o rotativos, ya que Jsoup no tiene incorporada la rotación de IP, la suplantación de huellas digitales ni el manejo de CAPTCHA.

¿Puede Jsoup analizar XML, fuentes RSS o HTML malformado?

Sí a las tres preguntas. Pasa un analizador XML explícitamente con Jsoup.parse(input, baseUri, Parser.xmlParser()) para fuentes RSS, mapas de sitio y otros documentos XML. En el caso del HTML malformado, el analizador predeterminado es tolerante y normaliza el marcado tal y como lo haría un navegador moderno, por lo que las etiquetas sin cerrar y los caracteres extraños suelen seguir produciendo un Document.

¿Cuál es la última versión estable de Jsoup y cómo la mantengo actualizada?

Consulte directamente Maven Central, ya que los números de versión cambian con frecuencia y cualquier número citado en un tutorial puede estar ya desactualizado. Suscríbase a las notas de lanzamiento en el repositorio de GitHub de Jsoup, o ejecute un plugin de actualización de dependencias de Maven como versions:display-dependency-updates en CI para que aparezcan automáticamente las actualizaciones disponibles. Tanto Renovate como Dependabot funcionan si tu repositorio está alojado en las plataformas correspondientes.

Conclusión

Si terminas esta guía y recuerdas una sola cosa, que sea el ritmo de cuatro pasos: carga el HTML en un Document, selecciona lo que te interesa, extráelo o modifícalo, y vuelve a serializarlo. Esa secuencia es la columna vertebral de cada scraper basado en Jsoup, cada migración de contenido y cada sanitizador de HTML que escribas. Añade un User-Agent real, tiempos de espera razonables, un manejo estructurado de excepciones y una política de reintentos con backoff, y tendrás un analizador que sobrevive al tráfico de producción.

La advertencia honesta sigue siendo válida: Jsoup no ejecuta JavaScript y no elude las defensas antibots. Si la página se renderiza del lado del cliente, necesitas un navegador sin interfaz gráfica. Si el objetivo bloquea tu IP o identifica tu fetcher, necesitas una capa de solicitudes más inteligente.

Es en ese segundo caso donde una API de scraping gestionada demuestra su valía. La API Scraper de WebScrapingAPI devuelve el HTML sin procesar incluso de objetivos hostiles, gestionando por su cuenta la rotación de proxies, los CAPTCHAs y la identificación de navegadores, de modo que puedes mantener tu código de análisis de Jsoup sin cambios y simplemente cambiar el paso de obtención. Es la forma más limpia que hemos encontrado de dotar de resiliencia en producción a un analizador Java ligero.

Acerca del autor
Mihai Maxim, Desarrollador Full Stack @ WebScrapingAPI
Mihai MaximDesarrollador Full Stack

Mihai Maxim es desarrollador full stack en WebScrapingAPI, donde colabora en todas las áreas del producto y ayuda a crear herramientas y funciones fiables para la plataforma.

Empieza a crear

¿Estás listo para ampliar tu recopilación de datos?

Únete a más de 2000 empresas que utilizan WebScrapingAPI para extraer datos de la web a escala empresarial sin ningún gasto de infraestructura.