[Parte 01] Aprenda a construir um web crawling com puppeteer

Karoline Alencar
IM+: finanças e investimentos
5 min readAug 17, 2021

Todos podemos concordar que a internet fornece um mar de informação, e com isso, podemos utilizar esses dados para criar aplicações de todo tipo: desde projetos pessoais até aplicações que podem ajudar milhares de pessoas. É disso que web crawling se trata, nada mais que um meio de navegar e extrair informações de um site.

Hoje vamos aprender como construir um web crawling com uma ferramenta chamada puppeteer, que fornece uma API para controlar o Chrome/Chromium. A ideia da nossa aplicação é: Entrar na página coinbase.com/price e extrair as 30 primeiras cryptos em alta.

Vou até dar um spoiler da nossa parte 2: com esses dados em mãos, vamos enviar essas informações para um grupo do telegram.

Agora que já entendemos o que é um web crawling e o que é o puppeteer, vamos colocar a mão na massa. Primeiro, vamos começar com a estrutura de pastas do nosso projeto:

src/
services/
crawler.js
index.js

O arquivo crawler vai conter toda a lógica da nossa aplicação e o index.js vai importar as funções e construir o fluxo.

Para começar precisamos iniciar o npm em nosso projeto, assim conseguimos instalar o puppeteer. Fazemos isso com os seguintes comandos na raiz do projeto:

npm init 
npm install puppeteer-extra puppeteer-extra-plugin-stealth

💡 Aqui estamos instalando os pacotes adicionais para o puppeteer mas nem sempre isso é necessário, no nosso caso o site que vamos utilizar consegue detectar o puppeteer padrão, com o plugin stealth conseguimos previnir alguns tipos de detecção.

Depois disso, será gerado dois arquivos em nosso projeto: o package.json, que vai conter todas as informações do pacote e suas dependências e o package-lock.json, que contém a “árvore” de dependências, assim garantimos que todos tenham a mesma versão dos pacotes instalados.

Agora que criamos a estrutura do projeto, podemos começar pelo index.js. Vamos criar uma função assíncrona chamada start e executá-la no final do arquivo.

// index.js

const start = async () => {
console.log('[ 🤖 ] service started')

try {

} catch (error) {
console.log(`[ ❌ ] error: ${error}`)
}

}

start()

Vamos envolver nosso fluxo dentro do try/catch, assim conseguimos manipular erros de uma forma mais fácil e limpa. Também é importante colocar logs na nossa aplicação, assim conseguimos acompanhar o fluxo e identificar facilmente em qual parte ocorreu um erro.

Com a estrutura do index criada podemos começar a desenvolver o fluxo do nosso crawler, então podemos ir para o arquivo crawler.js, importar o puppeteer e criar uma função para criar o navegador e abrir uma página.

// crawler.js

const puppeteer = require('puppeteer-extra')
const StealthPlugin = require('puppeteer-extra-plugin-stealth')
puppeteer.use(StealthPlugin())

const launchBrowser = async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
return { browser, page }
}

module.exports = {
launchBrowser,
}

Nossa função retorna o browser e page devido a forma que estamos estruturando o código: as próximas funções precisam dessa informação para funcionarem corretamente, para navegar no site precisamos do page e quando a aplicação finalizar precisamos fechar o browser.

Já que temos um browser criado e uma page para navegar, podemos começar a parte divertida: Entrar no site e extrair as informações, primeiro vamos criar uma função chamada getCryptoList e utilizar o método goto do puppeteer para navegar até a página desejada, como utilizamos o await, a promise será resolvida quando o evento load for emitido.

// crawler.js

const getCryptoList = async (page) => {
console.log('[ 🤖 ] navigating to coinbase.com')
await page.goto('https://www.coinbase.com/price')
}

Nem sempre esperar o evento load é suficiente, em algumas situações esse evento é emitido antes de todo o conteúdo que precisamos estar na página, no geral, ter 100% certeza que finalizou de carregar é bem difícil, por isso colocamos uma validação “extra”: esperamos que algum seletor importante para nossa extração esteja visível na página.

No nosso caso vamos esperar pelo seletor do ícone da crypto.

// crawler.js

const getCryptoList = async (page) => {
...
console.log('[ 🤖 ] waiting for page content')
await page.waitForSelector('#main table tbody tr img')
}

💡 Sempre que for extrair dados de um site é importante analisar a estrutura dele: Qual o seletor do elemento que deseja capturar? Nesse tutorial já vamos ter os seletores prontos, mas caso for começar um do 0, é necessário fazer essa análise.

Depois de esperar a página carregar podemos extrair os dados da lista de crypto. Vou utilizar o método evaluate, que nos permite utilizar métodos da API do browser, como o querySelector e textContent, como nosso objetivo é extrair o conteúdo dos seletores, essa é a forma mais fácil de fazer isso com o puppeteer, já que vamos utilizar métodos comuns.

// crawler.js

const getCryptoList = async (page) => {
...
const cryptos = await page.evaluate(() => {
const list = Array.from(document.querySelectorAll('#main table tbody tr'))
return list.map((item) => {
const icon = item.querySelector('td:nth-child(1) img').src
const name = item.querySelector('td:nth-child(1) span:nth-child(1)').textContent
const symbol = item.querySelector('td:nth-child(1) span:nth-child(2)').textContent
const price = item.querySelector('td:nth-child(2) span:nth-child(1)').textContent
const change = item.querySelector('td:nth-child(3) span, td:nth-child(3)').textContent
const volume = item.querySelector('td:nth-child(5) span span').textContent
const marketCap = item.querySelector('td:nth-child(6) span span').textContent

return {
name,
symbol,
icon,
price,
volume,
change,
marketCap,
}
})
})
}

Basicamente, aqui estamos capturando todos os elementos da lista de crypto, transformando em array para conseguir utilizar o método .map, e dentro do loop, capturando os campos que desejamos, extraindo o texto e retornando o array com os dados, com isso nossa variável cryptos irá conter todos os dados que desejamos.

Por fim, podemos retornar a variável crypto e exportá-la.

// crawler.js

const getCryptoList = async (page) => {
...
console.log('[ 🤖 ] finished crypto info scraping')
return cryptos
}

module.exports = {
launchBrowser,
getCryptoList,
}

Agora só falta um passo para finalizar nosso arquivo crawler.js: Sempre que terminamos de utilizar o puppeteer devemos fechar o browser, isso evita gastar recursos desnecessários na nossa máquina, imagina que depois de extrair os dados, a sua aplicação ainda continua fazendo outros trabalhos que não envolvem o puppeteer, o browser e a página vão ficar abertos sem ser utilizados.

Então vamos criar uma função chamada closeBrowser, que será responsável por fechar o navegador.

const closeBrowser = async (browser) => browser.close()

module.exports = {
launchBrowser,
getCryptoList,
closeBrowser,
}

Agora que temos nosso fluxo de navegação finalizado, vamos ligar as peças no index.js.

Vamos voltar para o arquivo, importar as funções que criamos e construir o fluxo, nosso arquivo final é o seguinte:

const { closeBrowser, getCryptoList, launchBrowser } = require('./services/crawler')

const start = async () => {
console.log('[ 🤖 ] service started')

try {
console.log('[ 🤖 ] starting browser')
const { browser, page } = await launchBrowser()

console.log('[ 🤖 ] starting crypto scraping')
const cryptos = await getCryptoList(page)

console.log('[ 🤖 ] closing browser')
await closeBrowser(browser)

console.log('[ 🤖 ] we are done')
} catch (error) {
console.log(`[ ❌ ] error: ${error}`)
}
}

start()

Agora para executar nossa aplicação, entre na raiz do projeto e execute node src/index.js, assim irá ver todo o fluxo sendo executado! ❤

Espero que tenham gostado do post! Na parte 2 vamos enviar esses dados em um grupo do telegram utilizando a API oficial, espero vocês lá! Ah, e se tiver alguma dúvida ou sugestão, só comentar aqui, ta bom?

Gostaria de trabalhar com isso no seu dia a dia? Aqui na Fliper estamos com vaga para o time de crawler, SÓ VEM!

--

--