Extraer tablas de PDF en Python
Extraer tablas de documentos PDF es una de las tareas más habituales en las canalizaciones de procesamiento documental. Ya sea que extraigas datos financieros de informes anuales, armes catálogos de productos o alimentes un LLM con datos estructurados, una detección fiable de tablas resulta indispensable. Esta guía cubre todo lo que necesitas saber para hacerlo en Python: desde líneas rápidas hasta flujos listos para producción con tablas que abarcan varias páginas.
Motor de detección
PDF Oxide utiliza la canalización universal de detección de tablas bordes → ajuste/unión → intersecciones → celdas → grupos — el mismo enfoque que Tabula, pdfplumber y PyMuPDF, implementado en Rust puro.
Capacidades de detección:
- Basada en intersecciones — busca cruces de líneas H×V, construye celdas a partir de rectángulos de cuatro esquinas y las agrupa en tablas mediante union-find.
- Cuadrícula extendida — cuando las líneas horizontales y verticales ocupan regiones distintas de la página, se arma una cuadrícula virtual con el producto cartesiano de todas las coordenadas.
- Detección de texto con conciencia de columnas — segmenta las maquetas de dos columnas con un histograma de proyección en X y luego aplica una detección de tablas solo por texto en cada columna.
- Tablas de texto delimitadas por H-rules — detecta tablas acotadas por líneas horizontales sin líneas verticales (frecuentes en artículos académicos).
- Detección híbrida de filas — infiere los límites de fila a partir de las posiciones Y del texto cuando solo existen bordes verticales (líneas de factura).
- Reconstrucción de líneas punteadas o discontinuas — une segmentos cortos en bordes continuos.
- División por separadores de sección — parte formularios con varias secciones en los divisores horizontales de ancho completo.
- Filtro por cobertura de bordes — descarta bordes huérfanos que no participan en ninguna cuadrícula potencial.
Configuración
TableDetectionConfig expone parámetros ajustables:
| Campo | Predeterminado | Descripción |
|---|---|---|
horizontal_strategy |
"lines_strict" |
"lines_strict", "lines", "text" o "explicit" |
vertical_strategy |
"lines_strict" |
Mismo vocabulario |
v_split_gap |
20.0 pt |
Separación entre líneas verticales que dispara la división en tablas distintas (antes de v0.3.20 estaba fijada en 4 pt) |
snap_tolerance |
3.0 pt |
Tolerancia al fusionar bordes cercanos |
text_tolerance |
3.0 pt |
Tolerancia al fusionar líneas de texto |
Cambio de comportamiento
A partir de v0.3.20, la estrategia por defecto de extract_tables() en Python es Both (detecta por líneas y por texto). Las páginas que dependían del antiguo comportamiento solo-texto deben pasar explícitamente horizontal_strategy="text" y vertical_strategy="text".
El binding de Python ya lee correctamente vertical_strategy del diccionario table_settings; antes lo ignoraba en silencio.
Renderizado
Las tablas extraídas se emiten con alineación de columnas rellenada con espacios (en lugar de los caracteres ASCII de trazado de versiones anteriores). Las columnas de monedas y números se alinean a la derecha automáticamente. Los prefijos de numeración de formulario ("1 Apr 11" → "Apr 11") y las celdas decorativas de guiones o guiones bajos ("------") se eliminan durante el renderizado.
Extrae los datos de tabla de un PDF mediante la conversión a Markdown:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)
# La salida incluye tablas en formato GFM:
# | Item | Qty | Price |
# |------|-----|-------|
# | Widget | 10 | $9.99 |
WASM
import { WasmPdfDocument } from "pdf-oxide-wasm";
const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
console.log(md);
// La salida incluye tablas en formato GFM:
// | Item | Qty | Price |
// |------|-----|-------|
// | Widget | 10 | $9.99 |
doc.free();
Rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("invoice.pdf")?;
let md = doc.to_markdown(0, true)?;
println!("{}", md);
Go
package main
import (
"fmt"
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
doc, err := pdfoxide.Open("invoice.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()
md, err := doc.ToMarkdown(0)
if err != nil { log.Fatal(err) }
fmt.Println(md)
}
C#
using PdfOxide;
using var doc = PdfDocument.Open("invoice.pdf");
Console.WriteLine(doc.ToMarkdown(0));
PDF Oxide detecta las disposiciones tabulares mediante análisis espacial de bloques de texto alineados y emite tablas en GitHub Flavored Markdown.
Por qué extraer tablas de PDF cuesta tanto
Si alguna vez copiaste una tabla de un PDF y la pegaste en una hoja de cálculo, ya conoces el resultado: un desastre, la mayoría de las veces. No es un fallo de tu visor de PDF: refleja una limitación fundamental del propio formato PDF.
Los PDFs no tienen un concepto de “tabla”. A diferencia de HTML, que emplea <table>, <tr> y <td> para definir la estructura tabular, un archivo PDF guarda solo instrucciones de dibujo: coloca este glifo en la coordenada (x, y), traza una línea del punto A al punto B. No existe una capa semántica que diga “estos caracteres pertenecen a una celda en la fila 3, columna 2”. Toda biblioteca de extracción debe reconstruir esa estructura analizando la disposición espacial del texto y las líneas en la página.
Esa reconstrucción es difícil por varios motivos:
-
Tablas con y sin bordes. Cuando una tabla tiene líneas de cuadrícula visibles, las herramientas pueden usarlas como límites de celda. Las tablas sin bordes — habituales en estados financieros, informes gubernamentales y artículos académicos — no tienen ninguna línea. La biblioteca debe inferir los límites de columna a partir de los huecos en blanco entre bloques de texto, lo cual es propenso a fallar cuando las columnas son de ancho variable o los valores numéricos se alinean a la derecha.
-
Celdas combinadas y encabezados de varias columnas. Una celda de cabecera que ocupa tres columnas se ve como un único bloque de texto ancho. Sin las líneas de la cuadrícula que la delimiten, un parser no tiene una forma fiable de saber qué columnas cubre el encabezado. Algunas bibliotecas lo gestionan bien; muchas producen salidas incorrectas sin avisar.
-
Contenido de celda en varias líneas. Cuando una celda contiene un párrafo que se envuelve en varias líneas, un parser ingenuo basado en filas trata cada línea envuelta como una fila distinta. Volver a agruparlas en una sola celda exige conocer la extensión vertical de cada fila.
-
Tablas de varias páginas. Las tablas grandes se reparten a menudo en dos o más páginas. El encabezado puede repetirse en cada página o no, y entre las filas pueden aparecer pies de página, marcas de agua o números de página. Unir esos fragmentos en una tabla coherente requiere una lógica consciente de la página.
-
Texto rotado y maquetas fuera de lo común. Algunos PDFs usan texto rotado para los encabezados de columna o colocan tablas en maquetas de página a varias columnas. Estos casos rompen las suposiciones sobre el orden de lectura de izquierda a derecha y de arriba a abajo que hacen la mayoría de los parsers.
Entender estos desafíos ayuda a elegir la herramienta adecuada para tus documentos. Para tablas simples y alineadas — la mayoría de las facturas, confirmaciones de pedido e informes sencillos — un enfoque rápido de análisis espacial como el de PDF Oxide funciona muy bien. Para documentos con fusiones complejas, maquetas sin bordes o formatos inusuales, puede que necesites una biblioteca con heurísticas más sofisticadas.
Extracción de tablas: PDF Oxide frente a otras bibliotecas
Elegir una biblioteca para extraer tablas de PDF en Python depende de tus documentos, de tus requisitos de rendimiento y del formato de salida que necesitas. Así se comparan las principales opciones:
| Biblioteca | Detección de tablas | Tablas con bordes | Tablas sin bordes | Formato de salida | Velocidad |
|---|---|---|---|---|---|
| PDF Oxide | Integrada | Sí | Básica | Markdown/HTML | 0,8 ms |
| pdfplumber | Integrada | Sí | Avanzada | Listas de Python | 23,2 ms |
| Camelot | Integrada | Sí | Sí (lattice/stream) | DataFrames | ~50 ms+ |
| PyMuPDF | Básica (v1.23+) | Sí | Limitada | DataFrames | 4,6 ms |
| pypdf | No | No | No | N/A | N/A |
| tabula-py | Integrada | Sí | Sí | DataFrames | ~100 ms+ (Java) |
PDF Oxide es, con mucho, la opción más rápida. Detecta tablas mediante análisis espacial de bloques de texto alineados y produce tablas limpias en GitHub Flavored Markdown. Con 0,8 ms de tiempo medio de extracción, es 29× más rápido que pdfplumber y más de 100× más rápido que tabula-py. Maneja bien las tablas con bordes y las tablas sin bordes simples y alineadas. En las canalizaciones para LLM donde ya necesitas salida Markdown, es la elección natural.
pdfplumber tiene la detección de tablas sin bordes más madura. Su método find_tables() usa estrategias configurables para detectar filas y columnas según la alineación del texto, y gestiona celdas combinadas y contenido multilínea mejor que la mayoría de alternativas. El contrapunto es la velocidad: 23,2 ms por página lo hacen notablemente más lento en procesamiento por lotes.
Camelot ofrece dos modos — lattice (tablas con bordes) y stream (tablas sin bordes). Produce DataFrames de pandas directamente, lo cual resulta cómodo para flujos de análisis de datos. Sin embargo, depende de Ghostscript y OpenCV, lo que hace la instalación más pesada, y su velocidad es la más baja entre las opciones de Python puro.
PyMuPDF (fitz) añadió una extracción básica de tablas en la versión 1.23. Es rápido (4,6 ms) y funciona bien con tablas simples con bordes, pero su soporte para tablas sin bordes es limitado comparado con pdfplumber o Camelot.
pypdf no dispone de ninguna capacidad de detección de tablas. Extrae texto en crudo, por lo que tendrías que escribir tu propia lógica de parsing para reconstruir la estructura.
tabula-py es un wrapper en Python sobre la biblioteca Tabula en Java. Ofrece buena detección tanto para tablas con como sin bordes, pero requiere un runtime de Java y es la opción más lenta por la sobrecarga de arranque de la JVM. Se presta mejor a extracciones puntuales que a canalizaciones de alto rendimiento.
Para la mayoría de casos de uso en producción, la recomendación es emplear PDF Oxide como extractor principal por velocidad y simplicidad, y recurrir a pdfplumber para ese subconjunto de documentos con maquetas complejas que exigen heurísticas avanzadas.
Instalación
pip install pdf_oxide
Extracción básica de tablas
Como tablas Markdown
El método más sencillo: convertir la página a Markdown, que incluye las tablas con sintaxis GFM:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
for i in range(doc.page_count()):
md = doc.to_markdown(i, detect_headings=True)
if "|" in md: # La página contiene una tabla
print(f"--- Página {i + 1} ---")
print(md)
WASM
const doc = new WasmPdfDocument(bytes);
for (let i = 0; i < doc.pageCount(); i++) {
const md = doc.toMarkdown(i);
if (md.includes("|")) { // La página contiene una tabla
console.log(`--- Página ${i + 1} ---`);
console.log(md);
}
}
doc.free();
Rust
let mut doc = PdfDocument::open("report.pdf")?;
for i in 0..doc.page_count()? {
let md = doc.to_markdown(i, true)?;
if md.contains("|") {
println!("--- Página {} ---", i + 1);
println!("{}", md);
}
}
Go
doc, _ := pdfoxide.Open("report.pdf")
defer doc.Close()
n, _ := doc.PageCount()
for i := 0; i < n; i++ {
md, _ := doc.ToMarkdown(i)
if strings.Contains(md, "|") {
fmt.Printf("--- Página %d ---\n%s\n", i+1, md)
}
}
C#
using var doc = PdfDocument.Open("report.pdf");
for (int i = 0; i < doc.PageCount; i++)
{
var md = doc.ToMarkdown(i);
if (md.Contains("|"))
Console.WriteLine($"--- Página {i + 1} ---\n{md}");
}
Extracción estructurada de tablas (v0.3.34)
Para acceder con tipos a filas y cajas delimitadoras sin parsear Markdown, llama a ExtractTables(pageIndex) (Go, C#) o extract_tables(page) (Python, Rust). Cada tabla expone celdas estructuradas, así puedes pasar los resultados directamente a una base de datos o a un DataFrame sin usar expresiones regulares.
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
for table in doc.extract_tables(0):
for row in table.rows:
print(row)
Rust
let mut doc = PdfDocument::open("invoice.pdf")?;
for table in doc.extract_tables(0)? {
for row in &table.rows {
println!("{:?}", row);
}
}
Go
doc, _ := pdfoxide.Open("invoice.pdf")
defer doc.Close()
tables, _ := doc.ExtractTables(0)
for _, t := range tables {
for _, row := range t.Rows {
fmt.Println(row)
}
}
C#
using var doc = PdfDocument.Open("invoice.pdf");
foreach (var table in doc.ExtractTables(0))
foreach (var row in table.Rows)
Console.WriteLine(string.Join(" | ", row));
Parsear tablas Markdown en filas
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
md = doc.to_markdown(0)
# Extraer las filas de tabla del Markdown
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
header = rows[0] if rows else []
data = rows[1:] if len(rows) > 1 else []
print(f"Columnas: {header}")
for row in data:
print(row)
WASM
const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
const rows = [];
for (const line of md.split("\n")) {
const trimmed = line.trim();
if (trimmed.startsWith("|") && !trimmed.startsWith("|--")) {
const cells = trimmed.split("|").slice(1, -1).map(c => c.trim());
rows.push(cells);
}
}
const header = rows[0] || [];
const data = rows.slice(1);
console.log("Columnas:", header);
data.forEach(row => console.log(row));
doc.free();
Rust
let mut doc = PdfDocument::open("invoice.pdf")?;
let md = doc.to_markdown(0, false)?;
let rows: Vec<Vec<String>> = md.lines()
.map(|l| l.trim())
.filter(|l| l.starts_with('|') && !l.starts_with("|--"))
.map(|l| l.split('|').skip(1).map(|c| c.trim().to_string())
.take_while(|c| !c.is_empty()).collect())
.collect();
if let Some(header) = rows.first() {
println!("Columnas: {:?}", header);
for row in &rows[1..] {
println!("{:?}", row);
}
}
Exportar a CSV
import csv
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
md = doc.to_markdown(0)
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
with open("table.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows)
Exportar a un DataFrame de pandas
import pandas as pd
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
md = doc.to_markdown(0)
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
if rows:
df = pd.DataFrame(rows[1:], columns=rows[0])
print(df)
Usar posiciones de caracteres para parsear tablas a medida
Si necesitas control fino, aprovecha la extracción a nivel de carácter y el análisis espacial:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("financial.pdf")
chars = doc.extract_chars(0)
# Agrupar caracteres por posición Y (filas)
rows = {}
for ch in chars:
row_key = round(ch.y / 2) * 2 # Ajustar a rejilla de 2 pt
rows.setdefault(row_key, []).append(ch)
# Ordenar filas de arriba abajo y caracteres de izquierda a derecha
for y in sorted(rows.keys(), reverse=True):
line_chars = sorted(rows[y], key=lambda c: c.x)
text = "".join(c.char for c in line_chars)
print(text)
WASM
const doc = new WasmPdfDocument(bytes);
const chars = doc.extractChars(0);
// Agrupar caracteres por posición Y (filas)
const rows = new Map();
for (const ch of chars) {
const rowKey = Math.round(ch.y / 2) * 2; // Ajustar a rejilla de 2 pt
if (!rows.has(rowKey)) rows.set(rowKey, []);
rows.get(rowKey).push(ch);
}
// Ordenar filas de arriba abajo y caracteres de izquierda a derecha
const sortedKeys = [...rows.keys()].sort((a, b) => b - a);
for (const y of sortedKeys) {
const lineChars = rows.get(y).sort((a, b) => a.x - b.x);
const text = lineChars.map(c => c.char).join("");
console.log(text);
}
doc.free();
Rust
use std::collections::BTreeMap;
let mut doc = PdfDocument::open("financial.pdf")?;
let chars = doc.extract_chars(0)?;
let mut rows: BTreeMap<i32, Vec<_>> = BTreeMap::new();
for ch in &chars {
let row_key = ((ch.y / 2.0).round() * 2.0) as i32;
rows.entry(row_key).or_default().push(ch);
}
for (_, line_chars) in rows.iter().rev() {
let mut sorted = line_chars.clone();
sorted.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap());
let text: String = sorted.iter().map(|c| c.char).collect();
println!("{}", text);
}
Go
doc, _ := pdfoxide.Open("financial.pdf")
defer doc.Close()
chars, _ := doc.ExtractChars(0)
rows := map[int][]pdfoxide.Char{}
for _, ch := range chars {
key := int(math.Round(float64(ch.Y)/2) * 2)
rows[key] = append(rows[key], ch)
}
keys := make([]int, 0, len(rows))
for k := range rows { keys = append(keys, k) }
sort.Sort(sort.Reverse(sort.IntSlice(keys)))
for _, y := range keys {
line := rows[y]
sort.Slice(line, func(i, j int) bool { return line[i].X < line[j].X })
var b strings.Builder
for _, c := range line { b.WriteString(c.Char) }
fmt.Println(b.String())
}
C#
using var doc = PdfDocument.Open("financial.pdf");
var chars = doc.ExtractChars(0);
var rows = chars
.GroupBy(c => (int)(Math.Round(c.Y / 2) * 2))
.OrderByDescending(g => g.Key);
foreach (var row in rows)
{
var line = string.Concat(row.OrderBy(c => c.X).Select(c => c.Char));
Console.WriteLine(line);
}
Extraer tablas a Markdown
Markdown es el formato de salida ideal cuando alimentas contenido PDF a un modelo de lenguaje grande, armas una canalización RAG o guardas los datos extraídos en un formato legible tanto para personas como para máquinas. PDF Oxide emite tablas nativamente en GitHub Flavored Markdown (GFM), así que no hace falta un paso extra de conversión.
from pdf_oxide import PdfDocument
doc = PdfDocument("quarterly-report.pdf")
# Extraer todas las tablas de todas las páginas como Markdown
all_tables = []
for i in range(doc.page_count()):
md = doc.to_markdown(i, detect_headings=True)
# Dividir el markdown en secciones y detectar los bloques de tabla
in_table = False
current_table = []
for line in md.split("\n"):
if line.strip().startswith("|"):
in_table = True
current_table.append(line)
else:
if in_table and current_table:
all_tables.append("\n".join(current_table))
current_table = []
in_table = False
if current_table:
all_tables.append("\n".join(current_table))
print(f"Se encontraron {len(all_tables)} tablas")
for idx, table in enumerate(all_tables):
print(f"\n--- Tabla {idx + 1} ---")
print(table)
La salida GFM es directamente compatible con los prompts de LLM. Puedes pasarla tal cual a una llamada de la API de OpenAI o Anthropic y el modelo entenderá la estructura tabular sin ningún formateo adicional:
# Pasar la tabla extraída a un LLM para su análisis
prompt = f"""Analiza la siguiente tabla financiera y resume las tendencias principales:
{all_tables[0]}
"""
Este enfoque es mucho más rápido que extraer tablas con pdfplumber y convertirlas a Markdown a mano.
Manejar tablas que abarcan varias páginas
Las tablas que se extienden en varias páginas son un reto clásico en la extracción de PDF. Los estados financieros, los listados de inventario y los expedientes regulatorios suelen contener tablas que ocupan dos, cinco o decenas de páginas. La clave está en extraer la tabla de cada página por separado y luego unir las filas, teniendo cuidado con los encabezados repetidos y los artefactos de página.
from pdf_oxide import PdfDocument
doc = PdfDocument("long-report.pdf")
def extract_table_rows(md_text):
"""Extraer filas de tabla del markdown devolviendo encabezado y datos."""
header = None
data_rows = []
for line in md_text.split("\n"):
line = line.strip()
if not line.startswith("|") or line.startswith("|--"):
continue
cells = [cell.strip() for cell in line.split("|")[1:-1]]
if header is None:
header = cells
else:
data_rows.append(cells)
return header, data_rows
# Acumular filas de todas las páginas
combined_header = None
combined_rows = []
for i in range(doc.page_count()):
md = doc.to_markdown(i)
header, rows = extract_table_rows(md)
if header is None:
continue # Esta página no tiene tabla
if combined_header is None:
combined_header = header
elif header == combined_header:
pass # Saltar encabezado repetido en páginas siguientes
else:
# Tabla distinta: guardar la actual y empezar una nueva
print(f"Tabla con {len(combined_rows)} filas encontrada")
combined_header = header
combined_rows = []
combined_rows.extend(rows)
if combined_header and combined_rows:
print(f"Columnas: {combined_header}")
print(f"Filas totales: {len(combined_rows)}")
for row in combined_rows[:5]:
print(row)
if len(combined_rows) > 5:
print(f"... y {len(combined_rows) - 5} filas más")
Este patrón funciona de forma fiable con tablas cuyo encabezado se repite en cada página (el caso más habitual). Si el encabezado solo aparece en la primera página, puedes simplificar la lógica capturándolo únicamente de la primera página con tabla y tratando el resto como datos.
Exportar tablas a CSV o DataFrame
Una vez extraídos los datos, muchas veces los necesitas en un formato estructurado para analizarlos. Los ejemplos siguientes muestran cómo pasar de un PDF a un DataFrame de pandas o a un CSV en apenas unas líneas.
Exportación por lotes: todas las tablas a archivos CSV separados
import csv
from pdf_oxide import PdfDocument
doc = PdfDocument("catalog.pdf")
table_count = 0
for i in range(doc.page_count()):
md = doc.to_markdown(i)
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
if len(rows) > 1: # Al menos encabezado y una fila de datos
table_count += 1
filename = f"table_page{i + 1}_{table_count}.csv"
with open(filename, "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows)
print(f"Guardado {filename} ({len(rows) - 1} filas de datos)")
print(f"Se exportaron {table_count} tablas en total")
Tabla de varias páginas a DataFrame
Para tablas que abarcan varias páginas, combina el patrón de unión multipágina con pandas:
import pandas as pd
from pdf_oxide import PdfDocument
doc = PdfDocument("financial-statement.pdf")
header = None
all_rows = []
for i in range(doc.page_count()):
md = doc.to_markdown(i)
for line in md.split("\n"):
line = line.strip()
if not line.startswith("|") or line.startswith("|--"):
continue
cells = [cell.strip() for cell in line.split("|")[1:-1]]
if header is None:
header = cells
elif cells == header:
continue # Saltar encabezado repetido
else:
all_rows.append(cells)
if header and all_rows:
df = pd.DataFrame(all_rows, columns=header)
# Limpiar columnas numéricas
for col in df.columns:
# Intentar convertir las columnas que parezcan numéricas
cleaned = df[col].str.replace(r"[$,%]", "", regex=True).str.strip()
try:
df[col] = pd.to_numeric(cleaned)
except (ValueError, TypeError):
pass # Dejar como cadena
print(df.dtypes)
print(df.head(10))
df.to_csv("financial_data.csv", index=False)
Este flujo te deja un DataFrame limpio con los tipos numéricos correctos, listo para analizar con pandas, graficar con matplotlib o cargar en una base de datos.
Tablas complejas: cuándo conviene pdfplumber
La detección de tablas de PDF Oxide resuelve bien las tablas alineadas estándar. En escenarios complejos — celdas combinadas, encabezados que abarcan varias columnas, tablas sin bordes o contenido multilínea — los algoritmos dedicados de pdfplumber son más robustos:
import pdfplumber
with pdfplumber.open("complex-report.pdf") as pdf:
page = pdf.pages[0]
tables = page.extract_tables()
for table in tables:
for row in table:
print(row)
Cuándo usar cada uno
| Escenario | Recomendado |
|---|---|
| Tablas alineadas simples | PDF Oxide (29× más rápido) |
| Tablas dentro de Markdown de página completa | PDF Oxide |
| Celdas combinadas complejas / encabezados que abarcan varias columnas | pdfplumber |
| Tablas sin bordes | pdfplumber |
| Procesamiento por lotes donde importa la velocidad | PDF Oxide |
Usar ambos juntos
Extracción rápida de texto con PDF Oxide, extracción compleja de tablas con pdfplumber:
from pdf_oxide import PdfDocument
import pdfplumber
# Extracción rápida de texto completo
doc = PdfDocument("report.pdf")
text = doc.extract_text(0)
# Extracción dirigida de tablas para páginas complejas
with pdfplumber.open("report.pdf") as pdf:
tables = pdf.pages[0].extract_tables()
Páginas relacionadas
- Conversión a Markdown — referencia completa de la API de Markdown
- Extracción de texto — texto plano y extracción de caracteres
- PDF Oxide vs pdfplumber — comparación detallada
- PDF a Markdown — guía de conversión a Markdown