Skip to content

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 0x41 puede 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 arrays TJ desplazan 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