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

La guía definitiva para crear un rastreador web con Pyppeteer

La guía definitiva para crear un rastreador web con Pyppeteer

En lo que respecta a Python y la automatización web, Selenium era prácticamente la opción por excelencia. Al menos hasta ahora. Debido al éxito de Puppeteer en la comunidad de JavaScript, los desarrolladores de Python empezaron a interesarse cada vez más por él. Y así es como surgió Pyppeteer. Pero, ¿qué es exactamente Pyppeteer? ¿Y por qué deberíamos elegirlo en lugar de Selenium? ¿Es lo suficientemente fiable como para crear una solución compleja con él? A todas estas preguntas y muchas más, responderemos en el artículo de hoy. Mi objetivo para hoy es que, si lees este material, te quedes al menos con:

  • Una definición de Pyppeteer y sus casos de uso
  • Una comprensión de cómo se compara Pyppeteer con Selenium
  • Una implementación real de un rastreador web utilizando Pyppeteer

¡Así que prepárate, porque hoy vamos a hablar y a ponernos manos a la obra con la programación!

¿Qué es realmente Pyppeteer y cómo se puede utilizar?

Si estás leyendo esto, es probable que ya estés familiarizado con lo que es la programación web en general. Y seguramente ya hayas oído hablar de Puppeteer o Selenium, dependiendo de tu lenguaje de programación favorito. Pero Pyppeteer es, sin duda, una novedad en el ámbito del scraping web. Bueno, para resumir, Pyppeteer se parece mucho más a Puppeteer que a Selenium.

Puppeteer es una biblioteca de Node.js que facilita el control de una versión sin interfaz gráfica de Chrome a través del protocolo DevTools. Pyppeteer es una adaptación a Python de Puppeteer. Al igual que el Puppeteer original, Pyppeteer es una biblioteca, escrita en Python, que básicamente automatiza un navegador. En otras palabras, Pyppeteer es una implementación en Python de la API de Puppeteer, lo que te permite utilizar las funciones de Puppeteer en un entorno Python. La principal diferencia entre ambos es el lenguaje utilizado.

Terminología de Pyppeteer que debes conocer

Antes de continuar, creo que deberíamos repasar algunos términos que se utilizan habitualmente en el contexto de Pyppeteer:

  • Headless: significa iniciar un navegador sin interfaz gráfica de usuario (GUI). En otras palabras, se ejecuta «entre bastidores» y no se ve en la pantalla. Suele utilizarse para reducir el consumo de recursos durante el scraping.
  • Headful: Por el contrario, un navegador «headful» es aquel que se ejecuta con una GUI. Es lo opuesto a un navegador headless y se utiliza a menudo para realizar pruebas, depurar o interactuar con páginas web manualmente.
  • Contexto del navegador: Es un estado compartido por todas las páginas de un navegador. Se suele utilizar para configurar ajustes a nivel de todo el navegador, como cookies, encabezados HTTP y geolocalización.
  • DOM: El Modelo de Objetos de Documento (DOM) es una interfaz de programación para documentos HTML y XML. Representa la estructura de una página web en un formato arbóreo, con nodos que representan elementos. Pyppeteer te permite interactuar con los elementos de una página manipulando el DOM.
  • Elementos: Son los componentes básicos de una página web. Se definen mediante etiquetas, atributos y valores.

Por supuesto, hay mucho más y aprenderás más sobre la marcha. Pero quería que te hicieras una idea general para que tengamos una base sólida. Estoy convencido de que conocer estos términos te ayudará a comprender mejor la esencia de este artículo.

¿Por qué utilizar Pyppeteer en tu proyecto de scraping?

Creo que hay dos aspectos que hay que tener en cuenta. El primero es por qué Pyppeteer es una buena opción para el web scraping en general. El segundo es por qué utilizar Pyppeteer en lugar de Selenium. En general, algunas de las ventajas de Pyppeteer son:

  • Evaluación de JavaScript: Pyppeteer proporciona una función `page.evaluate()`. Te permite ejecutar código JavaScript dentro del contexto de la página.
  • Control de red: Pyppeteer proporciona un método `page.on()`. Esto te permite escuchar eventos de red, como solicitudes y respuestas, que tienen lugar en una página.
  • Rastreo y registro: Pyppeteer permite rastrear la actividad del navegador y registrar los mensajes del navegador desde una página. Esto facilita la depuración, el rastreo y la comprensión de lo que hace un sitio web.

En comparación con Selenium, es bastante similar, ya que ambos se utilizan para automatizar un navegador web. Sin embargo, hay algunas diferencias clave y ventajas que Pyppeteer tiene sobre Selenium:

  • Simplicidad: Pyppeteer tiene una API más sencilla y coherente que Selenium, lo que facilita su uso para los principiantes. La API de Pyppeteer se basa en el protocolo DevTools, que está muy cerca del navegador y es fácil de aprender y utilizar.
  • Rendimiento: Pyppeteer puede ser más rápido que Selenium porque se basa en el protocolo DevTools. El protocolo está diseñado para depurar páginas web y es mucho más rápido que Selenium WebDriver.
  • Mejor control de la red: Pyppeteer permite un mayor control sobre la configuración de red del navegador, como la interceptación de solicitudes y el bloqueo de solicitudes/respuestas. Esto facilita la prueba y el diagnóstico de problemas relacionados con la red.

Y, por supuesto, también es una cuestión de elección. Tomémosme a mí como ejemplo. En mi día a día, programo en JavaScript. Y estoy bastante familiarizado con Puppeteer. Pero, por otro lado, mi lenguaje de programación favorito es Python. Así que, si tuviera que crear un scraper con una tecnología conocida en el lenguaje que prefiero, probablemente optaría por Pyppeteer.

Y dicho esto, creo que ya hemos cubierto los aspectos teóricos de este artículo. Es hora de empezar con la programación propiamente dicha.

Cómo crear un scraper web con Pyppeteer

Antes de empezar a programar, déjame presentarte la documentación oficial de Pyppeteer. Soy partidario de usar la documentación oficial siempre que uno se sienta atascado. Eso es antes de hacer preguntas en la comunidad (como en Stackoverflow). Normalmente encuentro que la mayoría de las respuestas se pueden encontrar si simplemente lees la documentación primero. Así que tómate esto como un consejo de mi parte. Siempre que te quedes atascado, consulta la documentación, luego busca respuestas y solo haz preguntas como último recurso.

#1: Configurar el entorno

Lo primero es lo primero: como desarrollador de Python, probablemente estés familiarizado con los entornos virtuales. Así que lo primero que tenemos que hacer es crear un entorno virtual para nuestro proyecto. Esta es, por lo general, la secuencia de comandos que utilizo:

# Create a new directory and navigate into it

~ » mkdir py_project && cd py_project

# Create the virtual environment

~ » python3 -m venv env

# Activate the virtual environment

~ » source env/bin/activate


En cuanto al entorno virtual, ya lo tienes todo listo. Es hora de seguir adelante e instalar Pyppeteer. Como ya tienes el terminal abierto, solo tienes que escribir:

# Install the package using pip

~ » python3 -m pip install pyppeteer

# Open the project in your IDE

~ » code .

#2: Crear un sencillo scraper con Pyppeteer

El último comando abre Visual Studio Code o tu IDE preferido. Ahora que estás en el «entorno de desarrollo», creemos un nuevo archivo `.py` que contendrá nuestro código. Llamaré a mi archivo `scraper.py`. Ten en cuenta que Pyppeteer admite de forma nativa la ejecución asíncrona. Así que importemos tanto `asyncio` como `pyppeteer` a nuestro archivo:

import asyncio
from pyppeteer import launch

Una vez hecho esto, podemos pasar a un código más complejo. En general, no soy un gran defensor de la programación funcional. Sin embargo, creo que dividir el código en pequeños fragmentos facilita el aprendizaje. Así que vamos a envolver nuestro código dentro de una función:

async def scrape(url):

   browser = await launch()

   page = await browser.newPage()

   await page.goto(url)

   content = await page.content()

   await browser.close()

  

   return content

Esta función toma una URL como entrada y lanza un navegador sin interfaz gráfica utilizando Pyppeteer. A continuación, navega a la URL proporcionada, recupera el contenido de la página y cierra el navegador. El valor que devuelve no es otro que el HTML recopilado de la página. Puedes utilizar esta función para extraer datos de casi cualquier sitio web. Para utilizar la función, la llamarías en un bucle de eventos `asyncio`, así:

async def main():

   content = await scrape('https://www.example.com')

   print(content)

loop = asyncio.get_event_loop()

loop.run_until_complete(main())

#3: Añadir más funcionalidades

Hasta este punto, tenemos un scraper que funciona. Pero eso es prácticamente todo lo que tenemos. Si quieres crear un scraper web más avanzado con Pyppeteer, tendrás que añadir más funcionalidades a id. Alerta de spoiler: nos adentraremos en el mundo de la programación orientada a objetos. Pero primero, definamos nuestros objetivos. ¿Qué queremos que sea capaz de hacer nuestro scraper?

  • Inicializar el navegador con algunos valores personalizados
  • Navegar y extraer contenido de una página web
  • Escribir texto en un campo de entrada
  • Extraer el valor de un único elemento
  • Extraer el valor de varios elementos

3.1. Opciones personalizadas

Así que vamos a crear una nueva clase `Scraper` por ahora y añadiremos sus métodos más adelante:

class Scraper:

   def __init__(self, launch_options: dict) -> None:

   	self.options = launch_options['options']

 	self.viewPort = launch_options['viewPort'] if 'viewPort' in launch_options else None

pass

El único argumento que utilizamos para nuestro Scraper es un diccionario `launch_options`. Como ves, contiene dos claves en su interior. Una clave define las opciones del lanzador de Pyppeteer. La segunda opción es `None` o un diccionario que contiene el `width` y el `height` del `viewPort`. Esta última se utiliza para este método.

3.2. Navegar a una página

Si observas la función que utilizamos anteriormente, verás que cubre tanto la navegación como la extracción de datos sin procesar de una URL específica. Lo único que tenemos que hacer es modificarla y convertirla en un método para nuestro Scraper:

async def goto(self, url: str) -> None:

       self.browser = await launch(options=self.options)

       self.page = await self.browser.newPage()

       await self.page.setViewport(self.viewPort) if self.viewPort != None else print('[i] Using default viewport')

       await self.page.goto(url)

Este método es bastante sencillo. En primer lugar, abre un nuevo navegador con las opciones personalizadas que hemos establecido anteriormente. A continuación, crea una nueva página y, si nuestro diccionario `launch_options` contiene `viewPort`, establece el viewPort de la página. De lo contrario, registra un mensaje sencillo. Por último, pero no menos importante, nos lleva al destino.

3.3. Extraer datos sin procesar de una página

De nuevo, tenemos el método en nuestra función `scraper` inicial. Solo esperaremos a que se cargue `page.content()` y devuelva su valor:

async def get_full_content(self) -> str:

       content = await self.page.content()

       return content

3.4. Escribir texto en un campo de entrada

Para escribir algo en un campo de entrada utilizando Pyppeteer, necesitarás dos cosas. Primero, localizar el elemento. Segundo, añadirle algún valor. Por suerte, Pyppeteer tiene métodos para ambas acciones:

async def type_value(self, selector: str, value: str) -> None:

       element = await self.page.querySelector(selector)

       await element.type(value)

3.5. Extraer el valor de la página

Recuerda que queremos poder extraer tanto el valor de un único elemento como los valores de varios elementos. Podríamos usar un único método para ambos. Pero a mí, por lo general, me gusta tener las cosas separadas. Así que, por ahora, añadiré dos métodos más:

async def extract_one(self, selector) -> str:

       element = await self.page.querySelector(selector)

       text = await element.getProperty("textContent")

       return await text.jsonValue()

Aquí, estamos localizando el elemento utilizando el método `querySelector`. A continuación, esperamos el `textContent` y devolvemos su `jsonValue()`. Por otro lado, cuando queramos seleccionar muchos elementos, utilizaremos `querySelector`:

async def extract_many(self, selector) -> list:

       result = []

       elements = await self.page.querySelectorAll(selector)

       for element in elements:

           text = await element.getProperty("textContent")

           result.append(await text.jsonValue())

       return result

Este método funciona de manera similar a `extract_one`. La única diferencia es su valor de retorno. En esta ocasión, devolvemos una lista con todo el texto que hay dentro de los elementos seleccionados. Y supongo que, con esto añadido, hemos cubierto todos nuestros objetivos.

#4: Hazlo sigiloso

En el web scraping, el sigilo puede describirse como la capacidad de pasar desapercibido. Por supuesto, crear un scraper totalmente indetectable requiere mucho trabajo. Por ejemplo, el modo sigiloso de la API de Web Scraping lo mantiene un equipo dedicado. Y el esfuerzo invertido en ello hace que la huella digital de nuestro scraper sea única en cada solicitud.

Pero mi objetivo general con este tutorial es encaminarte por el camino correcto. Y el camino correcto para un scraper web completo con Pyppeteer implica añadirle alguna funcionalidad de sigilo. Por suerte, al igual que existe `puppeteer-extra-plugin-stealth` en Node, también hay un paquete para Python. Y se llama, de forma intuitiva, `pyppeteer-stealth`. Para añadirlo a tu proyecto, primero instálalo usando pip:

~ » python3 -m pip install pyppeteer_stealth

A continuación, impórtalo a tu proyecto y añade una línea de código adicional:

async def goto(self, url: str) -> None:

       self.browser = await launch(options=self.options)

       self.page = await self.browser.newPage()

	  # Make it stealthy

       await stealth(self.page)  

       await self.page.setViewport(self.viewPort) if self.viewPort != None else print('[i] Using default viewport')

       await self.page.goto(url)

Y así es como se ejecuta el scraper. He añadido algunos comentarios al código para destacar lo que hace cada paso:

async def main():

   # Define the launch options dictionary

   launch_options = {

       'options': {

           'headless': False,

           'autoClose': True

       },

       'viewPort': {

           'width': 1600,

           'height': 900

       }

   }

   # Initialize a new scraper

   scraper = Scraper(launch_options)

   # Navigae to your target

   await scraper.goto('https://russmaxdesign.github.io/accessible-forms/accessible-name-input01.html')

   # Type `This is me` inside the input field

   await scraper.type_value(

       '#fish',

       'This is me')

   # Scrape the entire page

   content = await scraper.get_full_content()

   print(content)

   # Scrape one single element

   el = await scraper.extract_one('body > div:nth-child(14) > ul')

   print(el)

   # Scrape multiple elements

   els = await scraper.extract_many('p')

   print(els)

loop = asyncio.get_event_loop()

loop.run_until_complete(main())

Conclusión

Pyppeteer es una herramienta increíble para el scraping web. Porta toda la API de Puppeteer a Python, lo que permite a la comunidad de Python utilizar esta tecnología sin necesidad de aprender JavaScript. Además, no creo que sea un sustituto de Selenium, pero sin duda es una buena alternativa.

Espero que el artículo de hoy haya aportado valor a tu proceso de aprendizaje. Y como me gusta que todos superen sus límites, te reto a que amplíes lo que has aprendido hoy. El scraper que hemos creado juntos es un punto de partida realmente bueno e introduce un elemento clave en la programación: la programación orientada a objetos (OOP). Así que te reto a que añadas más métodos a `Scraper` y lo conviertas en algo realmente increíble.

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.