StackPractices
beginner Por Mathias Paulenko

Extraer Texto de PDFs en Python, JavaScript y Java

Extrae texto, tablas y metadatos de archivos PDF con Python, JavaScript y Java, incluidos archivos cifrados y documentos escaneados con OCR.

Temas: data

Visión General

Las facturas, contratos e informes siguen llegando en PDF, y no hay una API para consultarlos: tienes que extraer el texto por tu cuenta. PDF es un formato de descripción de página, no de datos: guarda glifos en coordenadas absolutas, así que la extracción nunca es tan limpia como analizar JSON o CSV. Esta receta muestra las librerías que de verdad funcionan para texto, tablas y metadatos en Python, JavaScript y Java, más los dos casos que rompen el parsing ingenuo: archivos cifrados y documentos escaneados.

Cuándo Usar

Usa este enfoque cuando:

  • Sacar facturas, recibos o formularios de los adjuntos en PDF.
  • Indexar un corpus de PDFs para búsqueda de texto completo.
  • Sacar las tablas de informes financieros o artículos de investigación.
  • Leer autor y fecha de creación de cada documento para auditarlos o deduplicarlos.

No lo uses cuando los PDFs son escaneos puros sin capa de texto: esos necesitan OCR primero (ver PDFs escaneados y cifrados). Y si el trabajo es crear PDFs en lugar de leerlos, la receta que quieres es Generar Reportes PDF en Python.

Solución

Todos los snippets de abajo funcionan tal cual. Una versión ejecutable está en el repositorio complementario, con un script que genera un PDF de muestra para practicar.

Python

pypdf (el sucesor mantenido de PyPDF2) cubre extracción de texto y metadatos:

# pip install pypdf
from pypdf import PdfReader

reader = PdfReader("document.pdf")
print(f"Páginas: {len(reader.pages)}")
for page in reader.pages:
    print(page.extract_text())

Los metadatos viven en el diccionario de información del documento, no en el contenido de las páginas:

reader = PdfReader("document.pdf")
meta = reader.metadata
print(meta.title, meta.author, meta.creation_date)

Para tablas, pdfplumber detecta líneas de separación y alineación del texto:

# pip install pdfplumber
import pdfplumber

with pdfplumber.open("document.pdf") as pdf:
    for page in pdf.pages:
        for table in page.extract_tables():
            for row in table:
                print(row)

JavaScript

pdf-parse envuelve PDF.js de Mozilla y devuelve texto más el número de páginas:

// npm install pdf-parse
import pdfParse from "pdf-parse/lib/pdf-parse.js";
import fs from "node:fs";

const dataBuffer = fs.readFileSync("document.pdf");
const data = await pdfParse(dataBuffer);
console.log(data.text);
console.log(`Páginas: ${data.numpages}`);

pdf-lib lee y edita metadatos y campos de formulario:

// npm install pdf-lib
import { PDFDocument } from "pdf-lib";
import fs from "node:fs";

const pdfDoc = await PDFDocument.load(fs.readFileSync("document.pdf"));
console.log(`Páginas: ${pdfDoc.getPageCount()}`);
console.log(pdfDoc.getTitle(), pdfDoc.getAuthor());

Java

Apache PDFBox es el estándar en proyectos JVM:

// Maven: org.apache.pdfbox:pdfbox
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;

try (PDDocument doc = PDDocument.load(new java.io.File("document.pdf"))) {
    PDFTextStripper stripper = new PDFTextStripper();
    System.out.println(stripper.getText(doc));
    System.out.println("Páginas: " + doc.getNumberOfPages());
}

Explicación

Una página PDF es una lista de instrucciones de dibujo: “pon el glifo X en la coordenada (72, 540)”. No hay garantía de que el flujo de contenido siga el orden visual de lectura: un artículo a dos columnas suele extraerse entrelazado, línea a línea entre ambas columnas. Un archivo que se renderiza perfecto puede seguir soltando texto desordenado.

Las librerías difieren sobre todo en cuánto análisis de layout hacen sobre los objetos de texto crudos. pypdf concatena bloques de texto en el orden del contenido, suficiente para documentos de una columna. Como pdfplumber se apoya en pdfminer.six, conserva las coordenadas de cada carácter, suficiente para reconstruir palabras, líneas y tablas. pdf-parse delega en PDF.js, el mismo motor que usa Firefox para renderizar PDFs en el navegador. PDFBox ofrece PDFTextStripper para salida plana y PDFTextStripperByArea cuando ya sabes qué región de la página contiene los datos.

Una vez que tienes texto plano o filas fuera del PDF, el trabajo posterior es parsing de datos normal: por ejemplo, alimentar las filas extraídas por el mismo pipeline de Analizar Archivos CSV o normalizar campos con Serializar y Deserializar Datos.

Variantes

TecnologíaLibreríaMejor paraNotas
PythonpypdfTexto, metadatos, unir/dividirSucesor del deprecado PyPDF2; Python puro
PythonpdfplumberTablas y extracción con layoutBasado en pdfminer.six; más lento pero preciso
PythonpymupdfVelocidad, imágenes, anotacionesMotor MuPDF en C; también renderiza páginas
PythonpikepdfArchivos cifrados o dañadosEnvuelve qpdf; repara y descifra PDFs
JavaScriptpdf-parseExtracción rápida de texto en NodeWrapper ligero sobre PDF.js
JavaScriptpdf-libMetadatos, formularios, modificaciónFunciona en Node y en el navegador
JavaApache PDFBoxPipelines enterprise en JVMAcceso de bajo nivel; es la base de Tabula

Elige pdfplumber cuando las tablas son el objetivo, pymupdf cuando importa el rendimiento, y pypdf para todo lo demás. Si el pipeline tiene que descifrar o reparar archivos primero, pon pikepdf delante.

Elegir la Librería Correcta

flowchart diagram: PDF de entrada

PDFs Escaneados y Cifrados

Los PDFs escaneados guardan cada página como imagen, así que extract_text() devuelve cadenas vacías aunque una persona vea el texto. Detecta este caso comprobando si la extracción solo produce espacios en blanco y después ejecuta OCR. La vía más fácil es ocrmypdf, que añade una capa de texto buscable al propio archivo:

# Añade una capa de texto oculta; el resultado se analiza como un PDF normal
ocrmypdf entrada-escaneado.pdf salida-busqueda.pdf

En Python, pdf2image junto con pytesseract te da control página a página; en la nube, AWS Textract, Google Document AI y Azure Document Intelligence manejan formularios y escritura a mano mejor que Tesseract en crudo.

Los PDFs cifrados lanzan errores o devuelven cadenas vacías hasta que les pasas la contraseña. Comprueba reader.is_encrypted y llama a reader.decrypt(password) con pypdf; para contraseñas de propietario o archivos dañados, pikepdf es más tolerante:

# pip install pikepdf
import pikepdf

with pikepdf.open("bloqueado.pdf", password="secreto") as pdf:
    pdf.save("desbloqueado.pdf")  # luego se analiza con pypdf o pdfplumber

Mejores Prácticas

  • Haz un muestreo de la calidad de extracción en unas pocas páginas antes de procesar miles de archivos; la precisión varía mucho según la herramienta que generó el PDF.
  • Sea cual sea el lenguaje, mantén los readers dentro de bloques with o try-with-resources para que los handles de archivo y los buffers se liberen de verdad. Los parsers de PDF son codiciosos con ambos.
  • Para trabajos por lotes, usa concurrent.futures.ProcessPoolExecutor: la extracción de PDF está limitada por CPU, así que los hilos no ayudan.
  • pdfminer.six (y por tanto pdfplumber) puede quedarse colgado con archivos malformados; en producción, ejecútalo en un subproceso con timeout.
  • Guarda el texto extraído junto a un hash del archivo fuente para reprocesar solo lo que cambió.

Errores Comunes

  • Analizar un PDF escaneado y no obtener nada: si extract_text() devuelve vacío casi siempre significa páginas de solo imagen; envíalas a OCR.
  • Asumir que el orden visual es el orden de extracción: los layouts de varias columnas mezclan líneas de ambas. Si el orden importa, usa pdfplumber con layout=True o extrae regiones por bounding box.
  • La trampa de debug de pdf-parse: importar pdf-parse directamente (en lugar de pdf-parse/lib/pdf-parse.js) puede disparar su bloque de test y lanzar ENOENT sobre test/data/05-versions-space.pdf. No es un error tuyo: viene de cómo está empaquetado pdf-parse 1.x.
  • Ignorar el cifrado: llamar a extract_text() sobre un archivo cifrado lanza errores o no devuelve nada; comprueba is_encrypted primero.
  • Tratar la salida del OCR como exacta: el OCR confunde 0/O, 1/l, celdas fusionadas y símbolos de moneda, así que los importes e identificadores necesitan una validación de formato antes de que los consuma cualquier sistema.

Solución de Problemas

  • Texto vacío pero el PDF se ve bien: probablemente el archivo es solo imágenes. Pásale pdffonts (viene con poppler); si no lista ninguna fuente, no hay capa de texto que leer.
  • Caracteres corruptos o tildes perdidas: el PDF usa una codificación de fuente personalizada sin mapa ToUnicode; prueba pymupdf o OCR en su lugar.
  • Columnas fusionadas en una sola línea: pasa la extracción a pdfplumber y ajusta sus table_settings, o recorta la región que te interesa con PDFTextStripperByArea en PDFBox.
  • El parser se cuelga con un archivo: envuelve la llamada en un subproceso con timeout y mueve el archivo a una carpeta de cuarentena para revisión manual.

Ver También

  • Documentación de pypdf: texto, metadatos, cifrado y unión de archivos.
  • pdfplumber en GitHub: extracción a nivel de carácter y detección de tablas.
  • Apache PDFBox: el toolkit estándar de la JVM para trabajar con PDFs.
  • PDF.js: el motor PDF de Mozilla en el navegador, base de pdf-parse.
  • ocrmypdf: deja los PDFs escaneados buscables añadiendo una capa de texto sin cambiar su aspecto.

Preguntas frecuentes

¿Cómo instalo un parser de PDF en Python?

Ejecuta pip install pypdf para texto y metadatos, o pip install pdfplumber cuando necesites tablas. Evita PyPDF2: pasó a llamarse pypdf en 2022 y el paquete antiguo ya no recibe actualizaciones.

¿Cuál es la diferencia entre pypdf y pdfplumber?

pypdf extrae texto crudo rápido y maneja metadatos, unión y cifrado básico. pdfplumber analiza las posiciones de los caracteres, así que puede encontrar bordes de tabla y reconstruir filas: úsalo para facturas e informes, aceptando que es más lento.

¿Puedo analizar PDFs en el navegador?

Sí. PDF.js, el motor de Mozilla, corre entero en el cliente y saca el texto página a página; con pdf-lib además puedes leer metadatos y editar formularios sin salir del navegador. Descarga los archivos grandes a un Web Worker para no bloquear el hilo de la interfaz.

¿Por qué el texto extraído sale vacío?

El PDF es casi seguro un escaneo: páginas guardadas como imágenes sin capa de texto. Compruébalo con pdffonts (sin fuentes = sin texto) y pasa OCR con ocrmypdf o un servicio en la nube como Textract antes de extraer.

¿Cómo extraigo tablas de un PDF?

Usa pdfplumber en Python (page.extract_tables()) o camelot para tablas con líneas de rejilla. En Java, los algoritmos de extracción de Tabula corren sobre PDFBox. Para tablas sin bordes, ajusta table_settings con estrategias verticales explícitas como "text".

¿Puedo rellenar o leer campos de formulario PDF?

Sí. pypdf expone los campos AcroForm con reader.get_fields(), y pdf-lib permite leer y rellenar formularios en Node y en el navegador. Los formularios XFA (comunes en PDFs de administraciones públicas) necesitan aplanarse primero; pdftk o PDFBox pueden hacerlo.