Introducción a las tablas HTML y su extracción
Las tablas HTML son una estructura común para organizar datos en sitios web, como informes financieros o listados de productos. Para utilizar estos datos, es necesario extraerlos del diseño web. Esta guía explica cómo lograrlo con Python, cubriendo desde tablas estáticas simples hasta escenarios dinámicos complejos.
Estructura de tablas HTML
Antes de extraer datos, comprenda la estructura básica de las tablas. Los elementos clave son:
<table>: Contenedor principal de la tabla.<tr>: Define una fila.<th>: Celda de encabezado.<td>: Celda de datos.
Estos elementos están anidados: <table> contiene <tr>, que a su vez contienen <th> y <td>. Esta jerarquía es crucial para seleccionar los datos con precisión.
Requisitos previos
Instale las siguientes bibliotecas de Python:
- Requests para descargar páginas web.
- BeautifulSoup4 para parsear HTML.
- Pandas para estructurar datos y exportarlos.
- lxml como analizador para pandas.
- Selenium para sitios que requieren JavaScript.
Use el siguiente comando para la instalación:
pip install requests beautifulsoup4 pandas selenium lxml
Extracción de tablas estáticas
Las tablas estáticas contienen todos los datos en el HTML inicial sin necesidad de JavaScript. Existen dos métodos principales.
Método 1: Uso de pandas.read_html
Para tablas simples, la función read_html() de pandas automatiza la extracción. Ejemplo:
import pandas as pd
import urllib.request
from io import StringIO
pagina = "https://es.wikipedia.org/wiki/Anexo:Países_por_población"
encabezado_usuario = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
# Obtener contenido HTML
solicitud = urllib.request.Request(pagina, headers=encabezado_usuario)
respuesta = urllib.request.urlopen(solicitud)
contenido = respuesta.read().decode('utf-8')
# Extraer todas las tablas del HTML
lista_tablas = pd.read_html(StringIO(contenido))
print(f"Tablas encontradas: {len(lista_tablas)}")
# Seleccionar la primera tabla
datos = lista_tablas[0]
print(datos.head())
# Guardar en CSV
datos.to_csv("poblacion_datos.csv", index=False)
Método 2: Uso manual con BeautifulSoup
Cuando se necesita más control, BeautifulSoup ofrece flexibilidad. Siga estos pasos:
from bs4 import BeautifulSoup
import pandas as pd
import urllib.request
pagina = "https://es.wikipedia.org/wiki/Anexo:Países_por_población"
encabezado_usuario = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
# Paso 1: Descargar página
solicitud = urllib.request.Request(pagina, headers=encabezado_usuario)
respuesta = urllib.request.urlopen(solicitud)
contenido_html = respuesta.read().decode('utf-8')
# Paso 2: Parsear HTML
sopa = BeautifulSoup(contenido_html, 'html.parser')
# Paso 3: Localizar tabla (usando atributo de clase)
tabla = sopa.find('table', attrs={'class': 'wikitable'})
if tabla:
# Extraer encabezados
encabezados = [th.text.strip() for th in tabla.find_all('th')]
# Extraer filas
filas_datos = []
for fila in tabla.find('tbody').find_all('tr'):
celdas = fila.find_all('td')
if celdas:
datos_fila = [celda.text.strip() for celda in celdas]
filas_datos.append(datos_fila)
# Paso 4: Crear DataFrame
df = pd.DataFrame(filas_datos, columns=encabezados)
print(df.head())
# Paso 5: Exportar
df.to_csv('datos_extraidos.csv', index=False)
else:
print("No se encontró la tabla.")
Manejo de tablas con celdas combinadas
Algunas tablas usan colspan o rowspan para fusionar celdas. Primero, intente con pandas.read_html(), que suele resolver esto automáticamente. Si falla, use bibliotecas especializadas como html-table-extractor.
Instale la biblioteca:
pip install html-table-extractor
<p>Ejemplo de uso con BeautifulSoup:</p>
<code>import requests
from bs4 import BeautifulSoup
from html_table_extractor.extractor import Extractor
import csv
pagina = "https://es.wikipedia.org/wiki/Comparación_de_navegadores_web"
encabezado_usuario = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
# Obtener y parsear página
respuesta = requests.get(pagina, headers=encabezado_usuario)
sopa = BeautifulSoup(respuesta.text, 'html.parser')
# Buscar tabla específica
tablas = sopa.find_all("table")
for indice, tabla in enumerate(tablas):
extractor = Extractor(str(tabla))
extractor.parse()
datos_tabla = extractor.return_list()
if datos_tabla and "Navegador" in datos_tabla[0]:
# Guardar en CSV
with open("navegadores.csv", "w", newline="", encoding="utf-8") as archivo:
escritor = csv.writer(archivo)
escritor.writerows(datos_tabla)
break</code>
<h3>Limpieza y exportación de datos</h3>
<p>Los datos extraídos a menudo necesitan limpieza. Por ejemplo, convertir cadenas numéricas a valores reales:</p>
<code># Supongamos que 'df' es el DataFrame obtenido
df['Población_Limpia'] = df['Población'].str.replace('.', '', regex=False)
df['Población_Limpia'] = pd.to_numeric(df['Población_Limpia'])
# Exportar a diferentes formatos
df.to_csv('datos_limpios.csv', index=False)
df.to_json('datos_limpios.json', orient='records')</code>
<h3>Extracción de tablas dinámicas (JavaScript)</h3>
<p>Cuando los datos se cargan con JavaScript, <code>requests</code> no es suficiente. Dos soluciones:</p>
<ol>
<li><strong>API oculta</strong>: Use las herramientas de desarrollador del navegador (pestaña Network) para interceptar solicitudes JSON y replicarlas en Python.</li>
<li><strong>Navegador sin cabeza</strong>: Herramientas como Selenium automatizan un navegador real para renderizar JavaScript.</li>
</ol>
<p>Ejemplo con Selenium para una tabla dinámica:</p>
<code>from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
import pandas as pd
from io import StringIO
import time
pagina = "https://datatables.net/examples/data_sources/ajax.html"
opciones = Options()
opciones.add_argument("--headless")
controlador = webdriver.Chrome(options=opciones)
controlador.get(pagina)
time.sleep(3) # Esperar carga inicial
lista_datos = []
while True:
tabla_html = controlador.find_element(By.ID, "example").get_attribute("outerHTML")
df = pd.read_html(StringIO(tabla_html))[0]
lista_datos.append(df)
# Verificar botón "Siguiente"
try:
boton = controlador.find_element(By.CSS_SELECTOR, "button.next")
if "disabled" in boton.get_attribute("class"):
break
boton.click()
time.sleep(2)
except:
break
controlador.quit()
df_final = pd.concat(lista_datos, ignore_index=True)
df_final.to_csv("datos_dinamicos.csv", index=False)</code>
<h3>Extracción de tablas paginadas</h3>
<p>Para tablas en múltiples páginas, identifique si la paginación es estática o dinámica.</p>
- Paginación estática: Cambios en la URL (ej:
?pagina=2). Use un bucle conrequests. - Paginación dinámica: La URL no cambia. Use Selenium para interactuar con botones.
Ejemplo de paginación estática:
import requests
import pandas as pd
from io import StringIO
import time
datos_acumulados = []
pagina_actual = 1
encabezado_usuario = {"User-Agent": "Mozilla/5.0"}
while True:
url = f"https://ejemplo.com/datos?pagina={pagina_actual}"
respuesta = requests.get(url, headers=encabezado_usuario)
if respuesta.status_code != 200:
break
tablas = pd.read_html(StringIO(respuesta.text))
if tablas:
datos_acumulados.append(tablas[0])
pagina_actual += 1
time.sleep(2)
df_total = pd.concat(datos_acumulados, ignore_index=True)
df_total.to_csv("datos_paginados.csv", index=False)
Consideraciones éticas y mejores prácticas
Al rastrear sitios web, respete las normas:
- Consulte el archivo
robots.txtdel sitio. - Cumpla con los términos de servicio.
- Use proxies para rotar direcciones IP.
- Incluya un User-Agent realista.
- Añada retrasos entre solicitudes.
Solución de problemas comunes
Error frecuente: AttributeError: 'NoneType' object has no attribute 'find'. Esto ocurre cuando un selector no encuentra elementos. Verifique que la tabla exista en el HTML y que el seelctor sea correcto.