Volver al blog
Guías
Raluca Penciuc21 de diciembre de 20229 min de lectura

Scraping con Cheerio: Cómo recopilar fácilmente datos de páginas web

Scraping con Cheerio: Cómo recopilar fácilmente datos de páginas web

Introducción a Cheerio

«Pero, ¿qué es Cheerio?», te preguntarás. Bueno, para aclarar un error común, empezaré por lo que Cheerio no es: un navegador.

La confusión puede deberse a que Cheerio analiza documentos escritos en un lenguaje de marcado y, a continuación, ofrece una API que permite manipular la estructura de datos resultante. Sin embargo, a diferencia de un navegador, Cheerio no muestra visualmente el documento, no carga archivos CSS ni ejecuta JavaScript.

En resumen, lo que hace Cheerio es recibir una entrada HTML o XML, analizar la cadena y devolver la API. Esto lo convierte en una herramienta increíblemente rápida y fácil de usar, de ahí su popularidad entre los desarrolladores de Node.js.

Configuración del entorno

Ahora, veamos algunos ejemplos prácticos de lo que puede hacer Cheerio. Lo primero es lo primero: debes asegurarte de que tu entorno esté correctamente configurado.

No hace falta decir que debes tener Node.js instalado en tu ordenador. Si no es así, solo tienes que seguir las instrucciones de su página web oficial, según tu sistema operativo.

Asegúrate de descargar la versión de soporte a largo plazo (LTS) y no te olvides del gestor de paquetes de Node.js (NPM). Puedes ejecutar estos comandos para comprobar que la instalación se ha realizado correctamente:

node -v
npm -v

El resultado debería ser el siguiente:

Terminal de Windows en el que se muestran los comandos para consultar la versión de Node.js y npm, así como sus resultados

Ahora, en cuanto al debate sobre los entornos de desarrollo integrado (IDE): para este tutorial, voy a utilizar Visual Studio Code, ya que es bastante flexible y fácil de usar, pero puedes utilizar el IDE que prefieras.

Solo tienes que crear una carpeta donde guardar tu pequeño proyecto y abrir un terminal. Ejecuta el siguiente comando para configurar un proyecto de Node.js:

npm init -y

Esto creará una versión predeterminada del archivo package.json, que se puede modificar en cualquier momento.

Próximo paso: voy a instalar TypeScript junto con las definiciones de tipos para Node.js:

npm install typescript @types/node -save-dev

En este tutorial he elegido TypeScript por su tipado estático opcional para los objetos de JavaScript, lo que hace que el código sea más a prueba de fallos en lo que respecta a los errores de tipado. 

Esta es la misma ventaja que ha hecho que su popularidad no haya dejado de crecer entre la comunidad de JavaScript, según una encuesta reciente de CircleCI sobre los lenguajes de programación más populares.

Para comprobar que el comando anterior se ha instalado correctamente, puedes ejecutar:

npx tsc --version

Ahora voy a crear el archivo de configuración `tsconfig.json` en la raíz del directorio del proyecto, donde se deben definir las opciones del compilador. Si quieres conocer mejor este archivo y sus propiedades, la documentación oficial de TypeScript te puede ayudar. 

Si no es así, solo tienes que copiar y pegar lo siguiente:

{
    "compilerOptions": {
        "module": "commonjs",
        "esModuleInterop": true,
        "target": "es6",
        "moduleResolution": "node",
        "sourceMap": true,
        "outDir": "dist"
    },
    "lib": ["es2015"]
}

¡Ya casi está! Ahora tienes que instalar Cheerio (por supuesto):

npm install cheerio

Por último, pero no por ello menos importante, crea el directorio «src» , que contendrá los archivos de código. Y ya que hablamos de los archivos de código, crea el archivo «index.ts» y colócalo en el directorio «src ».

¿Cómo funciona Cheerio?

¡Perfecto! Ahora ya puedes ponerte manos a la obra.

Por ahora, voy a mostrar algunas funciones básicas de Cheerio utilizando un documento HTML estático. Solo tienes que copiar y pegar el contenido que aparece a continuación en un nuevo archivo llamado static.html dentro de tu proyecto:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Page Name - Static HTML Example</title>
</head>
<body>
    <div class="page-heading">
        <h1>Page Heading</h1>
    </div>
    <div class="page-container">
        <div class="page-content">
            <ul>
                <li>
                    <a href="#">Item 1</a>
                      <p class="price">$100</p>
                      <p class="stock">12</p>
                </li>
                <li>
                    <a href="#">Item 2</a>
                    <p class="price">$200</p>
                    <p class="stock">422</p>
                </li>
                <li>
                    <a href="#">Item 3</a>
                    <p class="price">$150</p>
                    <p class="stock">5</p>
                </li>
            </ul>
        </div>
    </div>
    <footer class="page-footer">
        <p>Last Updated: Friday, September 23, 2022</p>
    </footer>
</body>
</html>

A continuación, debes proporcionar el archivo HTML como entrada a Cheerio, que a continuación devolverá la API resultante:

import fs from 'fs'
import * as cheerio from 'cheerio'

const staticHTML = fs.readFileSync('static.html')
const $ = cheerio.load(staticHTML)

Si aparece un error en este paso, asegúrate de que el archivo de entrada contenga un documento HTML válido, ya que a partir de la versión 1.0.0 de Cheerio también se comprueba este criterio.

Ahora puedes empezar a probar todo lo que Cheerio tiene para ofrecer. El paquete NPM es conocido por su sintaxis similar a la de jQuery y por el uso de selectores CSS para extraer los nodos que buscas. Puedes consultar su documentación oficial para hacerte una idea más clara.

Supongamos que quieres extraer el título de la página:

const title = $('title').text()
console.log("Título de la página HTML estática:", title)

Deberíamos probar esto, ¿no? Como estás usando TypeScript, tienes que compilar el código, lo que creará el directorio «dist» , y luego ejecutar el archivo «index.js» correspondiente. Para simplificar, definiré el siguiente script en el archivo «package.json »:

"scripts": {
    "test": "npx tsc && node dist/index.js",
}

De esta forma, lo único que tengo que hacer es ejecutar:

npm ejecutar prueba

y el script se encargará de los dos pasos que acabo de describir.

Vale, pero ¿qué pasa si el selector coincide con más de un elemento HTML? Intentemos extraer el nombre y el valor de las acciones de los elementos que aparecen en la lista sin ordenar:

const itemStocks = {}
$('li').each((index, element) => {
    const name = $(element).find('a').text()
    const stock = $(element).find('p.stock').text()
    itemStocks[name] = stock
})
console.log("All items stock:", itemStocks)

Ahora vuelve a ejecutar el script del acceso directo y el resultado en tu terminal debería ser similar a este:

Static HTML page title: Page Name - Static HTML Example
All items stock: { 'Item 1': '12', 'Item 2': '422', 'Item 3': '5' }

Casos de uso de Cheerio

En definitiva, eso era solo la punta del iceberg. Cheerio también es capaz de analizar documentos XML, extraer el estilo de los elementos HTML e incluso modificar los atributos de los nodos.

Pero, ¿cómo puede ayudar Cheerio en un caso práctico real?

Supongamos que queremos recopilar algunos datos para entrenar un modelo de aprendizaje automático con vistas a un proyecto futuro de mayor envergadura. Lo habitual sería buscar en Google algunos archivos de entrenamiento y descargarlos, o bien utilizar la API del sitio web.

Pero, ¿qué haces cuando no encuentras algunos archivos relevantes o cuando la página web que estás consultando no ofrece una API, tiene una limitación de solicitudes o no proporciona todos los datos que ves en la página?

Pues bien, aquí es donde el web scraping resulta muy útil. Si te interesan otros ejemplos prácticos de aplicación del web scraping, puedes echar un vistazo a este artículo tan bien redactado de nuestro blog.

Volviendo al tema que nos ocupa, por poner un ejemplo, imaginemos que nos encontramos precisamente en esta situación: necesitamos datos, pero no los encontramos por ninguna parte. Recuerda que Cheerio no se encarga de la extracción de HTML, ni de la carga de CSS, ni de la ejecución de JS.

Por lo tanto, en este tutorial, voy a utilizar Puppeteer para acceder al sitio web, extraer el código HTML y guardarlo en un archivo. A continuación, repetiré el proceso de la sección anterior.

Para ser más concreto, quiero recopilar opiniones de los usuarios de Reddit sobre un módulo de batería muy popular y centralizar los datos en un único archivo que luego se utilizará para alimentar un posible modelo de aprendizaje automático. Lo que suceda a continuación también puede variar: análisis de opiniones, estudios de mercado y un largo etcétera.

Página de un hilo antiguo de Reddit con el enlace abreviado de la publicación en la barra lateral

Solicitud del código HTML

Veamos cómo quedaría este caso de uso plasmado en código. En primer lugar, debes instalar el paquete NPM de Puppeteer:

npm install puppeteer

También crearé un nuevo archivo llamado reddit.ts, para mantener el proyecto más organizado, y definiré un nuevo script en el archivo package.json:

"scripts": {
    "test": "npx tsc && node dist/index.js",
    "parse": "npx tsc && node dist/reddit.js"
},

Para obtener el documento HTML, definiré una función con el siguiente aspecto:

import fs from 'fs'
import puppeteer from 'puppeteer'
import * as cheerio from 'cheerio'

async function getContent(url: string): Promise<void> {

    // Open the browser and a new tab
    const browser = await puppeteer.launch()
    const page = await browser.newPage()

    // Navigate to the URL and write the content to file
    await page.goto(url)
    const pageContent = await page.content()
    fs.writeFileSync("reddit.html", pageContent)

    // Close the browser
    await browser.close()
    console.log("Got the HTML. Check the reddit.html file.")
}

Para comprobarlo rápidamente, añade un punto de entrada en tu código y llama a la función:

async function main() {


    const targetURL = 'https://old.reddit.com/r/Drumming/comments/r3tidc/yamaha_ead10/'
    await getContent(targetURL)
}

main()
    .then(() => {console.log("All done!")})
    .catch(e => {console.log("Unexpected error occurred:", e.message)})

El archivo reddit.html debería aparecer en el árbol de tu proyecto, donde se encontrará el documento HTML que queremos.

¿Dónde están mis nodos?

Ahora viene una parte un poco más complicada: tienes que identificar los nodos que nos interesan para nuestro caso de uso. Vuelve a tu navegador (el de verdad) y ve a la URL de destino. Coloca el cursor del ratón sobre la sección de comentarios, haz clic con el botón derecho y selecciona la opción «Inspeccionar».

Se abrirá la pestaña «Herramientas de desarrollo», en la que verás exactamente el mismo documento HTML que guardaste anteriormente en tu ordenador.

Hilo de Reddit abierto junto con las herramientas de desarrollo del navegador, en el que se examina un elemento de comentario en el código HTML

Para extraer solo los comentarios, debes identificar los selectores propios de esta sección de la página. Verás que toda la lista de comentarios se encuentra dentro de un contenedor div con la clase «sitable-nestedlisting ».

Si lo analizamos con más detalle, cada comentario tiene como elemento padre un elemento `form`, con la clase `usertext warn-on-unload`. Además, en la parte inferior, se puede ver que el texto de cada comentario está dividido en varios elementos `p`.

Análisis y almacenamiento de los datos

Veamos cómo funciona esto en el código:

function parseComments(): void {

    // Load the HTML document
    const staticHTML = fs.readFileSync('reddit.html')
    const $ = cheerio.load(staticHTML)

    // Get the comments section
    const commentsSection = $('div.sitetable.nestedlisting')

    // Iterate each comment
    const comments = []


    $(commentsSection).find('form.usertext.warn-on-unload').each((index, comment) => {
        let commentText = ""

          // Iterate each comment section and concatenate them
          $(comment).find('p').each((index, piece) => {
            commentText += $(piece).text() + '\n'
          })

          comments.push(commentText)
    })

    // Write the results to external file
    fs.writeFileSync("comments.json", JSON.stringify({comments}))
}

Muy bien, y ahora actualicemos el punto de entrada con la función que acabamos de definir y veamos cómo funciona todo este código en conjunto:

async function main() {

    const targetURL = 'https://old.reddit.com/r/Drumming/comments/r3tidc/yamaha_ead10/'
    await getContent(targetURL)
    parseComments()
}

main()
    .then(() => {console.log("All done. Check the comments.csv file.")})
    .catch(e => {console.log("Unexpected error occurred:", e.message)})

Ejecuta el código con el script definido anteriormente:

npm run parse

El navegador sin interfaz gráfica tardará entre 5 y 10 segundos en abrirse y acceder a nuestra URL de destino. Si tienes curiosidad, puedes añadir marcas de tiempo al principio y al final de cada una de nuestras funciones para comprobar realmente lo rápido que es Cheerio.

El archivo comments.json debería ser nuestro resultado final:

Editor de código que muestra un archivo comments.json que contiene una matriz con el texto de los comentarios recopilados

Este caso de uso se puede ampliar fácilmente para analizar el número de votos positivos y negativos de cada comentario, o para obtener las respuestas anidadas de los comentarios. Las posibilidades son infinitas.

Para llevar

Gracias por llegar hasta el final de este tutorial. Espero que hayas comprendido lo indispensable que es Cheerio en el proceso de extracción de datos y cómo integrarlo rápidamente en tu próximo proyecto de scraping.

También utilizamos Cheerio en nuestro producto, WebScrapingAPI. Si alguna vez te ves envuelto en los numerosos retos que plantea el web scraping (bloqueos de IP, detección de bots, etc.), te recomendamos que lo pruebes.

Acerca del autor
Raluca Penciuc, desarrolladora full-stack en WebScrapingAPI
Raluca PenciucDesarrollador full-stack

Raluca Penciuc es desarrolladora full stack en WebScrapingAPI, donde se dedica a crear rastreadores, mejorar las técnicas de evasión y buscar formas fiables de reducir la detección en los sitios web de destino.

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.