Volver al blog
Guías
Mihnea-Octavian ManolacheLast updated on Mar 31, 20267 min read

Cómo crear un rastreador y descargar un archivo con Puppeteer

Cómo crear un rastreador y descargar un archivo con Puppeteer

Si te dedicas al web scraping y utilizas Node.js, es muy probable que hayas oído hablar de Puppeteer. Y seguro que te has encontrado con alguna tarea que te ha obligado a descargar archivos con Puppeteer. De hecho, se trata de una tarea habitual en la comunidad del scraping. Sin embargo, no está bien explicada en la documentación de Puppeteer.

Por suerte, lo abordaremos juntos. En este artículo, vamos a hablar de la descarga de archivos en Puppeteer. Hay dos objetivos que quiero que tratemos hoy:

  • Comprender bien cómo gestiona Puppeteer las descargas
  • Crear un scraper de descarga de archivos que funcione utilizando Node y Puppeteer

Al final de este artículo, habrás adquirido tanto los conocimientos teóricos como las habilidades prácticas que un desarrollador necesita para crear un scraper de archivos. Si este proyecto te parece tan emocionante como a mí, ¡manos a la obra!

¿Por qué descargar archivos con Puppeteer?

Hay muchos casos de uso para un scraper de archivos y StackOverflow está lleno de desarrolladores que buscan respuestas sobre cómo descargar archivos con Puppeteer. Y debemos entender que los archivos incluyen imágenes, PDF, documentos de Excel o Word, y muchos más. Es fácil ver por qué todos estos pueden proporcionar información muy importante para alguien.

Por ejemplo, hay empresas del sector del dropshipping que dependen de imágenes extraídas de fuentes externas, como los marketplaces. Otro buen ejemplo de uso de un rastreador de descarga de archivos es el de las empresas que supervisan documentos oficiales. O incluso proyectos pequeños. Yo mismo tengo un script que descarga facturas de la página web de un socio.

Ahora bien, en lo que respecta al uso de Puppeteer para descargar archivos, he observado que la mayoría de la gente lo elige principalmente por dos razones:

  • Está diseñado para Node JS, y Node JS es uno de los lenguajes de programación más populares, tanto para el front-end como para el back-end.
  • Abre un navegador real y algunos sitios web dependen de JavaScript para mostrar el contenido. Esto significa que no podrías descargar los archivos utilizando un cliente HTTP normal, que no es capaz de ejecutar archivos JavaScript.

Cómo gestiona Puppeteer la descarga de archivos

Para entender cómo se descargan archivos con Puppeteer, también tenemos que saber cómo lo hace Chrome. Esto se debe a que, en esencia, Puppeteer es una biblioteca que «controla» Chrome a través del Protocolo de herramientas de desarrollo de Chrome (CDP).

En Chrome, los archivos se pueden descargar:

  • Manualmente, por ejemplo, con un clic en un botón
  • De forma programada, a través del dominio de la página desde el CDP.

Y también existe una tercera técnica utilizada en el web scraping. Concretamente, integra un nuevo actor: un cliente HTTP. De esta forma, el web scraper recopila los `hrefs` de los archivos y, a continuación, se utiliza un cliente HTTP para descargarlos. Cada opción tiene sus casos de uso particulares, por lo que exploraremos ambas formas.

Descargar archivos en Puppeteer con un clic en un botón

En el afortunado caso de que el sitio web del que quieres extraer archivos utilice botones, lo único que tienes que hacer es simular el evento de clic en Puppeteer. La implementación de un descargador de archivos es bastante sencilla en este escenario. Puppeteer incluso documenta el método Page.click() y puedes encontrar más información aquí.

Dado que se trata de un comportamiento «similar al humano», lo que tenemos que hacer es:

  • Abrir el navegador
  • Navegar hasta la página web de destino
  • Localizar el elemento del botón (por su selector CSS o xPath, por ejemplo)
  • Hacer clic en el botón

Solo hay cuatro sencillos pasos que debemos implementar en nuestro script. Sin embargo, antes de ponernos a programar, déjame decirte que, al igual que tu navegador habitual, la instancia de Chrome controlada por Puppeteer guardará el archivo descargado en la carpeta de descargas predeterminada, que es:

  • \Usuarios\<nombre de usuario>\Descargas para Windows
  • /Users/<nombre de usuario>/Descargas para Mac
  • /home/<nombre de usuario>/Descargas para Linux

Teniendo esto en cuenta, empecemos a programar. Supongamos que somos astrofísicos y necesitamos recopilar algunos datos de la NASA, que procesaremos más adelante. Por ahora, centrémonos en descargar los archivos .doc.

#1: Identificar elementos «clicables»

Navegaremos hasta el dominio de la NASA aquí e inspeccionaremos los elementos de la página. Nuestro objetivo es identificar los elementos «clicables». Para encontrar los elementos, abre las Herramientas de desarrollador (Comando + Opción + I / Control + Mayús + I en Chrome):

#2: Programar el proyecto

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()

})()

Lo que vamos a hacer aquí es:

  • Iniciar Puppeteer y navegar hasta nuestro sitio web de destino
  • Seleccionar todos los elementos `tr`, que contienen el `href` en el que queremos hacer clic más tarde
  • Recorrer los elementos `tr` y a. Comprobar si el texto dentro del elemento contiene la palabra «doc» b. Si es así, creamos el selector y hacemos clic en el elemento
  • Cerrar el navegador

Y eso es todo. Descargar archivos con Puppeteer no puede ser más sencillo.

Descargar archivos en Puppeteer con CDP

Sé que la carpeta de descargas predeterminada no supone un gran problema para proyectos pequeños. Sin embargo, en proyectos más grandes, seguramente querrás organizar los archivos descargados con Puppeteer en diferentes directorios. Y ahí es donde entra en juego CDP. Para lograr este objetivo, mantendremos el código actual y solo le añadiremos elementos.

Lo primero que se nos ocurre es resolver la ruta al directorio actual. Por suerte, podemos usar el módulo integrado `node:path`. Todo lo que tenemos que hacer es importar el módulo `path` a nuestro proyecto y usar el método `resolve`, como verás en un momento.

El segundo aspecto es configurar la ruta en nuestro navegador utilizando CDP. Como dije antes, utilizaremos el método `.setDownloadBehavior` de Page Domain. Así es como queda nuestro código actualizado con las dos adiciones:

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()

})()

Esto es lo que hacemos con el código añadido:

  • Estamos creando una nueva CDPSession para «comunicarnos mediante el protocolo Chrome Devtools sin procesar»
  • Estamos emitiendo el evento `Page.setDownloadBehavior` donde a. `behavior` se establece en `allow` para permitir descargas b. `downloadPath` se construye con `node:path` para apuntar a la carpeta donde almacenaremos nuestros archivos

Y eso es todo lo que tienes que hacer si quieres cambiar el directorio en el que se guardan los archivos con Puppeteer. Además, también hemos logrado nuestro objetivo de crear un rastreador web para la descarga de archivos.

Descargar archivos con Puppeteer y Axios

La tercera opción que hemos comentado es recopilar enlaces a los sitios de interés y utilizar un cliente HTTP para descargarlos. Personalmente, prefiero Axios, pero también hay alternativas a la hora de realizar scraping web. Así pues, a efectos de este tutorial, voy a utilizar Axios y daré por hecho que estamos creando un scraper para imágenes de coches subastados.

#1: Recopilar los enlaces a nuestros archivos

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

}

Estoy seguro de que a estas alturas ya estás familiarizado con la sintaxis de Puppeteer. A diferencia de los scripts anteriores, lo que estamos haciendo ahora es evaluar los elementos `img`, extraer su URL de origen, añadirla a una matriz y devolver la matriz. No hay nada complicado en esta función.

#2: Guardar archivos con 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 función es un poco más compleja, ya que introduce dos nuevos paquetes: `fs` y `axios`. El primer método del paquete `fs` se explica prácticamente por sí mismo. Lo que hace es crear un flujo de escritura. Puedes leer más al respecto aquí.

A continuación, usamos axios para «conectarnos» a la URL del servidor y le indicamos a axios que la respuesta será de tipo «stream». Por último, dado que estamos trabajando con un stream, vamos a usar `pipe()` para escribir la respuesta en nuestro stream.

Con esta configuración, solo queda combinar las dos funciones en un programa ejecutable. Para ello, basta con añadir las siguientes líneas 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

})

Conclusiones

La documentación de Puppeteer puede resultar poco clara en lo que respecta a la descarga de archivos. A pesar de ello, hoy hemos descubierto bastantes formas de implementarla. Pero ten en cuenta que extraer archivos con Puppeteer no es realmente una tarea fácil. Verás, Puppeteer inicia un navegador sin interfaz gráfica y estos suelen bloquearse muy rápidamente.

Si buscas una forma discreta de descargar archivos mediante programación, quizá te interese un servicio de web scraping. En Web Scraping API hemos invertido mucho tiempo y esfuerzo en ocultar nuestras huellas para que no nos detecten. Y eso se refleja en nuestra tasa de éxito. Descargar archivos con Web Scraping API puede ser tan fácil como enviar una solicitud curl, y nosotros nos encargamos del resto.

Dicho esto, espero de verdad que el artículo de hoy te haya ayudado en tu proceso de aprendizaje. Además, espero que hayamos cumplido nuestros dos objetivos iniciales. A partir de ahora, deberías ser capaz de crear tus propias herramientas y descargar archivos con Puppeteer.

Acerca del autor
Mihnea-Octavian Manolache, Desarrollador Full Stack @ WebScrapingAPI
Mihnea-Octavian ManolacheDesarrollador Full Stack

Mihnea-Octavian Manolache es ingeniero Full Stack y DevOps en WebScrapingAPI, donde se encarga de desarrollar funciones para los productos y de mantener la infraestructura que garantiza el buen funcionamiento de la plataforma.

Empieza a crear

¿Estás listo para ampliar tu recopilación de datos?

Únete a más de 2000 empresas que utilizan WebScrapingAPI para extraer datos de la web a escala empresarial sin ningún gasto de infraestructura.