Eu crio webcrawlers e mecanismos de busca tem 7 anos na data que escrevo este post, sendo um dos empreendimentos digitais mais bem sucedidos da minha carreira o Busca Acelerada, um buscador de classificados automotivos. Já falei de como você pode criar buscadores em outras oportunidades que listo abaixo:
- Como criar um mecanismo de busca
- Como criar um mecanismo de busca com Node.js e MongoDB
- Como criar um mecanismo de busca com PHP e MongoDB
- Como criar um mecanismo de busca em ASP.NET Core e MongoDB
No entanto, eu nunca havia escrito antes sobre como criar um webcrawler antes, ou seja, um script que automaticamente percorre páginas web em busca de informações a serem indexadas para seu buscador. Na verdade eu não vou mostrar hoje como criar um webcrawler completo (que é uma tarefa que une muitos conceitos para um post só), mas sim como fazer um algoritmo que coleta informações de páginas HTML e armazena em um banco de dados, uma técnica chamada webscrapping.
Para que você consiga acompanhar este tutorial é importante que já esteja familiarizado com Node.js e MongoDB, que trato bastante aqui no blog em posts como esse e em meu livro sobre o assunto.
Veremos neste post:
Vamos lá!
#1 – Utilidade do Webscrapping
Basicamente webscrapping consiste em ler o conteúdo HTML de páginas web, extrair as informações que você deseja se baseando em padrões de elementos (tags), armazenar no seu banco de dados e ignorar o resto, avançando para a próxima página.
Webscrapping é útil em diversos cenários e largamente utilizado por grandes empresas de tecnologia e inteligência competitiva.
- você pode fazer scrapping de redes sociais para descobrir tópicos que estão na moda, como o Sentimonitor faz
- você pode fazer scrapping de endereços de email disponíveis em websites para vender como o Hunter.io faz
- você pode fazer scrapping de informações de outros sites para usar no seu, como o Google faz
- você pode fazer scrapping de preços de produtos em ecommerces para criar comparadores, como o Buscapé faz
Note, no entanto, que muitos websites consideram webscrapping errado, violando seus termos de uso. Sendo assim, utilize as técnicas apresentadas neste artigo com cuidado pois caso o seu webcrawler fique visitando demais o mesmo site é bem possível que seu IP seja bloqueado ou até mesmo que você receba emails com ameaças.
Faça webscrapping por sua própria conta e risco.
#2 – Criando o projeto
Agora que sabe o que é e para que serve webscrapping, vamos criar este projeto.
Crie uma pasta para salvar os fontes deste projeto na sua máquina. Eu chamei a minha de webscrapper.
Entre na pasta webscrapper via terminal e rode o seguinte comando:
1 |
npm init |
Siga o passo-a-passo que irá aparecer para criar as configurações desse projeto Node.js. Depois disso, vamos instalar as dependências que precisaremos neste projeto, usando o comando abaixo:
1 |
npm i -S request request-promise cheerio |
“npm i” significa “NPM Install”, o “-S” indica que as dependências devem ser salvas no seu packages.json e os demais nomes são os pacotes que devem ser instalados:
- request: biblioteca para fazer requisições HTTP
- request-promise: oferece suporte a promises com request (uma nova forma de lidar com os callbacks, veja mais aqui)
- cheerio: oferece as funcionalidades de seletores do JQuery em Node.js
Agora crie um arquivo index.js na raiz do seu projeto e vamos seguir em frente pois o setup está pronto!
Ah, sugiro usar o Visual Studio Code para ser mais produtivo programando em Node.js. É gratuito e multiplataforma.
#3 – Programando o Webscrapper
Comece o seu index.js adicionando as referências às bibliotecas que instalamos anteriormente:
1 2 |
const rp = require('request-promise') const cheerio = require('cheerio') |
A biblioteca request-promise aceita um objeto como input de suas requests e retorna uma promise. Basicamente este objeto, que chamarei de options, precisa de duas coisas:
- a URL que vamos fazer scrapping
- a função de transformação com o HTML retornado da página
Nesta função de transformação vamos dizer ao request-promise para delegar ao cheerio o carregamento do HTML. O código abaixo demonstra um objeto options válido, apontando para a página com os resultados do Brasileirão Série A do Globo Esporte:
1 2 3 4 5 6 |
const options = { uri: 'http://globoesporte.globo.com/futebol/brasileirao-serie-a/', transform: function (body) { return cheerio.load(body) } } |
A ideia aqui é pegar as informações da tabela do Brasileirão diretamente do site do Globo Esporte, provavelmente o mais atualizado do país neste sentido. O que vou fazer com esses dados? Eu não tenho nem ideia, mas achei que seria um bom exemplo…
Agora para fazer a requisição é bem simples, basta usarmos o objeto rp como uma função passando o objeto options para ele por parâmetro. Como é uma função que retorna uma promise JS, usaremos then e catch para lidar com o callback de retorno e com um possível erro, respectivamente:
1 2 3 4 5 6 7 |
rp(options) .then(($) => { console.log($); }) .catch((err) => { console.log(err); }) |
Se você seguiu os passos corretos até agora, abra o terminal e na pasta do projeto webscrapper digite o seguinte comando para executar este arquivo:
1 |
node index |
Como resultado, você deve ver no terminal uma mensagem semelhante à essa, indicando que o HTML de retorno do site foi carregado com sucesso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
{ [Function: initialize] fn: initialize { constructor: [Circular], _originalRoot: { type: 'root', name: 'root', namespace: 'http://www.w3.org/1999/xhtml', attribs: {}, 'x-attribsNamespace': {}, 'x-attribsPrefix': {}, children: [Array], parent: null, prev: null, next: null } }, load: [Function], html: [Function], xml: [Function], text: [Function], parseHTML: [Function], root: [Function], contains: [Function], merge: [Function], _root: { type: 'root', name: 'root', namespace: 'http://www.w3.org/1999/xhtml', attribs: {}, 'x-attribsNamespace': {}, 'x-attribsPrefix': {}, children: [ [Object], [Object], [Object] ], parent: null, prev: null, next: null }, _options: { withDomLvl1: true, normalizeWhitespace: false, xml: false, decodeEntities: true } } |
Isso quer dizer que seu webscrapper está funcionando!
#4 – Trabalhando com os dados
Agora que você tem um webscrapper simples porém funcional, é hora de trabalhar com aquele objeto que representa o documento HTML retornado pelo request-promise e transformado pelo cheerio.
Se você nunca usou JQuery antes (no meu livro de Node.js é um dos tópicos de front-end), o mais importante aqui é bem simples de aprender: seletores. Basicamente todo seletor começa com o cifrão ($) seguido de parênteses e o identificador do seletor que pode ser:
- #idDoElemento
- .classeDoElemento
- tagDoElemento
- tag[atributo=valor]
E muito mais, mas esses aí são os principais. Ou seja, se quiser carregar em memória um elemento HTML cujo id seja divCadastro, em JQuery você faria o seguinte:
1 |
$('#divCadastro') |
E depois na sequência poderia chamar funções para pegar informações deste elemento, incluindo seu texto, seu HTML, seus atributos, seus nós-filhos, etc.
E cheerio trabalha exatamente desta forma!
Para entender a informação que vamos pegar, é sempre útil ver o código-fonte da página que estamos fazendo scrapping ou melhor ainda: usar o F12 do Google Chrome para inspecionar elementos específicos, como fiz na imagem abaixo para entender a estrutura HTML da tabela do brasileirão.
Note que todas as linhas com nomes de clubes (tr) possuem a classe ‘tabela-body-linha’, sendo assim, fica fácil de fazer um seletor por essa classe e com um laço descobrir todas as linhas. Dentro de cada linha, usamos a função find para achar os elementos que possuem a classe com o nome de cada um dos times (tabela-times-time-nome):
1 2 3 4 5 6 7 8 9 |
rp(options) .then(($) => { $('.tabela-body-linha').each((i, item) => { console.log($(item).find('.tabela-times-time-nome').text()) }) }) .catch((err) => { console.log(err); }) |
Se você executar agora, verá no console o nome de todos os times da série A do Brasileirão (desculpe Internacional/RS, mas você não está na lista XD ).
Para encerrar, vou modificar o código uma última vez e colocá-lo todo abaixo, para pegar além do nome do time, a posição atual dele na tabela, guardando estas informações em um array JSON que facilmente você poderia depois salvar em um banco de dados como o MongoDB, como já mostrei em outros posts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
//index.js const rp = require('request-promise') const cheerio = require('cheerio') const options = { uri: 'http://globoesporte.globo.com/futebol/brasileirao-serie-a/', transform: function (body) { return cheerio.load(body) } } function processarDados(dados){ //salva no banco de dados console.log(JSON.stringify(dados)) } rp(options) .then(($) => { const times = [] $('.tabela-body-linha').each((i, item) => { const time = { nome: $(item).find('.tabela-times-time-nome').text(), posicao: parseInt($(item).find('.tabela-times-posicao').text()) } if(time.nome !== "") times.push(time) }) processarDados(times) }) .catch((err) => { console.log(err); }) |
Adicionei alguns códigos adicionais para incluir uma função de processamento, proporcionando um ponto onde facilmente você pode colocar sua lógica de persistência de dados. Também coloquei um teste para evitar salvar dados em branco (algo que com um pouco mais de paciência pode ser melhor filtrado com o cheerio).
O resultado esperado deste simples webscrapper, na data que escrevo este post é o array JSON abaixo impresso no console:
1 |
[{"nome":"Corinthians","posicao":1},{"nome":"Santos","posicao":2},{"nome":"Grêmio","posicao":3},{"nome":"Palmeiras","posicao":4},{"nome":"Cruzeiro","posicao":5},{"nome":"Botafogo","posicao":6},{"nome":"Flamengo","posicao":7},{"nome":"Atlético-PR","posicao":8},{"nome":"Vasco","posicao":9},{"nome":"Chapecoense","posicao":10},{"nome":"Atlético-MG","posicao":11},{"nome":"Fluminense","posicao":12},{"nome":"Bahia","posicao":13},{"nome":"Sport","posicao":14},{"nome":"Avaí","posicao":15},{"nome":"Vitória","posicao":16},{"nome":"São Paulo","posicao":17},{"nome":"Ponte Preta","posicao":18},{"nome":"Coritiba","posicao":19},{"nome":"Atlético-GO","posicao":20}] |
Atente ao fato de que muitos sites alteram o DOM da página usando JavaScript e que algumas vezes você não conseguirá ter acesso aos elementos HTML via cheerio. Nestes casos a técnica correta envolve usar headless browsers como o Phantom.
Mas essa é uma técnica muito mais elaborada que explico neste outro post.
Também aproveito para te convidar a acessar o vídeo abaixo, também sobre bots:
Espero que tenha gostado!
Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.
Procurei por isso desde maio deste ano. E finalmente consegui fazer o que eu queria.
Você salvou a minha vida! Muito obrigado
Fico feliz de ter salvado sua vida, hehehe
Prezado Luiz:
Bom seu artigo, parabéns! Luiz eu preciso saber se você pode me prestar um serviço à distância (um curso de webscraping em R ou em python para dados estatísticos de bancos de dados públicos). E qual será o preço? Caso possa fazer isto, poderá me enviar uma mensagem privada no messenger, no meu perfil do Facebook (https://www.facebook.com/josemaria.reganhan). Grato.
Infelizmente não possuo disponibilidade para consultoria ou aulas particulares. Deixo aqui seu contato caso alguém leia no futuro e possa lhe ajudar.