Extraer tablas de un PDF a Excel es una de las tareas más frecuentes en análisis de datos. Python ofrece tres bibliotecas principales para ello, cada una con sus puntos fuertes según el tipo de PDF.
## Comparativa de herramientas
| Biblioteca | Tipo de PDF | Precisión | Velocidad | Dependencias |
|---|---|---|---|---|
| **pdfplumber** | Texto nativo | Alta | Rápida | pdfminer |
| **tabula-py** | Texto nativo | Alta | Media | Java (JRE) |
| **camelot** | Texto nativo | Muy alta | Lenta | Ghostscript |
| **pytesseract** | Escaneado (OCR) | Media | Muy lenta | Tesseract |
## pdfplumber — extracción rápida y flexible
```python
# pip install pdfplumber openpyxl pandas
import pdfplumber
import pandas as pd
# Extraer todas las tablas de la primera página
with pdfplumber.open('informe.pdf') as pdf:
pagina = pdf.pages[0]
tablas = pagina.extract_tables()
for i, tabla in enumerate(tablas):
df = pd.DataFrame(tabla[1:], columns=tabla[0]) # Primera fila = cabecera
print(f"\n--- Tabla {i+1} ---")
print(df.head())
df.to_excel(f'tabla_{i+1}.xlsx', index=False)
```
## pdfplumber — múltiples páginas
```python
import pdfplumber
import pandas as pd
def extraer_todas_las_tablas(ruta_pdf):
"""Extrae y concatena tablas de todas las páginas del PDF."""
dfs = []
with pdfplumber.open(ruta_pdf) as pdf:
for num_pag, pagina in enumerate(pdf.pages, 1):
tablas = pagina.extract_tables()
for i, tabla in enumerate(tablas):
if not tabla or not tabla[0]:
continue
df = pd.DataFrame(tabla[1:], columns=tabla[0])
df['_pagina'] = num_pag
df['_tabla_num'] = i + 1
dfs.append(df)
print(f"Página {num_pag}, tabla {i+1}: {len(df)} filas")
return pd.concat(dfs, ignore_index=True) if dfs else pd.DataFrame()
df_total = extraer_todas_las_tablas('informe.pdf')
# Exportar a Excel con múltiples hojas por página
with pd.ExcelWriter('tablas_completas.xlsx', engine='openpyxl') as writer:
df_total.to_excel(writer, sheet_name='Todas', index=False)
print(f"Total filas extraídas: {len(df_total)}")
```
## tabula-py — extracción con precisión de área
tabula-py envuelve Tabula (Java) y es excelente para tablas con bordes bien definidos:
```python
# pip install tabula-py
# Requiere Java Runtime Environment (JRE) instalado
import tabula
import pandas as pd
# Extraer todas las tablas del PDF
lista_dfs = tabula.read_pdf('informe.pdf', pages='all', multiple_tables=True)
print(f"Tablas encontradas: {len(lista_dfs)}")
for i, df in enumerate(lista_dfs):
print(f"\n--- Tabla {i+1} ({len(df)} filas x {len(df.columns)} cols) ---")
print(df.head(3))
# Exportar la primera tabla a Excel
lista_dfs[0].to_excel('primera_tabla.xlsx', index=False)
# Exportar directamente a Excel (sin pasar por DataFrame)
tabula.convert_into('informe.pdf', 'tablas.xlsx', output_format='xlsx', pages='all')
# Extraer por área específica (en puntos: top, left, bottom, right)
df_area = tabula.read_pdf(
'informe.pdf',
pages=2,
area=[100, 50, 400, 550], # coordenadas de la región
multiple_tables=False,
)[0]
```
## camelot — máxima precisión para tablas complejas
camelot usa Ghostscript y es el más preciso para tablas con bordes explícitos:
```python
# pip install camelot-py[cv] ghostscript
import camelot
import pandas as pd
# Modo 'lattice' para tablas con bordes (líneas)
tablas = camelot.read_pdf('informe.pdf', flavor='lattice', pages='all')
print(f"Tablas encontradas: {tablas.n}")
print(tablas[0].parsing_report)
# {'accuracy': 99.02, 'whitespace': 12.24, 'order': 1, 'page': 1}
# Modo 'stream' para tablas sin bordes (solo espaciado)
tablas_stream = camelot.read_pdf('informe.pdf', flavor='stream', pages='1,2,3')
# Exportar todas a Excel (una hoja por tabla)
tablas.export('resultado.xlsx', f='excel', compress=False)
# O construir un Excel personalizado
with pd.ExcelWriter('resultado_custom.xlsx', engine='openpyxl') as writer:
for i, tabla in enumerate(tablas):
df = tabla.df
df.to_excel(writer, sheet_name=f'Tabla_{i+1}', index=False)
```
## PDFs escaneados — OCR con pytesseract
Para PDFs que son imágenes escaneadas (sin texto seleccionable):
```python
# pip install pytesseract pdf2image pillow
# Requiere Tesseract instalado: apt install tesseract-ocr tesseract-ocr-spa
import pytesseract
from pdf2image import convert_from_path
from PIL import Image
import pandas as pd
import re
def ocr_pdf_a_texto(ruta_pdf, idioma='spa'):
"""Convierte PDF escaneado a texto con OCR."""
imagenes = convert_from_path(ruta_pdf, dpi=300)
texto_total = ''
for i, img in enumerate(imagenes):
texto = pytesseract.image_to_string(img, lang=idioma)
texto_total += f'\n--- Página {i+1} ---\n' + texto
return texto_total
# Para tablas en PDFs escaneados, pytesseract ofrece tsv/data estructurado
def ocr_tabla_escaneada(imagen_path):
img = Image.open(imagen_path)
# Obtener datos de tabla estructurados
datos = pytesseract.image_to_data(img, output_type=pytesseract.Output.DATAFRAME)
# Filtrar solo el texto reconocido con confianza > 60
texto_confiable = datos[datos['conf'] > 60][['text', 'left', 'top', 'width', 'height']]
return texto_confiable
```
## Limpiar y exportar a Excel final
```python
import pandas as pd
import re
def limpiar_dataframe(df):
"""Limpieza estándar tras extracción de PDF."""
# Eliminar filas completamente vacías
df = df.dropna(how='all')
# Limpiar saltos de línea dentro de celdas
df = df.apply(lambda col: col.map(
lambda x: re.sub(r'\s+', ' ', str(x)).strip() if pd.notna(x) else x
))
# Eliminar filas duplicadas
df = df.drop_duplicates()
# Resetear índice
df = df.reset_index(drop=True)
return df
# Pipeline completo
import pdfplumber
with pdfplumber.open('datos.pdf') as pdf:
tablas_raw = []
for pagina in pdf.pages:
for t in pagina.extract_tables():
if t and len(t) > 1:
df = pd.DataFrame(t[1:], columns=t[0])
tablas_raw.append(df)
tablas_limpias = [limpiar_dataframe(df) for df in tablas_raw]
# Exportar a Excel con formato
with pd.ExcelWriter('resultado_final.xlsx', engine='openpyxl') as writer:
for i, df in enumerate(tablas_limpias):
nombre_hoja = f'Tabla_{i+1}'[:31] # Excel limita nombres a 31 chars
df.to_excel(writer, sheet_name=nombre_hoja, index=False)
# Ajustar ancho de columnas automáticamente
hoja = writer.sheets[nombre_hoja]
for col in hoja.columns:
max_len = max(len(str(c.value or '')) for c in col)
hoja.column_dimensions[col[0].column_letter].width = min(max_len + 2, 50)
print(f"Exportado: {len(tablas_limpias)} tablas a resultado_final.xlsx")
```
## Elegir la herramienta correcta
- **pdfplumber**: primera opción para la mayoría de PDFs nativos. Rápido y sin dependencias pesadas.
- **tabula-py**: ideal cuando las tablas tienen bordes explícitos y necesitas especificar áreas.
- **camelot**: para PDFs corporativos complejos donde la precisión es crítica.
- **pytesseract + pdf2image**: imprescindible para PDFs escaneados o fotografías de documentos.
Guía