Web Scraping com Jsoup

Escrito em 11/09/2020 13:42 por Benigno

Iremos conhecer um pouco sobre a biblioteca java JSOUP, utilizada para realizar web scraping de forma fácil e ágil. Mas o que é web scraping? Pode-se dizer que é a utilização de bots para a extração de dados relevantes da sites, ou seja, a automatização da captura de informações que estão disponíveis em HTML ou XML.

Neste tutorial irei utilizar como exemplo o site dos correios, para mostrar que podemos realizar e integrar conteúdos de outros sites dentro de nossos sistemas. Como exemplo, irei realizar a captura de um endereço utilizando como chave o CEP.

Utilizaremos as seguintes tecnologias:

Utilizaremos como base o sistema de busca de CEP localizado no site, veja foto abaixo:

Veja que o sistema aceita como entrada tanto o CEP como o texto do endereço, mas iremos utilizar apenas o CEP como chave de busca.

Para identificar o que iremos precisar fazer, vamos simular uma busca e analisar o que ocorre “por debaixo dos panos” no navegador. Para isso, clique como botão direito na página e selecione a opção inspecionar.

A janela DevTool irá se abrir, conforme imagem:

Clique em Network(Rede)

Confira se a janela vai estar limpa, caso não esteja clique no símbolo de de cancelar para limpar todas as conexões. Isso se faz necessário por precisamos nos concentrar especificamente na requisição que faz a busca do endereço acontecer.

Preencha o CEP e clique em Buscar e vá observar a aba Network(Rede)

Você conseguirá identificar a requisição que solicitou os dados da busca, qual o método usado(POST) e descendo a barra de rolagem, conforme imagem abaixo, você consegue ver os parâmetros passados:

A página de resposta vai ser exibida conforme a imagem abaixo:

Agora vamos iniciar a busca por padrões no HTML para que possamos iniciar o desenvolvimento do robô de consulta. Mais uma vez botão direito do mouse na página e clicar em inspecionar.

Vamos nos concentrar no html da tabela do resultado da consulta. Nela podemos identificar uma classe “tmptabela” para saber que é a tabela de resultado e identificar que o primeiro TR(linha) é apenas o cabeçalho e poderá ser descartado. Já na segunda linha, temos o que nos interessa 3 TD(colunas) a primeira indicando o logradouro, a segunda o bairro, a terceira a cidade e por último o CEP. Com esses dados agora vamos iniciar o desenvolvimento. Hands on!

Vamos criar um projeto Java Maven

Vou chamá-lo de jsoupwebscraping

Vou criar uma classe modelo chamada Endereco.java. Nela coloquei o CEP como campo obrigatório, até mesmo porque iremos utilizar sempre o CEP para realizar as buscas e não há endereço sem CEP.

public class Endereco {

    private String CEP;
    private String logradouro;
    private String bairro;
    private String cidade;

    public Endereco(String CEP) {
        this.CEP = CEP;
    }

    public String getCEP() {
        return CEP;
    }

    public void setCEP(String CEP) {
        this.CEP = CEP;
    }

    public String getLogradouro() {
        return logradouro;
    }

    public void setLogradouro(String logradouro) {
        this.logradouro = logradouro;
    }

    public String getBairro() {
        return bairro;
    }

    public void setBairro(String bairro) {
        this.bairro = bairro;
    }

    public String getCidade() {
        return cidade;
    }

    public void setCidade(String cidade) {
        this.cidade = cidade;
    }

}

Esta classe irá representar os endereços que iremos capturar. Agora iremos criar a classe que será utilizada para realizar o scraping, chamaremos ela de BuscaEndereco.java.

public class BuscaEndereco {
    
    private final String CEP;
    private final String URL_RESULTADO = "http://www.buscacep.correios.com.br/sistemas/buscacep/resultadoBuscaCepEndereco.cfm";
    
    public BuscaEndereco(String CEP) {
        this.CEP = CEP;
    }
    
    public Endereco buscarEnderecoCorreios() throws IOException, EnderecoNaoLocalizadoException {
        Endereco endereco = new Endereco(CEP);
        
        Connection connection = Jsoup.connect(URL_RESULTADO).data(preencherDadosBusca());
        Document documentResultado = connection.post();
        
        Elements tabelas = documentResultado.select(".tmptabela");
        Element tabelaResultado = tabelas.first();
        
        if (tabelaResultado == null) {
            throw new EnderecoNaoLocalizadoException();
        }        
        
        preencherEndereco(tabelaResultado, endereco);
        
        return endereco;
    }
    
    private void preencherEndereco(Element tabela, Endereco endereco) {
        Elements linhas = tabela.select("tr");        
        Element linhaResultado = linhas.last();
        Elements colunasResultado = linhaResultado.select("td");
        
        endereco.setLogradouro(colunasResultado.get(0).text());
        endereco.setBairro(colunasResultado.get(1).text());
        endereco.setCidade(colunasResultado.get(2).text());
    }
    
    private Map<String, String> preencherDadosBusca() {
        Map<String, String> dados = new HashMap();
        dados.put("relaxation", CEP);
        dados.put("tipoCEP", "ALL");
        dados.put("semelhante", "N");
        return dados;
    }    
}

Nesta classe temos como atributos os CEP e URL_RESULTADO. O CEP é o número que iremos realizar a busca, ja URL_RESULTADO é a url que identificamos como sendo através da qual o sistema dos correios busca os dados do endereço. No construtor também colocamos o campo CEP como obrigatório, afinal iremos utiliza-lo para buscar o endereço.

No método buscarEnderecoCorreios iremos simular a conexão ao site e realizar o scraping dos dados para extrair o endereço. Criamos uma conexão Jsoup para a url de busca em seguida executamos a conexão com o método POST, que também identificamos que é o método http utilizado para buscar os dados. O método preencherDadosBusca adiciona os campos necessários para a busca, que também identificamos anteriormente ao analisarmos as requisições no navegador. O método preencherEndereco vai fazer uma busca utilizando objetos Elements ou Element, através de seletores CSS para localizar qualquer elemento que utilize a classe CSS tmptabela. Caso não encontre a tabela com os dados do resultado, o sistema irá emitir uma exceção, caso consiga iremos buscar as linhas(TR) e colunas(TD). Como a primeira linha é o cabeçalho iremos utilizar o método last para pegar a última linha e de acordo com a sequência da colunas pegaremos  um objeto Endereco e adicionamos os dados encontrados na pesquisa. Pronto! Já estamos aptos a realizar a captura.

Para testar e verificar se o bot esta funcionando como esperado criamos uma classe no pacote de testes chamada BuscaEnderecoTest.java, com isso conseguiremos verificar se ao informarmos um CEP será retornado o endereço correto e se informarmos um CEP errado será retornada a exception esperada. Confira abaixo:

public class BuscaEnderecoTest {

    private BuscaEndereco buscaEndereco;
    private Endereco endereco;
    private final String CEP = "64052-535";
    private final String CEP_ERRADO = "00000-000";
    private final String LOGRADOURO = "Avenida Dom Severino - lado ímpar";
    private final String CIDADE = "Teresina/PI";
    private final String BAIRRO = "Horto";
    private static final Logger LOG = Logger.getLogger(BuscaEnderecoTest.class.getName());

    @Test
    public void deveConterOLogradouroEspecifico() {
        try {
            buscaEndereco = new BuscaEndereco(CEP);
            endereco = buscaEndereco.buscarEnderecoCorreios();
            System.out.println("Rua: " + endereco.getLogradouro());
            System.out.println("Bairro: " + endereco.getBairro());
            System.out.println("Cidade: " + endereco.getCidade());
            Assert.assertEquals(LOGRADOURO, endereco.getLogradouro());
            Assert.assertEquals(BAIRRO, endereco.getBairro());
            Assert.assertEquals(CIDADE, endereco.getCidade());
        } catch (IOException | EnderecoNaoLocalizadoException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }

    }

    @Test(expected = EnderecoNaoLocalizadoException.class)
    public void deveRetornarExcecaoEnderecoNaoLocalizado() throws EnderecoNaoLocalizadoException {
        try {
            buscaEndereco = new BuscaEndereco(CEP_ERRADO);
            endereco = buscaEndereco.buscarEnderecoCorreios();
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
    }

}

No vídeo abaixo eu demonstro a execução do teste com alguns  CEP:

Espero que tenham gostado, deixem seus comentários a respeito e sigam nosso site, iremos postar mais conteúdos semelhantes.

Abraço e Hands on!