Voltar ao blogue
Guias
Mihnea-Octavian ManolacheLast updated on Mar 31, 20267 min read

Como criar um scraper e descarregar um ficheiro com o Puppeteer

Como criar um scraper e descarregar um ficheiro com o Puppeteer

Se te dedicas ao web scraping e utilizas o Node.js, então provavelmente já ouviste falar do Puppeteer. E certamente já te deparaste com uma tarefa que exigia o download de um ficheiro com o Puppeteer. Esta é, de facto, uma tarefa recorrente na comunidade de scraping. Mas não está bem explicada na documentação do Puppeteer.

Felizmente, vamos tratar disso juntos. Neste artigo, vamos discutir o download de ficheiros no Puppeteer. Há dois objetivos que quero que abordemos hoje:

  • Ter uma compreensão sólida de como o Puppeteer lida com downloads
  • Criar um scraper funcional para download de ficheiros usando o Node e o Puppeteer

No final deste artigo, terá adquirido as competências teóricas e práticas de que um programador necessita para construir um scraper de ficheiros. Se este projeto lhe parece tão empolgante como me parece a mim, vamos a isso!

Porquê descarregar ficheiros com o Puppeteer?

Existem muitos casos de utilização para um scraper de ficheiros e o StackOverflow está repleto de programadores à procura de respostas sobre como descarregar ficheiros com o Puppeteer. E precisamos de compreender que os ficheiros incluem imagens, PDFs, documentos Excel ou Word e muitos mais. É fácil perceber porque é que todos estes podem fornecer informações muito importantes para alguém.

Por exemplo, existem empresas no setor do dropshipping que dependem de imagens extraídas de fontes externas, como marketplaces. Outro bom exemplo de caso de utilização de um scraper de download de ficheiros é para empresas que monitorizam documentos oficiais. Ou mesmo pequenos projetos. Eu próprio tenho um script que descarrega faturas do site de um parceiro.

Agora, no que diz respeito à utilização do Puppeteer para descarregar ficheiros, verifico que a maioria das pessoas o escolhe principalmente por duas razões:

  • Foi concebido para o Node JS, e o Node JS é uma das linguagens de programação mais populares, tanto para o front-end como para o back-end.
  • Ele abre um navegador real e alguns sites dependem de JavaScript para renderizar conteúdo. O que significa que não seria possível descarregar os ficheiros usando um cliente HTTP normal, incapaz de renderizar ficheiros JavaScript.

Como o Puppeteer lida com o download de ficheiros

Para compreender como descarregar ficheiros com o Puppeteer, temos de saber também como o Chrome o faz. Isto porque, na sua essência, o Puppeteer é uma biblioteca que «controla» o Chrome através do Chrome DevTools Protocol (CDP).

No Chrome, os ficheiros podem ser descarregados:

  • Manualmente, com um clique num botão, por exemplo
  • Programaticamente, através do Page Domain do CDP.

E existe também uma terceira técnica utilizada no web scraping. Nomeadamente, integra um novo interveniente: um cliente HTTP. Desta forma, o web scraper recolhe os `hrefs` dos ficheiros e, em seguida, utiliza-se um cliente HTTP para descarregar os ficheiros. Cada opção tem os seus casos de utilização específicos e, por isso, vamos explorar ambas as formas.

Descarregar ficheiros no Puppeteer com um clique num botão

Na feliz circunstância de o site de onde pretende extrair ficheiros utilizar botões, basta simular o evento de clique no Puppeteer. A implementação de um descarregador de ficheiros é bastante simples neste cenário. O Puppeteer até documenta o método Page.click() e pode encontrar mais informações aqui.

Uma vez que este é um comportamento «semelhante ao humano», o que precisamos de fazer é:

  • Abrir o navegador
  • Navegar até à página web pretendida
  • Localizar o elemento do botão (por exemplo, através do seu seletor CSS ou xPath)
  • Clicar no botão

São apenas quatro passos simples que precisamos de implementar no nosso script. No entanto, antes de nos dedicarmos à codificação, deixe-me dizer-lhe que, tal como no seu navegador diário, a instância do Chrome controlada pelo Puppeteer irá guardar o ficheiro descarregado na pasta de downloads predefinida, que é:

  • \Users\<username>\Downloads para Windows
  • /Users/<username>/Downloads para Mac
  • /home/<username>/Downloads para Linux

Tendo isto em mente, vamos começar a programar. Vamos assumir que somos astrofísicos e precisamos de recolher alguns dados da NASA, que iremos processar mais tarde. Por agora, vamos concentrar-nos em descarregar os ficheiros .doc.

#1: Identificar elementos «clicáveis»

Vamos navegar até ao domínio da NASA aqui e inspecionar os elementos da página. O nosso foco é identificar elementos «clicáveis». Para encontrar os elementos, abra as Ferramentas do Desenvolvedor (Command + Option + I / Control + Shift + I no Chrome):

#2: Codificar o projeto

import puppeteer from "puppeteer"

(async () => {

   const browser = await puppeteer.launch({ headless: false })

   const page = await browser.newPage()

   await page.goto('https://www.nasa.gov/centers/dryden/research/civuav/civ_uav_doc-n-ref.html',

       { waitUntil: 'networkidle0' })

   const tr_elements = await page.$x('html/body/div[1]/div[3]/div[2]/div[2]/div[5]/div[1]/table[2]/tbody/tr')

   for (let i = 2; i<=tr_elements.length; i ++) {

       const text = await tr_elements[i].evaluate(el => el.textContent)

       if (text.toLocaleLowerCase().includes('doc')) {

           try {

               await page.click(`#backtoTop > div.box_710_cap > div.box_710.box_white.box_710_white > div.white_article_wrap_detail.text_adjust_me > div.default_style_wrap.prejs_body_adjust_detail > table:nth-child(6) > tbody > tr:nth-child(${i}) > td:nth-child(3) > a`)

           }catch {}

       }

   }

   await browser.close()

})()

O que estamos a fazer aqui é:

  • Iniciar o Puppeteer e navegar até ao nosso site de destino
  • Selecionar todos os elementos `tr`, que contêm o `href` em que queremos clicar mais tarde
  • Percorrer os elementos `tr` e a. Verificar se o texto dentro do elemento contém a palavra «doc» b. Se contiver, criamos o seletor e clicamos no elemento
  • Fechar o navegador

E é isso. Baixar arquivos com o Puppeteer não poderia ser mais simples.

Descarregar ficheiros no Puppeteer com o CDP

Sei que a pasta de downloads padrão não é um grande problema para projetos pequenos. Para projetos maiores, por outro lado, certamente vai querer organizar os ficheiros descarregados com o Puppeteer em diretórios diferentes. E é aí que o CDP entra em ação. Para atingir este objetivo, vamos manter o código atual e apenas adicionar-lhe algo.

A primeira coisa que nos ocorre é resolver o caminho para o diretório atual. Felizmente, podemos usar o módulo node:path integrado. Tudo o que precisamos de fazer é importar o módulo `path` para o nosso projeto e usar o método `resolve`, como verá daqui a pouco.

O segundo aspeto é definir o caminho no nosso navegador utilizando o CDP. Como disse anteriormente, vamos utilizar o método `.setDownloadBehavior` do Page Domain. Eis como fica o nosso código atualizado com as duas adições:

import puppeteer from "puppeteer"

import path from 'path'

(async () => {

   const browser = await puppeteer.launch({ headless: false })

   const page = await browser.newPage()

   const client = await page.target().createCDPSession()

   await client.send('Page.setDownloadBehavior', {

       behavior: 'allow',

       downloadPath: path.resolve('./documents')

   });

   await page.goto('https://www.nasa.gov/centers/dryden/research/civuav/civ_uav_doc-n-ref.html',

       { waitUntil: 'networkidle0' })

   const tr_elements = await page.$x('html/body/div[1]/div[3]/div[2]/div[2]/div[5]/div[1]/table[2]/tbody/tr')

   for (let i = 1; i<=tr_elements.length; i ++) {

       const text = await tr_elements[i].evaluate(el => el.textContent)

       if (text.toLocaleLowerCase().includes('doc')) {

           try {

               await page.click(`#backtoTop > div.box_710_cap > div.box_710.box_white.box_710_white > div.white_article_wrap_detail.text_adjust_me > div.default_style_wrap.prejs_body_adjust_detail > table:nth-child(6) > tbody > tr:nth-child(${i}) > td:nth-child(3) > a`)

           } catch {}

       }

   }

   await browser.close()

})()

Eis o que estamos a fazer com o código adicionado:

  • Estamos a criar uma nova CDPSession para «comunicar em proto-logo Chrome Devtools»
  • Estamos a emitir o evento `Page.setDownloadBehavior` onde a. `behavior` é definido como `allow` para permitir downloads b. `downloadPath` é construído com `node:path` para apontar para a pasta onde iremos armazenar os nossos ficheiros

E isso é tudo o que tem de fazer se quiser alterar o diretório onde os ficheiros são guardados com o Puppeteer. Além disso, também alcançámos o nosso objetivo de criar um web scraper para download de ficheiros.

Descarregar ficheiros com o Puppeteer e o Axios

A terceira opção que discutimos é reunir links para os sites-alvo e usar um cliente HTTP para os descarregar. Pessoalmente, prefiro o Axios, mas também existem alternativas quando se trata de web scraping. Assim, para efeitos deste tutorial, vou usar o Axios e assumir que estamos a criar um scraper para imagens de carros leiloados.

const get_links = async (url) => {

   const hrefs = []

   const browser = await puppeteer.launch({ headless: false })

   const page = await browser.newPage()

   await page.goto(url, { waitUntil: 'networkidle0' })

   const images = await page.$$('img')

   for (let i = 1; i<=images.length; i ++) {

       try {

          hrefs.push(await images[i].evaluate(img => img.src))

      } catch {}

   }

   await browser.close()

   return hrefs

}

Tenho a certeza de que, a esta altura, já está familiarizado com a sintaxe do Puppeteer. Ao contrário dos scripts acima, o que estamos a fazer agora é avaliar elementos `img`, extrair o seu URL de origem, anexá-lo a uma matriz e devolver a matriz. Não há nada de especial nesta função.

#2: Guardar ficheiros com o axios

const download_file = async (url, save) => {

   const writer = fs.createWriteStream(path.resolve(save))

   const response = await axios({

       url,

       method: 'GET',

       responseType: 'stream'

   })

   response.data.pipe(writer)

   return new Promise((resolve, reject) => {

       writer.on('finish', resolve)

       writer.on('error', reject)

   })

}

Esta função é um pouco mais complexa, pois introduz dois novos pacotes: `fs` e `axios`. O primeiro método do pacote `fs` é bastante autoexplicativo. O que ele faz é criar um fluxo gravável. Pode ler mais sobre isso aqui.

Em seguida, usamos o axios para «aceder» à URL do servidor e informamos ao axios que a resposta será do tipo «stream». Por fim, como estamos a trabalhar com um stream, vamos usar `pipe()` para escrever a resposta no nosso stream.

Com esta configuração, tudo o que resta fazer é combinar as duas funções num programa executável. Para tal, basta adicionar as seguintes linhas de código:

let i = 1

const images = await get_links('https://www.iaai.com/Search?url=PYcXt9jdv4oni5BL61aYUXWpqGQOeAohPK3E0n6DCLs%3d')

images.forEach(async (img) => {

   await download_file(img, `./images/${i}.svg`)

   i += 1

})

Conclusões

A documentação do Puppeteer pode não ser muito clara sobre o download de ficheiros. Apesar disso, hoje descobrimos várias formas de o implementar. Mas note que extrair ficheiros com o Puppeteer não é realmente uma tarefa fácil. É que o Puppeteer inicia um navegador sem interface gráfica e estes costumam ser bloqueados muito rapidamente.

Se procura uma forma discreta de descarregar ficheiros programaticamente, pode considerar um serviço de web scraping. Na Web Scraping API, investimos muito tempo e esforço em ocultar as nossas pegadas digitais para não sermos detetados. E isso reflete-se nos resultados reais da nossa taxa de sucesso. Descarregar ficheiros usando a Web Scraping API pode ser tão fácil quanto enviar um pedido curl, e nós tratamos do resto.

Dito isto, espero sinceramente que o artigo de hoje o tenha ajudado no seu processo de aprendizagem. Além disso, espero que tenhamos cumprido os nossos dois objetivos iniciais. A partir de agora, deverá ser capaz de criar as suas próprias ferramentas e descarregar ficheiros com o Puppeteer.

Sobre o autor
Mihnea-Octavian Manolache, Desenvolvedor Full Stack @ WebScrapingAPI
Mihnea-Octavian ManolacheDesenvolvedor Full Stack

Mihnea-Octavian Manolache é engenheiro Full Stack e DevOps na WebScrapingAPI, onde desenvolve funcionalidades do produto e mantém a infraestrutura que garante o bom funcionamento da 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.