Voltar ao blogue
Guias
Mihai MaximLast updated on May 12, 202613 min read

Análise de HTML em Java com Jsoup

Análise de HTML em Java com Jsoup
Resumo: O Jsoup é a biblioteca padrão para a análise de HTML em Java. Este guia percorre todo o ciclo de vida (configuração do Maven, carregamento de um documento, seletores CSS, percurso do DOM, extração, modificação e serialização), além de incluir um projeto de scraping executável, tratamento de erros, paginação e os limites que o levam a optar por um navegador headless ou uma API de scraping.

Se precisar de extrair ou reescrever HTML dentro de um serviço JVM, tem algumas opções, mas para a maioria dos trabalhos reais, a análise de HTML em Java ainda começa e termina com o Jsoup. O web scraping é a extração automatizada de dados da fonte HTML de um site, e o Jsoup é a biblioteca de código aberto que transforma essa fonte num DOM navegável que pode consultar com seletores CSS e modificar no local.

Este tutorial do Jsoup foi concebido para programadores Java de nível intermédio (engenheiros de backend, engenheiros de dados, profissionais de SEO e QA, qualquer pessoa que execute migrações de conteúdo) que desejam um guia prático em vez de uma visão geral de marketing. Abordamos a configuração do Maven, o carregamento de um Document a partir de um String, Fileou URL, a configuração do pedido HTTP, o tratamento de erros, a navegação e seleção de elementos, a extração de texto e atributos, a modificação de nós e a serialização do resultado de volta para HTML limpo. Um projeto de scraping completo e executável encerra o artigo, com notas sobre paginação e limitação de taxa.

Somos também honestos quanto aos limites: o Jsoup não executa JavaScript, não alterna IPs nem contorna defesas anti-bot. A secção final identifica onde o Jsoup chega ao fim e o que se deve procurar a seguir.

Por que o Jsoup é a escolha padrão para análise de HTML em Java

Quando os dados de que precisa se encontram numa página web pública e o site não tem API, escreve-se um scraper. Para a análise de HTML em Java, o Jsoup tem sido a escolha padrão prática há anos: código aberto, lançamentos regulares, documentação sólida e uma API fluida que se adapta facilmente a partir de jQuery ou JavaScript DOM puro. Fundamentalmente, abrange ambas as partes do fluxo de trabalho: ler HTML e escrever HTML.

O que o Jsoup pode e não pode fazer num relance

O Jsoup implementa a especificação HTML5 do WHATWG, pelo que analisa praticamente qualquer marcação, desde a mais impecável até à genuinamente danificada, tal como um navegador moderno faria. O utilizador obtém uma árvore DOM, seletores ao estilo jQuery e métodos tanto para leitura como para escrita. O que não faz é executar JavaScript. Qualquer coisa injetada por uma estrutura do lado do cliente após a resposta inicial (um armazenamento React, linhas carregadas de forma diferida, conteúdo condicionado à hidratação) é invisível para o Jsoup. Esse limite determina a secção de limitações mais adiante.

Configurar o Jsoup num projeto Maven

Crie um esqueleto Maven com mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart, depois adicione a dependência do Jsoup a pom.xml. No momento da redação deste artigo, a versão atual é a série 1.17.x, mas confirme sempre a versão estável mais recente no Maven Central antes de fixar uma versão em produção:

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

Se quiser executar os exemplos com mvn exec:java, registe o Exec Maven Plugin no seu <plugins> . Use a versão que a página do plugin indicar como atual; tutoriais mais antigos citam a 3.0.0, que já pode estar desatualizada. Não usa o Maven? O Jsoup vem como um único JAR que pode ser colocado no classpath, e os utilizadores do Gradle podem declarar implementation 'org.jsoup:jsoup:1.17.x'.

Carregar HTML num documento Jsoup

O ponto de entrada para a análise de HTML em Java com esta biblioteca é a Jsoup classe. Pode analisar HTML a partir de um String, um File, um InputStreamou (mais frequentemente) obtê-lo diretamente de um URL. Um exemplo simples de conexão com o Jsoup fica assim:

// 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();

Em comparação com criar o seu próprio HttpURLConnection, a API fluente Jsoup.connect(...) API poupa muito código repetitivo: ela gere o socket, lê o corpo, descodifica o conjunto de caracteres e devolve um conteúdo analisado Document em uma única chamada. Esse Document é o DOM na memória com o qual trabalha para tudo o resto, desde seletores CSS até à modificação do DOM.

Configurar a ligação Jsoup (cabeçalhos, cookies, tempos de espera, agente do utilizador)

Jsoup.connect(url) retorna um Connection objeto que pode configurar antes de enviar o pedido. Os valores predefinidos são adequados para pontos de extremidade amigáveis, mas a maioria dos alvos reais requer, no mínimo, um User-Agent real e um tempo limite razoável:

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();

Escolha um User-Agent que identifique o seu bot de forma honesta. Muitos servidores devolvem uma resposta simplificada, ou bloqueiam de imediato, quando o UA se assemelha a um cliente HTTP Java padrão.

Tratamento de erros HTTP, códigos de estado e tempos de espera

Há duas exceções importantes aqui. HttpStatusException é lançada quando o servidor devolve um 4xx ou 5xx e fornece-lhe tanto o URL em questão como o código de estado. IOException cobre tudo o resto: falhas de DNS, reinicializações de ligação, tempos de espera de socket. Intercepte 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
}

Se precisares realmente do corpo de uma página 404 (para deteção de soft-404), encadeia .ignoreHttpErrors(true) antes .get(). Envolva as chamadas de rede num ciclo de repetição com recuo exponencial para scrapers de produção; erros 5xx transitórios e de reinicialização são normais em grande escala.

Selecionar elementos com seletores CSS do Jsoup

Assim que tiver um Document, a consulta é feita com uma única linha de código. Document.select(String cssQuery) aceita a mesma sintaxe que usaria em querySelectorAll e retorna uma Elements coleção que nunca é null, mesmo quando nada corresponde. Isso por si só elimina toda uma classe de exceções NullPointerException que, de outra forma, encontraria com código DOM simplista.

O vocabulário de seletores CSS do Jsoup vai muito além de tag e class. Um breve tour que vale a pena marcar como favorito ao lado de qualquer folha de referência de seletores CSS:

Seletor

Corresponde

div.post-card

<div> com classe post-card

article > h2

filho-direto h2 de um article

a[href^=https]

links cujos href começam por https

img[src*=authors]

imagens cujo src contém a subcadeia authors

li:nth-child(2)

segunda li na sua secção pai

section:has(h2)

que contenham pelo menos um h2

p:contains(error)

parágrafos que contenham o texto literal «erro»

Combine-os livremente. Um padrão eficaz consiste em definir o âmbito de um seletor filho em relação a um Element em vez de reexecutar consultas a partir da raiz do documento.

getElementById, getElementsByClass e select Comparado

O Jsoup espelha a API DOM do JavaScript para leitores que desejam getters explícitos. getElementById(id) retorna o único Element (ou null) com esse ID, idêntico ao document.getElementById num navegador. getElementsByClass(name) retorna todas as correspondências, tal como document.getElementsByClassName. select(cssQuery) é o equivalente a querySelectorAll e é o mais flexível dos três.

Use os getters explícitos quando a intenção for óbvia (um ID estável ou uma única classe semântica) e select() quando precisar de composição ou filtragem de atributos. Um aviso prático: evite classes utilitárias geradas por frameworks como seletores âncora. Uma classe Tailwind como p-[10px] ou text-slate-700 é um detalhe da compilação que pode desaparecer na próxima implementação. Recorra a IDs estáveis, funções ARIA ou tags semânticas, e os seus scrapers terão uma durabilidade muito maior.

Percorrendo a árvore DOM: pais, irmãos, filhos, primeiro/último/n-ésimo

Os seletores abrem-lhe a porta; a percussão leva-o aos irmãos e antepassados. A API de documentos do Jsoup expõe parent(), parents(), children(), e siblingElements(), além de acesso indexado através de first(), last(), e get(int n). Cada Element também tem o seu próprio select() método que delimita uma consulta a essa subárvore, o que é a forma mais limpa de escrever seletores 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 até um antepassado estável e voltar para baixo é muito mais durável do que encadear seletores de classe frágeis a partir da raiz do documento, especialmente em páginas que utilizam CSS-in-JS ou frameworks de classes utilitárias.

Extrair texto, HTML e atributos de elementos

Depois de selecionar um Element, quatro métodos cobrem quase todos os casos em que se extrai dados de HTML em Java. text() retorna texto visível, com espaços em branco colapsados (análogo a innerText). html() retorna o HTML interno como uma string. outerHtml() inclui as próprias tags do elemento. ownText() retorna apenas os nós de texto diretos do elemento, ignorando os descendentes.

Para atributos, attr("href") lê um valor e absUrl("href") resolve URLs relativas em relação ao URI base do documento, o que é inestimável ao extrair listas de links. A iteração é simples, uma vez que Elements é Iterable:

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

Você também pode fazer streaming, usar forEachou extrair por índice com get(n). O que parecer mais natural para a sua base de código está bem.

Modificar e gerar HTML com o Jsoup

A maioria dos tutoriais limita-se à extração, mas a análise de HTML em Java com o Jsoup é genuinamente bidirecional. O mesmo attr(), text()e html() funções funcionam também como setters. Pode criar novos nós com new Element(Tag.valueOf("...")), anexá-los com appendChild() ou appendElement(), e eliminá-los com remove(). A superfície de modificação de HTML do Jsoup tem este aspeto:

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();

Esse ciclo completo (analisar, alterar, serializar) é o que torna o Jsoup útil para migrações de conteúdo, sanitização de HTML e normalização de feeds, e não apenas para scraping pontual.

Projeto prático: extrair uma lista de blogs de ponta a ponta

Para juntar tudo, crie um pequeno scraper que extraia o título, o link, a imagem de cabeçalho e o avatar do autor de cada cartão de publicação numa lista de blogues pública. Abra primeiro a página no DevTools; o reconhecimento manual supera sempre as suposições. Identifique um seletor de contentor estável por cartão e, em seguida, escreva seletores campo a campo para o mesmo.

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 tem o seu próprio seletor orientado pela intenção. img[src*=authors] Filtra por substring de atributo, o que funciona melhor do que encadear seletores estruturais quando a marcação muda. Esse tipo de scraping web Java estruturado com Jsoup supera sempre a análise frágil baseada em índices.

Percorrer páginas paginadas

A maioria das listagens segue um esquema de URL previsível, como /blog, /blog/page/2/, /blog/page/3/. Trate a página 1 como um caso especial e faça um loop até encontrar um conjunto de resultados vazio ou um 404 de HttpStatusException. Aguarde um ou dois segundos entre os pedidos, randomize-os ligeiramente e respeite o ficheiro robots.txt do destino (RFC 9309). A paginação sem limitação de taxa é a forma mais rápida de ser banido e a razão mais comum pela qual as pessoas acabam por ler artigos sobre o motivo pelo qual se é bloqueado.

Limitações do Jsoup e quando procurar uma alternativa

O limite máximo do Jsoup é o JavaScript. Ele analisa o que o servidor devolve inicialmente, pelo que tudo o que é renderizado do lado do cliente (SPAs React, Vue ou Angular, rolagem infinita carregada de forma preguiçosa, conteúdo bloqueado por hidratação) fica invisível. Também não oferece suporte para renderização headless, rotação de proxy ou contorno anti-bot.

Quando a página é dinâmica, combine o Jsoup com um navegador headless: o Selenium e o Playwright controlam um Chromium real; o HtmlUnit é uma opção mais leve nativa da JVM; o Jaunt oferece uma API Java semelhante com HTTP integrado. Quando a página for estática mas hostil (Cloudflare, bloqueios frequentes de IP, fingerprinting), encaminhe o pedido através de uma API de scraping gerida que lide com proxies e CAPTCHAs e, em seguida, reenvie o HTML de resposta diretamente para o Jsoup. Isso mantém o seu código de análise limpo e reduz as partes móveis.

Conclusão: Construir analisadores HTML em Java resilientes

O fluxo de trabalho completo para a análise de HTML em Java com o Jsoup resume-se a quatro passos: carregar, selecionar, extrair ou modificar e, por fim, gerar a saída. Para uma leitura mais aprofundada, o livro de receitas do Jsoup e os Javadocs são as referências canónicas. Antes de iniciar um novo scraper, percorra uma lista de verificação rápida: A página é estática ou renderizada em JS? É provável que o alvo me bloqueie? Preciso de alterar o HTML ou apenas lê-lo? Essas três respostas indicam-lhe se o Jsoup por si só é suficiente.

Pontos-chave

  • Utilize o Jsoup para qualquer tarefa de análise de HTML em Java em que a marcação seja renderizada pelo servidor. Ele lida com HTML malformado da mesma forma que um navegador moderno.
  • Jsoup.connect(url).get() Combina a obtenção e a análise numa única chamada. Defina sempre um User-Agent real e um tempo de espera não padrão, e capture ambos HttpStatusException e IOException.
  • select() retorna uma Elements lista que pode estar vazia, mas nunca null. Prefira IDs estáveis, funções ARIA e seletores semânticos em vez de classes utilitárias geradas por frameworks.
  • O Jsoup é bidirecional: attr, text, e html como setters, além de appendChild e remove, permitem editar e resserializar HTML, não apenas lê-lo.
  • O Jsoup não executa JavaScript. Para SPAs, combine-o com o Selenium, o Playwright ou o HtmlUnit; para alvos bloqueados, encaminhe o pedido através de uma API de scraping gerida.

Perguntas frequentes

O Jsoup consegue extrair dados de aplicações renderizadas em JavaScript ou de página única?

Não. O Jsoup apenas analisa o HTML bruto que o servidor devolve, pelo que tudo o que for gerado por uma estrutura do lado do cliente após o carregamento da página é invisível para ele. Para extrair SPAs ou páginas que se atualizam no cliente, utilize um navegador real ou headless com o Selenium, o Playwright ou o HtmlUnit, capture o HTML totalmente renderizado e, em seguida, passe essa cadeia de caracteres ao Jsoup.parse(...) para extração baseada em seletores.

Em que é que o Jsoup difere do HtmlUnit, do Jaunt ou do Selenium na análise de HTML?

O Jsoup é um analisador HTML puro. Não executa JavaScript, não utiliza um motor JS nem simula um navegador. Tanto o HtmlUnit como o Selenium renderizam páginas com um motor JS (o HtmlUnit dentro da JVM, o Selenium através de um controlador de navegador real). O Jaunt assemelha-se mais ao Jsoup, sendo um analisador e um cliente HTTP simples. Utilize o Jsoup quando a página for estática; utilize os outros quando precisar de renderização ou interação.

Como evito ser bloqueado ou ter a minha taxa limitada ao analisar páginas com o Jsoup?

Identifique o seu bot de forma honesta no User-Agent, limite os pedidos a alguns por segundo por host, aleatorize os atrasos e reutilize cookies quando apropriado. Leia e respeite robots.txt. Para tarefas de maior volume ou alvos hostis, encaminhe os pedidos através de um conjunto de proxies residenciais ou rotativos, porque o próprio Jsoup não tem rotação de IP, falsificação de impressão digital ou tratamento de CAPTCHA incorporados.

O Jsoup consegue analisar XML, feeds RSS ou HTML malformado?

Sim, para os três. Passe um analisador XML explicitamente com Jsoup.parse(input, baseUri, Parser.xmlParser()) para feeds RSS, mapas do site e outros documentos XML. Para HTML malformado, o analisador padrão é tolerante e normaliza a marcação da mesma forma que um navegador moderno faria, pelo que tags não fechadas e caracteres dispersos normalmente ainda produzem um Document.

Qual é a versão estável mais recente do Jsoup e como a mantenho atualizada?

Verifique diretamente no Maven Central, porque os números de versão mudam frequentemente e qualquer número citado num tutorial pode já estar desatualizado. Subscreva as notas de lançamento no repositório GitHub do Jsoup ou execute um plugin de atualização de dependências do Maven, como o versions:display-dependency-updates no CI para apresentar as atualizações disponíveis automaticamente. O Renovate e o Dependabot funcionam ambos se o seu repositório estiver alojado em plataformas compatíveis.

Conclusão

Se terminar este guia e se lembrar de uma coisa, que seja o ritmo de quatro passos: carregar HTML num Document, selecione o que lhe interessa, extraia ou modifique-o e serialize de volta. Essa sequência é a espinha dorsal de todos os scrapers baseados em Jsoup, de todas as migrações de conteúdo e de todos os sanitizadores de HTML que irá escrever. Adicione um User-Agent real, tempos de espera razoáveis, tratamento estruturado de exceções e uma política de repetição com backoff, e terá um analisador que resiste ao tráfego de produção.

A advertência honesta continua a aplicar-se: o Jsoup não executa JavaScript e não contorna as defesas anti-bot. Se a página for renderizada do lado do cliente, precisas de um navegador headless. Se o alvo bloquear o teu IP ou identificar o teu fetcher, precisas de uma camada de pedidos mais inteligente.

É nesse segundo caso que uma API de scraping gerida mostra o seu valor. A API Scraper da WebScrapingAPI devolve o HTML bruto mesmo de alvos hostis, tratando da rotação de proxies, CAPTCHAs e identificação do navegador do seu lado, para que possa manter o seu código de análise Jsoup inalterado e apenas trocar a etapa de obtenção. É a forma mais simples que encontrámos para garantir resiliência de produção num analisador Java leve.

Sobre o autor
Mihai Maxim, Desenvolvedor Full Stack @ WebScrapingAPI
Mihai MaximDesenvolvedor Full Stack

Mihai Maxim é um programador Full Stack na WebScrapingAPI, contribuindo em todas as áreas do produto e ajudando a criar ferramentas e funcionalidades fiáveis para a plataforma.

Comece a construir

Pronto para expandir a sua recolha de dados?

Junte-se a mais de 2.000 empresas que utilizam a WebScrapingAPI para extrair dados da Web à escala empresarial, sem quaisquer custos de infraestrutura.