PDF es un formato pensado para la impresión y la visualización, no para el almacenamiento de datos. Precisamente por eso es el formato del que más difícil resulta extraer información de forma fiable entre todos los de nuestro panorama del parseo de documentos. En PDF llegan facturas, contratos, especificaciones, listas de precios, extractos bancarios y documentos de la administración — y casi siempre hay que convertirlos en datos estructurados.
La gran bifurcación: texto o imagen
Lo primero que determina toda la estrategia es cómo está almacenado el texto dentro del archivo. En un PDF «digital» (generado desde Word, desde una maquetación o desde el navegador) el texto está guardado como caracteres seleccionables — se puede extraer directamente. En un PDF escaneado, la página es una imagen: sin reconocimiento óptico (OCR) no contiene caracteres y la extracción devolverá el vacío. Comprobarlo es sencillo: si en el visor el texto se puede seleccionar con el ratón, es digital; si no, hace falta OCR. Los dos casos exigen herramientas completamente distintas, así que conviene empezar siempre por esta comprobación.
Extracción de texto de PDF digitales
Python
Para extraer texto y, sobre todo, tablas, la mejor opción es pdfplumber. Entiende las coordenadas de los caracteres y las líneas, y por eso sabe reconstruir la estructura tabular.
import pdfplumber
with pdfplumber.open("invoice.pdf") as pdf:
page = pdf.pages[0]
text = page.extract_text()
print(text)
for table in page.extract_tables():
for row in table:
print(row) # row es la lista de celdas de la filaCuando solo se necesita el texto y prima la velocidad, se recurre a PyMuPDF (se importa como fitz) — es rapidísimo y respeta bien la disposición del texto.
import fitz # PyMuPDF
doc = fitz.open("contract.pdf")
for page in doc:
print(page.get_text())Para tareas simples, como extraer el texto página a página o trabajar con los metadatos, sirve pypdf (el sucesor del obsoleto PyPDF2).
from pypdf import PdfReader
reader = PdfReader("report.pdf")
for page in reader.pages:
print(page.extract_text())JavaScript / Node.js
En Node, la vía rápida para obtener todo el texto del documento es pdf-parse.
const fs = require("fs");
const pdf = require("pdf-parse");
const buffer = fs.readFileSync("invoice.pdf");
pdf(buffer).then((data) => {
console.log(data.text); // todo el texto
console.log(data.numpages);
});Cuando hace falta control de bajo nivel o trabajar en el navegador, se usa pdf.js de Mozilla — da acceso a los fragmentos de texto individuales con sus coordenadas.
Java
En los proyectos corporativos está muy extendido Apache PDFBox. Tenga en cuenta el cambio de API entre versiones: en PDFBox 3.x el documento se abre con Loader.loadPDF(...), mientras que en 2.x era PDDocument.load(...).
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import java.io.File;
try (PDDocument doc = Loader.loadPDF(new File("report.pdf"))) {
String text = new PDFTextStripper().getText(doc);
System.out.println(text);
}Las tablas: un dolor aparte
En PDF no existe el concepto de «tabla»: lo que una persona ve como una tabla es en realidad un conjunto de bloques de texto y líneas con coordenadas. Por eso la extracción de tablas es siempre una heurística. pdfplumber las reconstruye a partir de las líneas de la rejilla y funciona bien cuando la tabla está trazada. Para tablas complejas sin líneas, en Python se emplean Camelot (método basado en la disposición) o tabula-py (un envoltorio del motor Java Tabula). Si las tablas son muchas y homogéneas, lo habitual es elegir una herramienta y calibrarla para ese diseño concreto.
A menudo lo más práctico es volcar el resultado directamente a una tabla — a partir de ahí, el procesamiento continúa en Excel o CSV, como con cualquier fuente tabular.
Escaneados y OCR
Si el texto no se deja seleccionar, antes de extraerlo hay que reconocer la página. La pila clásica en Python: renderizar las páginas como imágenes (con el propio PyMuPDF) y reconocerlas con el motor Tesseract a través del envoltorio pytesseract.
import fitz
import pytesseract
from PIL import Image
import io
doc = fitz.open("scan.pdf")
for page in doc:
pix = page.get_pixmap(dpi=300) # renderizamos la página como imagen
img = Image.open(io.BytesIO(pix.tobytes()))
text = pytesseract.image_to_string(img, lang="spa")
print(text)La calidad del OCR depende mucho de la resolución (use 300 DPI como mínimo), de la limpieza del escaneado y del idioma. Para documentos en español, indique siempre el paquete de idioma spa. No espere del OCR una precisión del cien por cien: el resultado casi siempre exige posprocesamiento y revisión.
Cuándo es mejor no parsear el PDF directamente
A veces resulta que el documento existe también en otro formato: el mismo extracto está disponible como exportación CSV o una API entrega los datos en JSON. Eso es casi siempre más fiable que extraer una tabla de la versión para imprimir, así que compruebe la fuente antes de lanzarse sobre el PDF.
PDF es un formato en el que la solución «universal» no existe: una factura, un contrato y un escaneado exigen herramientas distintas. Si recibe un flujo regular de documentos homogéneos — facturas, albaranes, especificaciones —, configuraremos la extracción de datos de sus PDF para ese diseño concreto, tablas y OCR de escaneados incluidos, con la carga del resultado en el sistema que necesite.