Voltar ao blogue
Guias
Mihai MaximLast updated on Mar 31, 20266 min read

JSoup: Análise de HTML em Java

JSoup: Análise de HTML em Java

Apresentamos o JSoup

O web scraping pode ser visto como uma caça ao tesouro digital. Percorre-se um site e extrai-se toda a informação necessária. É uma técnica utilizada para todo o tipo de fins, como encontrar os preços mais baixos, analisar a opinião dos clientes ou recolher dados para investigação.

O Java é considerado uma excelente linguagem de programação para web scraping, pois possui uma grande variedade de bibliotecas e frameworks que podem auxiliar nesse processo. Uma das bibliotecas mais conhecidas para web scraping em Java é o JSoup. O JSoup permite-lhe navegar e pesquisar no HTML de um site e extrair todos os dados de que necessita.

Ao combinar Java com a JSoup, pode criar aplicações fantásticas de web scraping capazes de extrair dados de sites de forma rápida e fácil. Neste artigo, vou guiá-lo pelos conceitos básicos do web scraping com a JSoup.

Configurar um projeto JSoup

Nesta secção, iremos criar um novo projeto Java com o Maven e configurá-lo para ser executado a partir da linha de comandos utilizando o exec-maven-plugin. Isto permitirá que empacote e execute facilmente o seu projeto num servidor, possibilitando a automatização e a escalabilidade do processo de extração de dados. Depois disso, iremos instalar a biblioteca JSoup.

Criação de um projeto Maven

O Maven é uma ferramenta de automatização de compilação para projetos Java. Ele gere dependências, compilações e documentação, facilitando a gestão de projetos Java complexos. Com o Maven, pode facilmente gerir e organizar o processo de compilação, as dependências e a documentação do seu projeto. Também permite uma integração fácil com ferramentas e frameworks.

A instalação do Maven é um processo simples que pode ser realizado em poucos passos.

Primeiro, descarregue a versão mais recente do Maven a partir do site oficial (https://maven.apache.org/download.cgi).

Assim que o download estiver concluído, extraia o conteúdo do arquivo para um diretório à sua escolha.

Em seguida, terá de configurar as variáveis de ambiente.

No Windows, defina a variável JAVA_HOME para a localização do seu JDK e adicione a pasta bin da instalação do Maven à variável PATH.

No Linux/macOS, terá de adicionar as seguintes linhas ao seu ficheiro ~/.bashrc ou ~/.bash_profile:

export JAVA_HOME=path/to/the/jdk

export PATH=$PATH:path/to/maven/bin

Confirme a instalação do Maven executando mvn --version num terminal.

Com o Maven instalado, pode agora criar um novo projeto Java Maven:

mvn archetype:generate -DgroupId=com.project.scraper

-DartifactId=jsoup-scraper-project

-DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Isto cria uma nova pasta chamada «jsoup-scraper-project» contendo o conteúdo do projeto.


O ponto de entrada da aplicação (a classe principal) estará no pacote “com.project.scraper”.

Executar o projeto a partir da linha de comandos

Para executar um projeto Java do Maven a partir da linha de comandos, utilizaremos o exec-maven-plugin.

Para instalar o plugin, é necessário adicioná-lo ao ficheiro pom.xml do projeto. Isto pode ser feito adicionando o seguinte trecho de código à secção <build><plugins> do ficheiro pom.xml:

<build>

 <plugins>

   <plugin>

     <groupId>org.codehaus.mojo</groupId>

     <artifactId>exec-maven-plugin</artifactId>

     <version>3.1.0</version>

     <executions>

       <execution>

         <goals>

           <goal>java</goal>

         </goals>

       </execution>

     </executions>

     <configuration>

       <mainClass>com.project.scraper.App</mainClass>

     </configuration>

   </plugin>

 </plugins>

</build>

Certifique-se de que seleciona o caminho correto para a classe principal do projeto.

Utilize mvn package exec:java no terminal (no diretório do projeto) para executar o projeto.

Instalação da biblioteca JSoup

Para instalar a biblioteca JSoup, adicione a seguinte dependência ao ficheiro pom.xml do seu projeto:

<dependency>

 <groupId>org.jsoup</groupId>

 <artifactId>jsoup</artifactId>

 <version>1.14.3</version>

</dependency>

Visite https://mvnrepository.com/artifact/org.jsoup/jsoup para verificar a versão mais recente.

Analisar HTML em Java com o JSoup

Nesta secção, vamos explorar o site https://www.scrapethissite.com/pages/forms/ e ver como podemos extrair as informações sobre equipas de hóquei. Ao examinar um site real, irá compreender os conceitos e técnicas utilizados na extração de dados da Web com o JSoup e como poderá aplicá-los aos seus próprios projetos.

Obter o HTML

Para obter o HTML do site, é necessário fazer uma solicitação HTTP ao mesmo. No JSoup, o método connect() é utilizado para criar uma ligação a um URL especificado. Este método devolve um objeto Connection, que pode ser utilizado para configurar a solicitação e recuperar a resposta do servidor.

Vamos ver como podemos usar o método connect() para obter o HTML da nossa URL e, em seguida, gravá-lo num ficheiro HTML local (hockey.html):

package com.project.scraper;

import org.jsoup.Jsoup;

import org.jsoup.nodes.Document;

import java.io.*;

import java.io.IOException;

public class App

{

   public static void main( String[] args )

   {

       String RAW_HTML;

       try {

           Document document = Jsoup.connect("https://www.scrapethissite.com/pages/forms/")

                   .get();

           RAW_HTML = document.html();

           FileWriter writer = new FileWriter("hockey.html");

           writer.write(RAW_HTML);

           writer.close();

       } catch (IOException e) {

           e.printStackTrace();

       }

   }

}

Agora podemos abrir o ficheiro e examinar a estrutura do HTML com as Ferramentas de Programador:

Os dados de que precisamos estão presentes numa tabela HTML na página. Agora que acedemos à página, podemos prosseguir para extrair o conteúdo da tabela utilizando seletores.

Escrever seletores

Os seletores no JSoup têm semelhanças com os seletores em JavaScript. Ambos têm uma sintaxe semelhante e permitem-lhe selecionar elementos de um documento HTML com base no nome da tag, classe, id e propriedades CSS.

Aqui estão alguns dos principais seletores que pode utilizar com o JSoup:

  • getElementsByTag(): Seleciona elementos com base no nome da sua tag.
  • getElementsByClass(): Seleciona elementos com base no nome da sua classe.
  • getElementById(): Seleciona um elemento com base no seu id.
  • select(): Seleciona elementos com base num seletor CSS (semelhante ao querySelectorAll)

Agora vamos usar alguns deles para extrair todos os nomes das equipas:

try {

   Document document = Jsoup.connect("https://www.scrapethissite.com/pages/forms/")

           .get();

   Elements rows = document.getElementsByTag("tr");

   for(Element row : rows) {

      

       Elements teamName = row.getElementsByClass("name");

      

       if(teamName.text().compareTo("") != 0)

           System.out.println(teamName.text());

      

   }

} catch (IOException e) {

   e.printStackTrace();

}

// Prints the team names:

Boston Bruins

Buffalo Sabres

Calgary Flames

Chicago Blackhawks

Detroit Red Wings

Edmonton Oilers

Hartford Whalers

...

Percorremos todas as linhas e, para cada uma, exibimos o nome da equipa utilizando o seletor de classe «name».

O último exemplo destaca a flexibilidade e a capacidade de aplicar métodos de seleção várias vezes nos elementos que foram extraídos. Isto é particularmente útil ao lidar com documentos HTML complexos e de grande dimensão.

Eis outra versão que utiliza fluxos Java e o método select() para imprimir todos os nomes das equipas:

try {

   Document document = Jsoup.connect("https://www.scrapethissite.com/pages/forms/")

           .get();

   Elements teamNamesElements = document.select("table .team .name");

   String[] teamNames = teamNamesElements.stream()

                                         .map(element -> element.text())

                                         .toArray(String[]::new);

   for (String teamName : teamNames) {

       System.out.println(teamName);

   }

} catch (IOException e) {

   e.printStackTrace();

}

// Also prints the team names:

Boston Bruins

Buffalo Sabres

Calgary Flames

...

Agora vamos imprimir todos os cabeçalhos e linhas da tabela:

try {

   Document document = Jsoup.connect("https://www.scrapethissite.com/pages/forms/")

           .get();

   Elements tableHeadersElements = document.select("table th");

   Elements tableRowsElements = document.select("table .team");

   String[] tableHeaders =

   tableHeadersElements.stream()

                       .map(element -> element.text())

                       .toArray(String[]::new);

   String[][] tableRows =

   tableRowsElements.stream()

            .map(

                table_row -> table_row

                .select("td")

                .stream()

                .map(row_element -> row_element.text())

                .toArray(String[]::new)

               )

            .toArray(String[][]::new);

   for (int i = 0; i < tableHeaders.length; i++) {

       System.out.print(tableHeaders[i] + " ");

   }

   for (int i = 0; i < tableRows.length; i++) {

       for (int j = 0; j < tableRows[i].length; j++) {

           System.out.print(tableRows[i][j] + " ");

       }

       System.out.println();

   }

} catch (IOException e) {

   e.printStackTrace();

}

// Prints

Team Name Year Wins Losses OT Losses Win ...

Boston Bruins 1990 44 24  0.55 299 264 35 

Buffalo Sabres 1990 31 30  0.388 292 278 14 

Calgary Flames 1990 46 26  0.575 344 263 81 

Chicago Blackhawks 1990 49 23  0.613 284 211 73 

Detroit Red Wings 1990 34 38  0.425 273 298 -25

...

Repare que utilizámos streams para armazenar as linhas. Aqui está uma forma mais simples de o fazer, utilizando loops for:

String[][] tableRows = new String[tableRowsElements.size()][];

for (int i = 0; i < tableRowsElements.size(); i++) {

   Element table_row = tableRowsElements.get(i);

   Elements tableDataElements = table_row.select("td");

   String[] rowData = new String[tableDataElements.size()];

   for (int j = 0; j < tableDataElements.size(); j++) {

       Element row_element = tableDataElements.get(j);

       String text = row_element.text();

       rowData[j] = text;

   }

   tableRows[i] = rowData;

}

Lidar com a paginação

Ao extrair dados de um site, é comum que a informação esteja dividida por várias páginas. Para extrair todos os dados relevantes, é necessário fazer pedidos a cada página do site e extrair a informação de cada uma delas. Podemos implementar facilmente esta funcionalidade no nosso projeto.

Tudo o que temos de fazer é alterar o parâmetro de consulta page_num no URL e fazer outra solicitação HTTP com o método connect().

int pageLimit = 25;

String [] tableHeaders = new String[0];

Vector<String[][]> rowsGroups = new Vector<String [][]>();

for (int currentPage=1; currentPage<pageLimit; currentPage++) {

   try {

       Document document = Jsoup.connect("https://www.scrapethissite.com/pages/forms/?page_num=" + currentPage)

               .get();

       if(currentPage == 1) {

           Elements tableHeadersElements = document.select("table th");

           tableHeaders = tableHeadersElements.stream()

                   .map(element -> element.text())

                   .toArray(String[]::new);

       }

       Elements tableRowsElements = document.select("table .team");

       String[][] tableRows = new String[tableRowsElements.size()][];

       for (int i = 0; i < tableRowsElements.size(); i++) {

           Element table_row = tableRowsElements.get(i);

           Elements tableDataElements = table_row.select("td");

           String[] rowData = new String[tableDataElements.size()];

           for (int j = 0; j < tableDataElements.size(); j++) {

               Element row_element = tableDataElements.get(j);

               String text = row_element.text();

               rowData[j] = text;

           }

           tableRows[i] = rowData;

       }

       rowsGroups.add(tableRows);

   } catch (IOException e) {

       e.printStackTrace();

   }

   // do something with the headers and the the table rows groups

}

Uma vez que as tabelas de cada página têm os mesmos cabeçalhos, deve certificar-se de que não as extrai várias vezes.

O código completo

Aqui está o código completo que extrai todas as tabelas do site https://www.scrapethissite.com/pages/forms/. Também incluí uma função que guarda os dados num ficheiro .CSV:

package com.project.scraper;

import org.jsoup.Jsoup;

import org.jsoup.nodes.Document;

import org.jsoup.nodes.Element;

import org.jsoup.select.Elements;

import java.io.*;

import java.io.IOException;

import java.util.Vector;

public class App

{

   public static void main( String[] args )

   {

       int pageLimit = 25;

       String [] tableHeaders = new String[0];

       Vector<String[][]> rowsGroups = new Vector<String [][]>();

       for (int currentPage=1; currentPage<pageLimit; currentPage++) {

           try {

               Document document = Jsoup.connect("https://www.scrapethissite.com/pages/forms/?page_num=" + currentPage)

                       .get();

               if(currentPage == 1) {

                   Elements tableHeadersElements = document.select("table th");

                   tableHeaders = tableHeadersElements.stream()

                           .map(element -> element.text())

                           .toArray(String[]::new);

               }

               Elements tableRowsElements = document.select("table .team");

               String[][] tableRows = new String[tableRowsElements.size()][];

               for (int i = 0; i < tableRowsElements.size(); i++) {

                   Element table_row = tableRowsElements.get(i);

                   Elements tableDataElements = table_row.select("td");

                   String[] rowData = new String[tableDataElements.size()];

                   for (int j = 0; j < tableDataElements.size(); j++) {

                       Element row_element = tableDataElements.get(j);

                       String text = row_element.text();

                       rowData[j] = text;

                   }

                   tableRows[i] = rowData;

               }

               rowsGroups.add(tableRows);

           } catch (IOException e) {

               e.printStackTrace();

           }

       }

       writeFullTableToCSV(rowsGroups, tableHeaders, "full_table.csv");

   }

   public static void writeFullTableToCSV(Vector<String[][]> rowsGroups, String[] headers, String fileName) {

       File file = new File(fileName);

       try {

           FileWriter writer = new FileWriter(file);

           // write the headers first

           for (int i = 0; i < headers.length; i++) {

               writer.append(headers[i]);

               if (i != headers.length - 1) {

                   writer.append(",");

               }

           }

           writer.append("\n");

           // write all the rows groups

           for (String [][] rowsGroup : rowsGroups) {

               for (String[] row : rowsGroup) {

                   for (int i = 0; i < row.length; i++) {

                       writer.append(row[i]);

                       if (i != row.length - 1) {

                           writer.append(",");

                       }

                   }

                   writer.append("\n");

               }

           }

           writer.flush();

           writer.close();

       } catch (IOException e) {

           e.printStackTrace();

       }

   }

}

Conclusão

Neste artigo, abordámos como instalar o Maven e criar um novo projeto Java Maven, bem como executar o projeto a partir da linha de comandos. Também discutimos como instalar a biblioteca JSoup, adicionando a dependência ao ficheiro pom.xml do projeto. Por fim, analisámos um exemplo de como utilizar o JSoup para analisar HTML e extrair dados de um site. Seguindo os passos descritos no artigo, deverá ter uma base sólida para configurar um projeto JSoup e começar a extrair dados de sites. O JSoup oferece uma vasta gama de opções e possibilidades para a extração de dados da web e encorajo-o a explorá-las e a aplicá-las aos seus próprios projetos.

Como viu, os dados são frequentemente partilhados entre várias páginas web. Fazer pedidos rápidos ao mesmo domínio pode levar ao bloqueio do seu IP. Com o nosso produto, WebScrapingAPI, nunca terá de se preocupar com esses problemas. A nossa API garante que pode fazer tantos pedidos quantos precisar. E a melhor parte é que pode experimentá-la gratuitamente.

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.