Saber cómo extraer datos de tablas HTML con JavaScript puede ser una habilidad fundamental a la hora de trabajar con datos tabulares que se muestran en la web. Los sitios web suelen mostrar información importante, como detalles de productos, precios, niveles de inventario y datos financieros, en tablas. Ser capaz de extraer esta información resulta extremadamente útil a la hora de recopilar datos para todo tipo de tareas de análisis. En este artículo, profundizaremos en las tablas HTML y crearemos un programa sencillo pero potente para extraer datos de ellas y exportarlos a un archivo CSV o JSON. Utilizaremos Node.js y cheerio para que el proceso sea muy sencillo.
Comprender la estructura de una tabla HTML
Las tablas HTML son una herramienta potente para marcar datos tabulares estructurados y mostrarlos de una forma que resulte fácil de leer y comprender para los usuarios. Las tablas se componen de datos organizados en filas y columnas, y el HTML proporciona varios elementos diferentes para definir y estructurar estos datos. Una tabla debe incluir al menos los siguientes elementos: <table>, <tr> (fila de la tabla) y <td> (datos de la tabla). Para añadir estructura y valor semántico, las tablas también pueden incluir el elemento <th> (encabezado de tabla), así como los elementos <thead>, <tbody> y <tfoot>.
Exploremos las etiquetas con la ayuda de un pequeño ejemplo.
Fíjate en cómo la segunda tabla utiliza una sintaxis más específica
La etiqueta <thead> aplicará una fuente en negrita a las celdas «Fruit» y «Color» de la segunda tabla. Aparte de eso, se puede ver cómo ambas sintaxis logran la misma organización de los datos.
Al extraer tablas de la web, es importante tener en cuenta que puedes encontrarte con tablas escritas con distintos grados de especificidad semántica. En otras palabras, algunas tablas pueden incluir etiquetas HTML más detalladas y descriptivas, mientras que otras pueden utilizar una sintaxis más simple y menos descriptiva.
Extracción de tablas HTML con Node.js y cheerio
¡Bienvenido a la parte divertida! Hemos aprendido sobre la estructura y la finalidad de las tablas HTML, y ahora es el momento de poner en práctica esos conocimientos con un ejercicio práctico. Nuestro objetivo para este tutorial es la tabla de los artistas más vendidos de todos los tiempos que se encuentra en https://chartmasters.org/best-selling-artists-of-all-time/. Empezaremos por configurar nuestro entorno de trabajo e instalar las bibliotecas necesarias. A continuación, exploraremos el sitio web de nuestro objetivo y crearemos algunos selectores para extraer los datos que se encuentran en la tabla. Después, escribiremos el código para extraer los datos y, por último, los exportaremos a diferentes formatos, como CSV y JSON.
Configuración del entorno de trabajo
¡Muy bien, empecemos con nuestro nuevo proyecto! Antes de empezar, asegúrate de tener instalado Node.js. Puedes descargarlo desde https://nodejs.org/en/.
Ahora abre tu editor de código favorito, abre el directorio de tu proyecto y ejecuta (en la terminal):
npm init -y
Esto inicializará un nuevo proyecto y creará un archivo package.json predeterminado.
{
"name": "html_table_scraper", // the name of your project folder
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}Para simplificar las cosas, importaremos nuestros módulos con «require». Pero si quieres importar módulos utilizando la instrucción «import», añade esto a tu archivo package.json:
"type": "module",
// this will enable you to use the import statement// ex: import * as cheerio from 'cheerio';
La opción «main»: «index.js» especifica el nombre del archivo que es el punto de entrada de nuestro programa. Dicho esto, ya puedes crear un archivo index.js vacío.
Usaremos la biblioteca cheerio para analizar el HTML de nuestro sitio web de destino. Puedes instalarla con:
npm install cheerio
Ahora abre el archivo index.js e inclúyelo como módulo:
const cheerio = require('cheerio');
El entorno de trabajo básico ya está configurado. En el siguiente capítulo, exploraremos la estructura de la tabla de los artistas más vendidos de todos los tiempos.
Probar el sitio web de destino con DevTools
Al inspeccionar la pestaña «Elementos» de las Herramientas de desarrollo, podemos extraer información valiosa sobre la estructura de la tabla:
La tabla está almacenada en formato <thead>, <tbody>.
A todos los elementos de la tabla se les asignan identificadores descriptivos, clases y roles.
En el contexto de un navegador, tienes acceso directo al HTML de una página web. Esto significa que puedes utilizar funciones de JavaScript como getElementsByTagName o querySelector para extraer datos del HTML.
Teniendo esto en cuenta, podemos utilizar la consola de las Herramientas de desarrollo para probar algunos selectores.
Extraigamos los nombres de los encabezados utilizando el atributo role="columnheader"
Ahora extraigamos los datos de la primera fila utilizando los atributos role="cell" y role="row":
Como puedes ver:
Podemos utilizar «[role=columnheader]» para seleccionar todos los elementos de encabezado.
Podemos usar «tbody [role=row]» para seleccionar todos los elementos de fila.
Para cada fila, podemos usar «[role=cell]» para seleccionar sus celdas.
Hay que tener en cuenta que la celda PIC contiene una imagen y debemos escribir una regla especial para extraer su URL.
Implementación de un rastreador de tablas HTML
Ahora es el momento de avanzar un poco más utilizando Node.js y cheerio.
Para obtener el HTML de un sitio web en un proyecto de Node.js, tendrás que realizar una solicitud fetch al sitio. Esto devuelve el HTML como una cadena, lo que significa que no puedes usar funciones DOM de JavaScript para extraer datos. Ahí es donde entra en juego cheerio. Cheerio es una biblioteca que te permite analizar y manipular la cadena HTML como si estuvieras en el contexto de un navegador. Esto significa que puedes utilizar selectores CSS habituales para extraer datos del HTML, tal y como lo harías en un navegador.
También es importante tener en cuenta que el HTML devuelto por una solicitud fetch puede ser diferente del HTML que ves en el navegador. Esto se debe a que el navegador ejecuta JavaScript, lo que puede modificar el HTML que se muestra. En una solicitud fetch, solo obtienes el HTML sin procesar, sin modificaciones de JavaScript.
Solo con fines de prueba, hagamos una solicitud fetch a nuestro sitio web de destino y escribamos el HTML resultante en un archivo local:
//index.js
const fs = require('fs');
(async () => {
const response = await fetch('https://chartmasters.org/best-selling-artists-of-all-time/');
const raw_html = await response.text();
fs.writeFileSync('raw-best-selling-artists-of-all-time.html', raw_html);
})();
// it will write the raw html to raw-best-selling-artists-of-all-time.html
// try it with other websites: https://randomwordgenerator.com/Puedes ejecutarlo con:
node index.js
Y obtendrás:
La estructura de la tabla sigue siendo la misma. Esto significa que los selectores que encontramos en el capítulo anterior siguen siendo válidos.
Bien, ahora continuemos con el código propiamente dicho:
Después de realizar la solicitud de recuperación a https://chartmasters.org/best-selling-artists-of-all-time/, tendrás que cargar el HTML sin procesar en cheerio:
const cheerio = require('cheerio');
(async () => {
const response = await fetch('https://chartmasters.org/best-selling-artists-of-all-time/');
const raw_html = await response.text();
const $ = cheerio.load(raw_html);
})();Con cheerio cargado, veamos cómo podemos extraer los encabezados:
const headers = $("[role=columnheader]")
const header_names = []
headers.each((index, el) => {
header_names.push($(el).text())
}) //header_names
[
'#',
'PIC',
'Artist',
'Total CSPC',
'Studio Albums Sales',
'Other LPs Sales',
'Physical Singles Sales',
'Digital Singles Sales',
'Sales Update',
'Streams EAS (Update)'
]Y la primera fila:
const first_row = $("tbody [role=row]")[0]
const first_row_cells = $(first_row).find('[role=cell]')
const first_row_data = []
first_row_cells.each((index, f_r_c) => {
const image = $(f_r_c).find('img').attr('src')
if(image) {
first_row_data.push(image)
}
else {
first_row_data.push($(f_r_c).text())
}
}) //first_row_data
[
'1',
'https://i.scdn.co/image/ab6761610000f178e9348cc01ff5d55971b22433',
'The Beatles',
'421,300,000',
'160,650,000',
'203,392,000',
'116,080,000',
'35,230,000',
'03/01/17',
'17,150,000 (01/03/23)'
]¿Recuerdas cuando extraímos la tabla HTML con JavaScript en la consola de Herramientas de desarrollo del navegador? En este punto, hemos replicado la misma funcionalidad que implementamos allí, pero en el contexto del proyecto Node.js. Puedes repasar el último capítulo y observar las numerosas similitudes entre ambas implementaciones.
Continuando, reescribamos el código para extraer todas las filas:
const rows = $("tbody [role=row]")
const rows_data = []
rows.each((index, row) => {
const row_cell_data = []
const cells = $(row).find('[role=cell]')
cells.each((index, cell) => {
const image = $(cell).find('img').attr('src')
if(image) {
row_cell_data.push(image)
}
else {
row_cell_data.push($(cell).text())
}
}) rows_data.push(row_cell_data)
})
//rows_data
[
[
'1',
'https://i.scdn.co/image/ab6761610000f178e9348cc01ff5d55971b22433',
'The Beatles',
'421,300,000',
'160,650,000',
'203,392,000',
'116,080,000',
'35,230,000',
'03/01/17',
'17,150,000 (01/03/23)'
],
[
'2',
'https://i.scdn.co/image/ab6761610000f178a2a0b9e3448c1e702de9dc90',
'Michael Jackson',
'336,084,000',
'182,600,000',
'101,997,000',
'79,350,000',
'79,930,000',
'09/27/17',
'15,692,000 (01/06/23)'
],
...
]Ahora que hemos obtenido los datos, veamos cómo podemos exportarlos.
Exportación de los datos
Una vez que hayas obtenido con éxito los datos que deseas extraer, es importante considerar cómo quieres almacenar la información. Las opciones más populares son .json y .csv. Elige el formato que mejor se adapte a tus necesidades y requisitos específicos.
Exportación de los datos a json
Si quieres exportar los datos a .json, primero debes agrupar los datos en un objeto JavaScript que se asemeje al formato .json.
Tenemos una matriz de nombres de encabezados (header_names) y otra matriz (rows_data, una matriz de matrices) que contiene los datos de las filas. El formato .json almacena la información en pares clave-valor. Necesitamos agrupar nuestros datos de tal manera que sigan esa regla:
[ // this is what we need to obtain
{
'#': '1',
PIC: 'https://i.scdn.co/image/ab6761610000f178e9348cc01ff5d55971b22433',
Artist: 'The Beatles',
'Total CSPC': '421,300,000',
'Studio Albums Sales': '160,650,000',
'Other LPs Sales': '203,392,000',
'Physical Singles Sales': '116,080,000',
'Digital Singles Sales': '35,230,000',
'Sales Update': '03/01/17',
'Streams EAS (Update)': '17,150,000 (01/03/23)'
},
{
'#': '2',
PIC: 'https://i.scdn.co/image/ab6761610000f178a2a0b9e3448c1e702de9dc90',
Artist: 'Michael Jackson',
'Total CSPC': '336,084,000',
'Studio Albums Sales': '182,600,000',
'Other LPs Sales': '101,997,000',
'Physical Singles Sales': '79,350,000',
'Digital Singles Sales': '79,930,000',
'Sales Update': '09/27/17',
'Streams EAS (Update)': '15,692,000 (01/06/23)'
}
...
]Así es como puedes lograrlo:
// go through each row
const table_data = rows_data.map(row => {
// create a new object
const obj = {};
// forEach element in header_names
header_names.forEach((header_name, i) => {
// add a key-value pair to the object where the key is the current header name and the value is the value at the same index in the row
obj[header_name] = row[i];
});
// return the object
return obj;
});Ahora puedes utilizar la función JSON.stringify() para convertir el objeto JavaScript en una cadena .json y, a continuación, escribirla en un archivo.
const fs = require('fs');
const table_data_json_string = JSON.stringify(table_data, null, 2)
fs.writeFile('table_data.json', table_data_json_string, (err) => {
if (err) throw err;
console.log('The file has been saved as table_data.json!');
})Exportar los datos a CSV
El formato .csv significa «valores separados por comas». Si quieres guardar tu tabla en formato .csv, tendrás que escribirla en un formato similar a este:
id,name,age // the table headers followed by the rows
1,Alice,20
2,Bob,253,Charlie,30
Los datos de nuestra tabla consisten en una matriz de nombres de encabezados (header_names) y otra matriz (rows_data, una matriz de matrices) que contiene los datos de las filas. Así es como puedes escribir estos datos en un archivo .csv:
let csv_string = header_names.join(',') + '\n'; // add the headers
// forEach row in rows_data
rows_data.forEach(row => {
// add the row to the CSV string
csv_string += row.join(',') + '\n';
});
// write the string to a file
fs.writeFile('table_data.csv', csv_string, (err) => {
if (err) throw err;
console.log('The file has been saved as table_data.csv!');
});Evita que te bloqueen
¿Alguna vez te has encontrado con el problema de intentar extraer datos de un sitio web y darte cuenta de que la página de la que intentas extraer información no se carga por completo? Esto puede resultar frustrante, especialmente si sabes que el sitio web utiliza JavaScript para generar su contenido. No tenemos la capacidad de ejecutar JavaScript como lo hace un navegador normal, lo que puede dar lugar a datos incompletos o incluso a que te bloqueen el acceso al sitio web por realizar demasiadas solicitudes en un breve periodo de tiempo.
Una solución a este problema es WebScrapingApi. Con nuestro servicio, solo tienes que realizar solicitudes a nuestra API y esta se encargará de todas las tareas complejas por ti. Ejecutará JavaScript, rotará proxies e incluso gestionará los CAPTCHAs.
A continuación te mostramos cómo puedes realizar una sencilla solicitud de recuperación a una <target_url> y escribir la respuesta en un archivo:
const fs = require('fs');
(async () => {
const result = await fetch('https://api.webscrapingapi.com/v1?' + new URLSearchParams({
api_key: '<api_key>',
url: '<target_url>',
render_js: 1,
proxy_type: 'residential',
}))
const html = await result.text();
fs.writeFileSync('wsa_test.html', html);
})();Puedes obtener una API_KEY gratuita creando una nueva cuenta en https://www.webscrapingapi.com/
Al especificar el parámetro render_js=1, habilitarás la capacidad de WebScrapingAPI para acceder a la página web de destino utilizando un navegador sin interfaz gráfica, lo que permite que los elementos JavaScript de la página se carguen antes de enviarte el resultado final del scraping.
Echa un vistazo a https://docs.webscrapingapi.com/webscrapingapi/advanced-api-features/proxies para descubrir las capacidades de nuestros proxies rotativos.
Conclusión
En este artículo, hemos aprendido sobre el potencial del web scraping de tablas HTML con JavaScript y cómo puede ayudarnos a extraer datos valiosos de sitios web. Hemos explorado la estructura de las tablas HTML y hemos aprendido a utilizar la biblioteca cheerio en combinación con Node.js para extraer datos de ellas fácilmente. También hemos visto diferentes formas de exportar los datos, incluyendo los formatos CSV y JSON. Siguiendo los pasos descritos en este artículo, ahora deberías tener una base sólida para extraer datos de tablas HTML en cualquier sitio web.
Tanto si eres un profesional experimentado como si acabas de empezar con tu primer proyecto de scraping, WebScrapingAPI está aquí para ayudarte en cada paso del camino. Nuestro equipo estará encantado de responder a cualquier pregunta que puedas tener y ofrecerte orientación sobre tus proyectos. Así que, si alguna vez te sientes atascado o simplemente necesitas una mano amiga, no dudes en ponerte en contacto con nosotros.




