Voltar ao blogue
Guias
Mihnea-Octavian Manolache25 de abril de 20238 min de leitura

Como construir um raspador e descarregar um ficheiro com o Puppeteer

Como construir um raspador e descarregar um ficheiro com o Puppeteer

Porquê descarregar um ficheiro com o Puppeteer?

Há muitos casos de uso para um raspador de arquivos e o StackOverflow está cheio de desenvolvedores procurando respostas sobre como baixar arquivos com o puppeteer. E temos de compreender que os ficheiros incluem imagens, PDFs, documentos do Excel ou do Word e muitos outros. Pode ver porque é que tudo isto pode fornecer informações muito importantes a alguém.

Por exemplo, há empresas no sector do dropshipping que dependem de imagens extraídas de fontes externas, como mercados. Outro bom exemplo de caso de utilização de um raspador de descarregamento de ficheiros é para empresas que monitorizam documentos oficiais. Ou mesmo pequenos projectos. Eu próprio tenho um script que descarrega facturas do sítio Web de um parceiro.

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

  • Foi concebido para Node JS, e Node JS é uma das linguagens de programação mais populares, tanto para o front end como para o back end.
  • Abre um navegador real e alguns sítios Web dependem do JavaScript para processar o conteúdo. Isto significa que não seria possível descarregar os ficheiros utilizando um cliente HTTP normal que não consegue processar ficheiros JavaScript.

Como o Puppeteer lida com o download de ficheiros

Para compreender como descarregar ficheiros com o Puppeteer, temos de saber como o Chrome também 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 de um botão, por exemplo
  • De forma programática, através do domínio de página do CDP.

E há ainda uma terceira técnica utilizada na recolha de dados da Web. Nomeadamente, integra um novo ator: um cliente HTTP. Desta forma, o web scraper recolhe `hrefs` dos ficheiros e depois é utilizado um cliente HTTP para descarregar os ficheiros. Cada opção tem os seus casos de utilização particulares e, por isso, vamos explorar ambas as formas.

Descarregar ficheiros no Puppeteer com um clique de um botão

No caso de o site que você deseja extrair arquivos usar botões, tudo o que você precisa fazer é simular o evento de clique no Puppeteer. A implementação de um downloader de arquivos é bastante simples neste cenário. O Puppeteer até documenta o método Page.click() e você pode encontrar mais informações aqui.

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

  • Abrir o browser
  • Navegar para a página Web visada
  • Localize o elemento do botão (através do seu seletor CSS ou xPath, por exemplo)
  • Clicar no botão

Existem apenas quatro passos simples que precisamos de implementar no nosso script. No entanto, antes de começarmos a codificá-lo, deixe-me dizer-lhe que, tal como o seu browser do dia a dia, a instância do Chrome controlada pelo Puppeteer irá guardar o ficheiro transferido na pasta de transferências predefinida, que é:

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

Com isto em mente, vamos começar a programar. Vamos assumir que somos astrofísicos e que 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 para o domínio da NASA aqui e inspecionar os elementos da página. O nosso objetivo é identificar os elementos "clicáveis". Para encontrar os elementos, abra as Ferramentas de desenvolvimento (Command + Option + I / Control + Shift + I no Chrome):

Página de listagem de documentos da NASA com as ferramentas de desenvolvimento do navegador a destacar um elemento de link de download

#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 para o nosso sítio Web de destino
  • Selecionar todos os elementos `tr`, que contêm a `href` em que queremos clicar mais tarde
  • Percorra os elementos da árvore e a. Verifique se o texto dentro do elemento contém a palavra «doc» b. Se contiver, criamos o seletor e clicamos no elemento
  • Fechar o browser

E é tudo. O download de ficheiros com o Puppeteer pode ser tão simples quanto possível.

Descarregar ficheiros no Puppeteer com CDP

Eu sei que a pasta de downloads padrão não é um grande problema para pequenos projetos. Para projetos maiores, por outro lado, você certamente vai querer organizar os arquivos baixados com o Puppeteer em diferentes diretórios. E é aí que o CDP entra em cena. Para atingir esse objetivo, manteremos o código atual e apenas adicionaremos algo a ele.

A primeira coisa em que se pode pensar é resolver o caminho para o diretório atual. Felizmente, nós podemos usar o módulo node:path embutido. Tudo o que precisamos fazer é importar o módulo `path` para o nosso projeto e usar o método `resolve`, como você verá daqui a pouco.

O segundo aspeto é definir o caminho no nosso navegador usando o CDP. Como eu disse antes, usaremos o método `.setDownloadBehavior` do Page Domain. Aqui está o aspeto do nosso código atualizado com os dois métodos adicionados:

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 "falar do protocolo Chrome Devtools em bruto
  • Estamos a emitir o evento `Page.setDownloadBehavior`, em que a. `behavior` é definido como `allow` para permitir downloads b. `downloadPath` é criado com `node:path` para apontar para a pasta onde iremos guardar os nossos ficheiros

E isto é tudo o que tem de fazer se quiser mudar o diretório onde guarda os ficheiros com o Puppeteer. Além disso, também atingimos o nosso objetivo de construir um web scraper de download de ficheiros.

Descarregar ficheiro com o Puppeteer e o Axios

A terceira opção que discutimos consiste em recolher ligações para os sítios visados e utilizar um cliente HTTP para as descarregar. Pessoalmente, prefiro o axios, mas também há alternativas a ele quando se trata de raspagem da Web. Por isso, para efeitos deste tutorial, vou utilizar o axios e assumir que estamos a construir 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

}

Eu tenho certeza que agora você já está familiarizado com a sintaxe do Puppeteer. Ao contrário dos scripts acima, o que estamos fazendo agora é avaliar os elementos `img`, extraindo sua url de origem, anexando-a a um array e retornando o array. 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` é praticamente auto-explicativo. O que ele faz é criar um fluxo gravável. Você pode ler mais sobre ele aqui.

Em seguida, usamos o axios para 'tocar' na URL do servidor, e deixamos o axios saber que a resposta será do tipo 'stream'. Por fim, como estamos trabalhando 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 o fazer, 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 clara sobre o download de ficheiros. Apesar disso, hoje descobrimos algumas formas de o implementar. Mas note que fazer scraping de arquivos com o Puppeteer não é uma tarefa fácil. É que o Puppeteer lança um browser sem cabeça e estes normalmente são bloqueados muito rapidamente.

Se está à procura de uma forma furtiva de descarregar ficheiros programaticamente, pode voltar a sua atenção para um serviço de raspagem da Web. Na Web Scraping API, investimos muito tempo e esforço para esconder as nossas impressões digitais, de modo a não sermos detectados. E isso mostra resultados reais na nossa taxa de sucesso. Descarregar ficheiros utilizando a Web Scraping API pode ser tão fácil como enviar um pedido curl, e nós tratamos do resto.

Dito isto, espero que o artigo de hoje o tenha ajudado no seu processo de aprendizagem. Além disso, espero que tenhamos atingido ambos os nossos objectivos iniciais. A partir de agora, deve ser capaz de construir as suas próprias ferramentas e descarregar ficheiros com o Puppeteer.

Sobre o autor
Mihnea-Octavian Manolache, Desenvolvedor Full Stack na 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.