Respuesta rápida: instala pypdf, abre documentos con PdfReader, construye el resultado con PdfWriter y escríbelo en un archivo nuevo. La biblioteca une, divide y rota páginas, gestiona metadatos, intenta extraer texto y cifra documentos. No hace OCR ni funciona como editor visual.
python -m pip install pypdf
Un PDF describe dónde se muestran elementos en páginas; no es simplemente un documento de texto. Esa diferencia explica sus posibilidades y sus límites. Conviene empezar con un entorno virtual de Python y aplicar las prácticas de nuestra guía de pathlib en Python. Los detalles de cada versión están en la documentación oficial de pypdf.
Unir PDF en un orden predecible
Entrega una lista explícita en vez de confiar en el orden del sistema de archivos. La función valida entradas, evita sobrescribir una de ellas, crea el directorio de salida y rechaza archivos protegidos que necesitan credenciales.
from pathlib import Path
from pypdf import PdfReader, PdfWriter
def unir_pdfs(entradas: list[Path], salida: Path) -> None:
if not entradas:
raise ValueError("Indica al menos un PDF")
salida = salida.resolve()
writer = PdfWriter()
for ruta in entradas:
ruta = ruta.resolve()
if ruta == salida:
raise ValueError("La salida no puede ser una entrada")
if ruta.suffix.lower() != ".pdf" or not ruta.is_file():
raise ValueError(f"PDF no válido: {ruta}")
reader = PdfReader(ruta)
if reader.is_encrypted:
raise ValueError(f"PDF protegido: {ruta.name}")
for pagina in reader.pages:
writer.add_page(pagina)
salida.parent.mkdir(parents=True, exist_ok=True)
with salida.open("wb") as archivo:
writer.write(archivo)
unir_pdfs(
[Path("contrato.pdf"), Path("anexo.pdf")],
Path("salida/documento-completo.pdf"),
)
Para lotes, genera la lista mediante una regla como sorted(carpeta.glob("*.pdf")) y registra los nombres. No conviertas un nombre no confiable directamente en ruta de salida. Esta etapa encaja en un flujo más amplio de automatización de tareas con Python.
Dividir páginas y rotar contenido
Dividir implica crear un escritor para cada página o intervalo. Los índices de Python empiezan en cero, aunque los nombres para usuarios suelen empezar en uno. La rotación usa múltiplos de 90 grados.
from pathlib import Path
from pypdf import PdfReader, PdfWriter
def dividir_pdf(origen: Path, carpeta: Path) -> list[Path]:
reader = PdfReader(origen)
carpeta.mkdir(parents=True, exist_ok=True)
creados = []
for numero, pagina in enumerate(reader.pages, start=1):
writer = PdfWriter()
writer.add_page(pagina)
destino = carpeta / f"pagina-{numero:03d}.pdf"
with destino.open("wb") as archivo:
writer.write(archivo)
creados.append(destino)
return creados
reader = PdfReader("entrada.pdf")
writer = PdfWriter()
for indice, pagina in enumerate(reader.pages):
if indice == 0:
pagina.rotate(90)
writer.add_page(pagina)
with open("rotado.pdf", "wb") as archivo:
writer.write(archivo)
La rotación cambia la orientación, pero no corrige automáticamente recortes, coordenadas de anotaciones ni formularios. Abre muestras verticales y horizontales antes de aprobar un lote.
Leer y cambiar metadatos
Título, autor y asunto sirven para catalogar, pero pueden faltar o ser incorrectos. Nunca los uses como fuente de identidad o autorización.
from pypdf import PdfReader, PdfWriter
reader = PdfReader("informe.pdf")
print(reader.metadata.title if reader.metadata else None)
writer = PdfWriter()
writer.append_pages_from_reader(reader)
writer.add_metadata({
"/Title": "Informe mensual",
"/Author": "Equipo de Datos",
"/Subject": "Consolidación interna",
})
with open("informe-catalogado.pdf", "wb") as archivo:
writer.write(archivo)
Una reescritura podría no conservar funciones avanzadas. Firmas digitales, adjuntos, JavaScript, formularios, marcadores y etiquetas de accesibilidad requieren pruebas específicas. Conserva el original y compara el resultado.
Extraer texto entendiendo los límites
extract_text() recupera caracteres presentes en la estructura. Un escaneo contiene píxeles, no caracteres, y puede devolver vacío; pypdf no realiza OCR. Columnas, tablas, ligaduras, fórmulas, fuentes especiales y posicionamiento absoluto también alteran el orden de lectura. La guía oficial explica con más detalle por qué es difícil extraer texto de un PDF.
from pypdf import PdfReader
reader = PdfReader("informe.pdf")
partes = []
for numero, pagina in enumerate(reader.pages, start=1):
texto = pagina.extract_text() or ""
if not texto.strip():
print(f"Página {numero}: no hay capa de texto; quizá requiera OCR")
partes.append(texto)
contenido = "\n\n".join(partes)
print(contenido[:500])
Conserva números de página y coteja campos importantes con el original. Después puedes normalizar patrones sencillos con nuestra guía de expresiones regulares en Python, pero una regex no es un analizador universal de tablas.
Cifrar y abrir un PDF protegido
Obtén contraseñas de variables de entorno o de un gestor de secretos, nunca del código. Tampoco las imprimas en registros o mensajes de error.
import os
from pypdf import PdfReader, PdfWriter
contrasena = os.environ.get("PDF_PASSWORD")
if not contrasena:
raise RuntimeError("Define PDF_PASSWORD")
reader = PdfReader("privado.pdf")
writer = PdfWriter()
writer.append_pages_from_reader(reader)
writer.encrypt(contrasena, algorithm="AES-256")
with open("privado-protegido.pdf", "wb") as archivo:
writer.write(archivo)
protegido = PdfReader("privado-protegido.pdf")
if protegido.is_encrypted and protegido.decrypt(contrasena) == 0:
raise ValueError("Contraseña incorrecta")
print(len(protegido.pages))
Según la versión instalada, ciertos algoritmos pueden necesitar una dependencia criptográfica adicional. Consulta la documentación actual. El cifrado tampoco evita que un lector autorizado redistribuya una copia abierta ni sustituye permisos de almacenamiento.
Proyecto práctico: procesador de informes
Crea una carpeta entrada, ordena informes según una convención acordada, únelos, añade metadatos, rota páginas conocidas y escribe primero una salida temporal. Vuelve a abrirla y comprueba el total de páginas antes de renombrarla como resultado final. Registra entradas, fecha y estado según nuestra guía de logging en Python.
Separa lectura, transformación y escritura para probar cada función con PDF pequeños. En archivos subidos, limita tamaño y páginas, rechaza extensiones inesperadas y procesa contenido no confiable en un entorno aislado. Un archivo pequeño puede ocupar mucha memoria al descomprimir sus flujos internos.
Conserva la geometría, los enlaces y los formularios de forma deliberada
Una página PDF posee varias cajas, entre ellas media, crop, bleed, trim y art. La caja de recorte limita el área que suele mostrarse, mientras que la caja de medios describe la página física. Al unir documentos creados por herramientas diferentes pueden aparecer páginas recortadas o con tamaños inconsistentes. Inspecciona esas propiedades antes de normalizarlas, porque cambiar dimensiones sin transformar el contenido puede ocultar o desplazar información. La guía oficial de recortes y transformaciones de pypdf explica las operaciones disponibles.
Las anotaciones y los formularios interactivos requieren la misma cautela. Un enlace visible puede estar almacenado como anotación, y un campo de formulario puede depender de estructuras globales además de la página donde aparece. Copiar páginas sueltas no siempre equivale a copiar el documento completo. Si los enlaces, marcadores, adjuntos o campos AcroForm son requisitos, crea un archivo de aceptación que incluya cada función y revisa el resultado en más de un visor. Aplanar un formulario o conservar una firma digital son requisitos independientes, no efectos automáticos de escribir con PdfWriter.
Diseña pruebas alrededor de invariantes
Una prueba fiable no compara byte por byte dos PDFs completos. Las fechas de metadatos, la numeración de objetos, la compresión y la serialización pueden cambiar aunque los documentos se vean iguales. Comprueba los invariantes importantes: número de páginas, dimensiones, rotación, metadatos seleccionados, estado de cifrado y presencia del texto esperado en páginas conocidas. Cuando el diseño sea relevante, renderiza una muestra representativa y compárala visualmente.
Incluye archivos dañados, vacíos, cifrados, grandes, verticales y horizontales. Cada prueba debe utilizar su propio directorio temporal para impedir colisiones en ejecuciones paralelas. En lotes de producción, calcula antes la cantidad esperada de resultados y concíliala al terminar. Estos controles hacen visibles los fallos parciales y permiten repetir el trabajo sin duplicar páginas silenciosamente.
Errores comunes y lista de verificación
PdfReadError: archivo corrupto, incompleto o incompatible; registra su nombre y consérvalo para inspección.- Contraseña errónea: comprueba
is_encryptedy el resultado dedecryptantes de leer páginas. - Texto vacío: confirma si existe una capa textual antes de añadir OCR.
- Salida truncada: escribe en modo binario, cierra el flujo y valida abriendo el resultado.
- Memoria elevada: rechaza documentos demasiado grandes o anormalmente complejos.
Antes de desplegar, comprueba: orden determinista; originales conservados; rutas separadas; secretos fuera de Git; límites de tamaño y páginas; registros sin contenido sensible; total de páginas verificado; muestras visuales; dependencias actuales y fijadas. Captura excepciones concretas sin ocultarlas; la guía de tratamiento de errores en Python explica este enfoque.
Conclusión
pypdf ofrece una API enfocada en automatización estructural. Empieza con copias descartables, verifica cada transformación y considera el texto extraído una aproximación condicionada por el documento. Validación de rutas, límites, registros y pruebas convierten estas operaciones en un procesador fiable sin atribuir al formato o a la biblioteca capacidades que no tienen.