Extraer texto de PDF en Python
Extraer texto de un PDF es una de las tareas más habituales en cualquier pipeline de procesamiento de documentos: construir índices de búsqueda, alimentar sistemas RAG, hacer minería de datos o responder a flujos de cumplimiento. Esta guía te muestra todo lo necesario para extraer texto de PDFs en Python, JavaScript y Rust con PDF Oxide, desde el texto plano hasta el posicionamiento carácter a carácter, los spans con estilo, el OCR para documentos escaneados, el manejo de archivos cifrados y el ajuste fino de rendimiento para pipelines en lote.
Extrae texto de cualquier PDF en tres líneas:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("document.pdf")
text = doc.extract_text(0) # pagina 0
print(text)
WASM
import { WasmPdfDocument } from "pdf-oxide-wasm";
const bytes = new Uint8Array(buffer);
const doc = new WasmPdfDocument(bytes);
const text = doc.extractText(0); // pagina 0
console.log(text);
doc.free();
Rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("document.pdf")?;
let text = doc.extract_text(0)?;
println!("{}", text);
Go
package main
import (
"fmt"
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
doc, err := pdfoxide.Open("document.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()
text, err := doc.ExtractText(0) // pagina 0
if err != nil { log.Fatal(err) }
fmt.Println(text)
}
C#
using PdfOxide;
using var doc = PdfDocument.Open("document.pdf");
var text = doc.ExtractText(0); // pagina 0
Console.WriteLine(text);
PDF Oxide extrae texto con una media de 0,8 ms por página, 5 veces más rápido que PyMuPDF y 15 veces más rápido que pypdf, con una tasa de éxito del 100 % sobre 3830 PDFs de prueba.
Por qué extraer texto de PDF es difícil
El PDF es un formato visual, no un formato de texto. A diferencia de HTML o Markdown, un archivo PDF no guarda “párrafos” ni “oraciones”: guarda caracteres individuales ubicados en coordenadas concretas dentro de una página. Convertirlos en texto legible requiere varios pasos:
- Decodificar fuentes — las fuentes PDF mapean códigos de carácter a glifos mediante tablas de codificación (WinAnsi, MacRoman, CMaps Unicode, Type 1, TrueType, CIDFont). El código
0x41puede significar “A” en una fuente y “α” en otra. - Parsear flujos de texto — operadores como
Tj,TJ,'y"colocan caracteres en la página. Los ajustes de kerning en los arraysTJdesplazan los caracteres fracciones de punto, y los espacios ausentes hay que inferirlos a partir de los huecos entre posiciones. - Reconstruir el layout — los caracteres no traen un orden de lectura explícito. Los layouts a dos columnas, las cabeceras, los pies de página, las tablas y las barras laterales exigen un análisis espacial para producir un flujo lineal de texto.
- Casos límite de codificación — el texto CJK (chino, japonés, coreano) usa codificaciones CIDFont/CMap con miles de glifos. El árabe y el hebreo requieren reordenamiento de derecha a izquierda. Las ligaduras (fi, fl, ffi) deben descomponerse.
- Subconjuntos incrustados — muchos PDFs incrustan sólo los glifos que realmente usan, con vectores de codificación propios. Una fuente puede mapear el glifo 1→“T”, 2→“h”, 3→“e” sin ninguna codificación estándar.
Por todo esto, distintas bibliotecas de PDF producen salidas de texto distintas para el mismo archivo y algunas fallan por completo con documentos complejos. PDF Oxide cubre todos estos casos con un parser en Rust probado sobre 3830 PDFs reales con una tasa de éxito del 100 %.
Instalación
Python (PyPI):
pip install pdf_oxide
Wheels precompilados para Linux (x86_64, aarch64), macOS (Intel y Apple Silicon) y Windows (x86_64). Python 3.8 o superior. Sin dependencias del sistema: el núcleo en Rust se compila dentro del wheel, así que no necesitas instalar Poppler, MuPDF ni ninguna biblioteca de C.
JavaScript (npm):
npm install pdf-oxide-wasm
Funciona en Node.js 18 o superior y en los navegadores modernos. El binario WASM viene incluido en el paquete.
Rust (Cargo):
cargo add pdf_oxide
Requiere Rust 1.70 o superior. No pide más dependencias que una toolchain estándar de Rust.
Extraer todas las páginas
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
full_text = []
for i in range(doc.page_count()):
text = doc.extract_text(i)
full_text.append(text)
print("\n".join(full_text))
WASM
const doc = new WasmPdfDocument(bytes);
const fullText = doc.extractAllText();
console.log(fullText);
doc.free();
Rust
let mut doc = PdfDocument::open("report.pdf")?;
let mut full_text = Vec::new();
for i in 0..doc.page_count()? {
full_text.push(doc.extract_text(i)?);
}
println!("{}", full_text.join("\n"));
Go
doc, err := pdfoxide.Open("report.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()
full, err := doc.ExtractAllText()
if err != nil { log.Fatal(err) }
fmt.Println(full)
C#
using var doc = PdfDocument.Open("report.pdf");
var parts = new List<string>();
for (int i = 0; i < doc.PageCount; i++)
parts.Add(doc.ExtractText(i));
Console.WriteLine(string.Join("\n", parts));
Extraer texto con posiciones de carácter
Obtén coordenadas exactas, nombres de fuente y tamaños para cada carácter:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
chars = doc.extract_chars(0)
for ch in chars[:20]:
print(f"'{ch.char}' at ({ch.x:.1f}, {ch.y:.1f}) "
f"font={ch.font_name} size={ch.font_size:.1f}")
WASM
const doc = new WasmPdfDocument(bytes);
const chars = doc.extractChars(0);
for (const ch of chars.slice(0, 20)) {
console.log(`'${ch.char}' at (${ch.x.toFixed(1)}, ${ch.y.toFixed(1)}) font=${ch.fontName} size=${ch.fontSize.toFixed(1)}`);
}
doc.free();
Rust
let mut doc = PdfDocument::open("paper.pdf")?;
let chars = doc.extract_chars(0)?;
for ch in chars.iter().take(20) {
println!("'{}' at ({:.1}, {:.1}) font={} size={:.1}",
ch.char, ch.x, ch.y, ch.font_name, ch.font_size);
}
Go
doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()
chars, _ := doc.ExtractChars(0)
for _, ch := range chars[:20] {
fmt.Printf("%q at (%.1f, %.1f) font=%s size=%.1f\n",
ch.Char, ch.X, ch.Y, ch.FontName, ch.FontSize)
}
C#
using var doc = PdfDocument.Open("paper.pdf");
var chars = doc.ExtractChars(0);
foreach (var ch in chars.Take(20))
Console.WriteLine($"'{ch.Char}' at ({ch.X:F1}, {ch.Y:F1}) font={ch.FontName} size={ch.FontSize:F1}");
Cada carácter incluye:
| Campo | Tipo | Descripción |
|---|---|---|
char |
str |
El carácter Unicode |
x, y |
float |
Posición en puntos |
font_size |
float |
Tamaño de fuente en puntos |
font_name |
str |
Nombre PostScript de la fuente |
bbox |
tuple |
Caja envolvente (x0, y0, x1, y1) |
La extracción a nivel de carácter resulta útil para reconstruir tablas, detectar encabezados por tamaño de fuente o construir cajas envolventes alrededor de regiones de texto. Por ejemplo, puedes agrupar los caracteres en líneas según la coordenada y y detectar los límites de columna por los huecos en las posiciones x.
Extraer spans de texto con estilo
Agrupa caracteres consecutivos por fuente y tamaño:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
spans = doc.extract_spans(0)
for span in spans:
print(f"'{span.text}' font={span.font_name} size={span.font_size:.1f}")
WASM
const doc = new WasmPdfDocument(bytes);
const spans = doc.extractSpans(0);
for (const span of spans) {
console.log(`'${span.text}' font=${span.fontName} size=${span.fontSize.toFixed(1)}`);
}
doc.free();
Rust
let mut doc = PdfDocument::open("paper.pdf")?;
let spans = doc.extract_spans(0)?;
for span in &spans {
println!("'{}' font={} size={:.1}", span.text, span.font_name, span.font_size);
}
Viene bien para detectar encabezados, texto en negrita o generar salidas estructuradas.
Procesamiento por lotes
Procesa cientos o miles de PDFs de una sola vez:
from pdf_oxide import PdfDocument, PdfError
from pathlib import Path
pdf_dir = Path("documents/")
for pdf_path in pdf_dir.glob("*.pdf"):
try:
doc = PdfDocument(str(pdf_path))
for i in range(doc.page_count()):
text = doc.extract_text(i)
# Procesa el texto...
except PdfError as e:
print(f"Omitido {pdf_path.name}: {e}")
A 0,8 ms por página, procesar 3830 PDFs toma unos 3,1 segundos. Para pipelines en producción, consulta la guía de procesamiento por lotes con patrones de paralelización usando multiprocessing y E/S asíncrona.
Manejo de PDFs escaneados (OCR)
Si un PDF contiene imágenes escaneadas en lugar de texto, extract_text() devuelve una cadena vacía o casi vacía. En ese caso usa el OCR que trae PDF Oxide:
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned.pdf")
text = doc.extract_text(0)
if not text.strip():
# Probablemente la pagina esta escaneada: usa OCR
text = doc.extract_text_ocr(0)
print(text)
PDF Oxide emplea PaddleOCR vía ONNX Runtime, sin necesidad de instalar Tesseract. Revisa la guía de OCR para elegir y configurar el modelo.
Manejo de PDFs cifrados
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("protected.pdf", password="secret")
text = doc.extract_text(0)
print(text)
WASM
const doc = new WasmPdfDocument(bytes);
doc.authenticate("secret");
const text = doc.extractText(0);
console.log(text);
doc.free();
Rust
let mut doc = PdfDocument::open_with_password("protected.pdf", "secret")?;
let text = doc.extract_text(0)?;
println!("{}", text);
Go
doc, _ := pdfoxide.Open("protected.pdf")
defer doc.Close()
if _, err := doc.Authenticate("secret"); err != nil { log.Fatal(err) }
text, _ := doc.ExtractText(0)
fmt.Println(text)
C#
using var doc = PdfDocument.OpenWithPassword("protected.pdf", "secret");
Console.WriteLine(doc.ExtractText(0));
Admite PDFs cifrados con AES-256, AES-128 y RC4. A diferencia de pdfplumber (que no puede abrir archivos cifrados) y pdfminer (que falla con AES-256), PDF Oxide maneja de forma transparente todos los métodos de cifrado estándar de PDF.
Salida como Markdown
Para obtener una salida estructurada con encabezados y formato:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)
WASM
const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
console.log(md);
doc.free();
Rust
let mut doc = PdfDocument::open("paper.pdf")?;
let md = doc.to_markdown(0, true)?;
println!("{}", md);
Go
doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()
md, _ := doc.ToMarkdown(0)
fmt.Println(md)
C#
using var doc = PdfDocument.Open("paper.pdf");
Console.WriteLine(doc.ToMarkdown(0));
Revisa la guía de PDF a Markdown con patrones de integración para RAG y LLM.
Búsqueda dentro de PDFs
Encuentra texto en todas las páginas con datos de posición:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("manual.pdf")
results = doc.search("configuration")
for r in results:
print(f"Page {r.page}: '{r.text}' at ({r.x:.0f}, {r.y:.0f})")
WASM
const doc = new WasmPdfDocument(bytes);
const results = doc.search("configuration", false);
for (const r of results) {
console.log(`Page ${r.page}: '${r.text}' at (${r.x.toFixed(0)}, ${r.y.toFixed(0)})`);
}
doc.free();
Rust
let mut pdf = Pdf::open("manual.pdf")?;
let results = pdf.search("configuration")?;
for r in &results {
println!("Page {}: '{}' at ({:.0}, {:.0})", r.page, r.text, r.bbox.x, r.bbox.y);
}
Go
doc, _ := pdfoxide.Open("manual.pdf")
defer doc.Close()
results, _ := doc.SearchAll("configuration", false)
for _, r := range results {
fmt.Printf("Page %d: %q at (%.0f, %.0f)\n", r.PageIndex, r.Text, r.X, r.Y)
}
C#
using var doc = PdfDocument.Open("manual.pdf");
foreach (var r in doc.SearchAll("configuration", caseSensitive: false))
Console.WriteLine($"Page {r.PageIndex}: '{r.Text}' at ({r.X:F0}, {r.Y:F0})");
Comparación con otras bibliotecas de PDF en Python
Existen varias bibliotecas de Python para extraer texto de PDFs. Así se comparan:
- pypdf — Python puro, sin dependencias de C. Fácil de instalar, pero lento (12 ms por página) y falla en un 1,6 % de los PDFs por una compatibilidad limitada con fuentes y codificaciones. No aporta datos de posición. Sirve para PDFs sencillos donde la velocidad no importa.
- pdfplumber — construido sobre pdfminer, ofrece extracción detallada de caracteres y tablas. Muy lento (23 ms por página) y no puede abrir PDFs cifrados. Es buena opción para tablas cuando necesitas datos por celda y no te preocupa el rendimiento.
- PyMuPDF (fitz) — bindings de Python a la biblioteca C MuPDF. Rápido (4,6 ms por página) y fiable (tasa de éxito del 99,3 %). Requiere instalar una biblioteca C y está bajo licencia AGPL. Una opción sólida si la licencia encaja con tu proyecto.
- pypdfium2 — bindings de Python al motor PDFium de Google. Rápido (4,1 ms por página), aunque la latencia p99 es alta (42 ms) con documentos complejos. Superficie de API limitada frente a PyMuPDF.
- pdfminer.six — Python puro con análisis detallado de layout. Muy lento y sin mantenimiento. Falla con PDFs cifrados con AES-256. En gran medida ha sido sustituido por pdfplumber.
- PDF Oxide — núcleo en Rust con bindings de Python vía PyO3. La opción más rápida (0,8 ms por página), tasa de éxito del 100 %, soporta todos los métodos de cifrado e incluye OCR integrado. Licencia MIT, sin dependencias del sistema.
PDF Oxide se construyó precisamente para tapar los huecos de las bibliotecas existentes: las limitaciones de rendimiento de los parsers en Python puro, las restricciones de licencia de MuPDF y los problemas de fiabilidad que hacen fallar a otras bibliotecas frente a PDFs reales con fuentes atípicas, tablas de referencias cruzadas rotas o codificaciones no estándar.
Rendimiento: ¿qué tan rápido es PDF Oxide?
Medido sobre 3830 PDFs provenientes de tres suites públicas e independientes:
| Biblioteca | Media | p99 | Tasa de éxito |
|---|---|---|---|
| PDF Oxide | 0,8 ms | 9 ms | 100 % |
| PyMuPDF | 4,6 ms | 28 ms | 99,3 % |
| pypdfium2 | 4,1 ms | 42 ms | 99,2 % |
| pypdf | 12,1 ms | 97 ms | 98,4 % |
| pdfplumber | 23,2 ms | 189 ms | 98,8 % |
Para un pipeline que procese 10 000 PDFs:
- PDF Oxide: 8 segundos
- PyMuPDF: 46 segundos
- pypdf: 2 minutos
- pdfplumber: 3,9 minutos
Consulta los benchmarks completos con la metodología y los pasos para reproducirlos.
Problemas comunes y soluciones
Salida de texto vacía
Si extract_text() devuelve una cadena vacía, es muy probable que la página contenga imágenes escaneadas en lugar de texto. Usa en su lugar extract_text_ocr(). En OCR para PDFs escaneados tienes las instrucciones de configuración.
Caracteres extraños o incorrectos
Suele deberse a una fuente con un vector de codificación no estándar o sin CMap ToUnicode. PDF Oxide maneja la mayoría de estos casos, pero algunos PDFs deliberadamente ofuscados (contenido con DRM) pueden producir una salida incorrecta.
Faltan espacios o palabras pegadas
Los operadores de texto en PDF colocan los caracteres uno a uno. La inferencia de espacios depende de la distancia entre posiciones respecto al ancho del espacio de la fuente. Si ves palabras pegadas, prueba con extract_chars() y aplica tu propia lógica de separación en función de las coordenadas.
La salida difiere de otras bibliotecas
Cada biblioteca aplica heurísticas distintas para inferir espacios, saltos de línea y orden de lectura. PDF Oxide alcanza un 99,5 % de paridad textual con PyMuPDF sobre 3830 PDFs. Ese 0,5 % restante se concentra en la normalización de espacios y el tratamiento de ligaduras.
Casos de uso reales
Indexación para búsqueda — Extrae el texto de cada página de cada PDF de un repositorio documental y alimenta ese texto en Elasticsearch, Typesense o una base de datos vectorial para búsqueda de texto completo. La velocidad de PDF Oxide hace práctico reindexar miles de documentos bajo demanda.
Pipelines RAG (generación aumentada por recuperación) — Extrae y trocea el texto de los PDFs para generar embeddings con OpenAI, Cohere o modelos open source. Usa extract_spans() para preservar la estructura de los encabezados y alinear los chunks con las secciones del documento. Para una salida optimizada para LLMs, consulta la guía de PDF a Markdown.
Cumplimiento y auditoría — Escanea contratos, facturas y presentaciones regulatorias buscando cláusulas o palabras clave. Usa doc.search() para localizar términos en todas las páginas con posiciones exactas, o extrae el texto completo para una detección de cláusulas basada en NLP.
Extracción de datos — Obtén datos estructurados de facturas, recibos, extractos bancarios y formularios. Combina extract_chars() con reglas propias del dominio para localizar campos como “Monto total” o “Fecha de la factura” y extraer los valores adyacentes.
Investigación académica — Procesa miles de artículos para revisiones bibliográficas, extracción de citas o metaanálisis. PDF Oxide cubre todo el abanico de productores de PDF (LaTeX, Word, InDesign, Quark) y las codificaciones de fuente típicas de las publicaciones académicas.
Páginas relacionadas
- API de extracción de texto — referencia completa de la API
- PDF a Markdown — conversión estructurada
- Procesamiento por lotes — patrones de paralelización
- OCR de PDFs escaneados — configuración y uso de OCR
- Benchmarks de rendimiento — metodología y resultados