Código fuente en Python de una aplicación de consola completa que permite obtener los metadatos de un fichero PDF (autor, asunto, título, creador, productor, fecha de creación, fecha de modificación, número de páginas) y permite limpiar los metadatos o modificarlos, introduciendo unos personalizados.
- Código fuente de la aplicación Python que obtiene y modifica los metadatos de un fichero PDF.
- Probando la aplicación metaPDF en Python para limpiar, mostrar y modificar los metadatos de un fichero PDF.
- Descarga del código fuente y la aplicación portable para Windows y para Linux.
Código fuente de la aplicación Python que obtiene y modifica los metadatos de un fichero PDF
El código fuente completo, que se puede copiar y pegar en un fichero de texto con el nombre metaPDF.py, será:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# ProyectoA.com Aplicación Python para obtener, modificar y limpiar metadatos de un fichero PDF # Versión 2.0 import argparse from datetime import datetime import os.path from pypdf import PdfReader, PdfWriter # Obtiene la propiedad del PDF si existe # Pasando como parámetro un reader.metadata y el nombre de la propiedad def ObtenerPropiedadPDF(metadatosPDF, propiedad, verbose): try: if hasattr(metadatosPDF, propiedad): if getattr(metadatosPDF, propiedad) is None: return "--VACÍO--" else: return getattr(metadatosPDF, propiedad) else: return "--VACÍO--" except Exception as ex: if verbose: print("Se ha producido un error al obtener la propiedad {0} del fichero PDF: {1}".format( propiedad, getattr(ex, 'message', str(ex)))) return 106 # Procedimiento para obtener los metadatos del fichero PDF y mostrarlos por pantalla def ObtenerMetadatosPDF(ficheroOrigen, verbose): if os.path.exists(ficheroOrigen): try: reader = PdfReader(ficheroOrigen) # Obtenemos los metadatos actuales titulo = ObtenerPropiedadPDF(reader.metadata, "title", verbose) asunto = ObtenerPropiedadPDF(reader.metadata, "subject", verbose) autor = ObtenerPropiedadPDF(reader.metadata, "author", verbose) productor = ObtenerPropiedadPDF(reader.metadata, "producer", verbose) creador = ObtenerPropiedadPDF(reader.metadata, "creator", verbose) fechaCreacion = ObtenerPropiedadPDF(reader.metadata, "creation_date", verbose) fechaModificacion = ObtenerPropiedadPDF(reader.metadata, "modification_date", verbose) try: numeroPaginas = len(reader.pages) except: numeroPaginas = "0" # Si se ha añadido el argumento -v, mostramos los metadatos obtenidos if verbose: print("Autor: " + autor) print("Creador: " + creador) print("Productor: " + productor) print("Fecha creación: {0}".format(fechaCreacion)) print("Fecha modificación: {0}".format(fechaModificacion)) print("Número de páginas: {0}".format(numeroPaginas)) else: # Si no se ha introducido el argumento -v, mostramos los datos en formato JSON metadatosJSON = '{{"titulo": "{0}","asunto": "{1}","autor": "{2}","creador": "{3}","productor": "{4}","fecha_creacion": "{5}","fecha_modificacion": "{6}","numero_paginas": "{7}"}}'.format( titulo, asunto, autor, creador, productor, fechaCreacion, fechaModificacion, numeroPaginas) print(metadatosJSON) except Exception as ex: if verbose: print("Se ha producido un error al obtener los metadatos del fichero PDF: {0}".format(getattr(ex, 'message', str(ex)))) return 104 return 0 else: if verbose: print("No existe el fichero PDF: {0}".format(ficheroOrigen)) return 100 # Procedimiento para modificar los metadatos de un fichero PDF def ModificarMetadatosPDF(ficheroOrigen, ficheroSalida, metadatosPDF, fecha, verbose, reemplazar): if os.path.exists(ficheroOrigen): try: reader = PdfReader(ficheroOrigen) writer = PdfWriter() # Añadimos todas las páginas para generar el nuevo PDF con los nuevos metadatos for page in reader.pages: writer.add_page(page) # Formatear fecha utc_time = "+01'00'" # UTC time optional fechaMod = datetime.now().strftime(f"D\072%Y%m%d%H%M%S{utc_time}") # fechaMod = datetime.now() if fecha != None: fechaMod = fecha # Escribimos los nuevos metadatos en el fichero PDF writer.add_metadata( { "/Author": metadatosPDF["autor"], "/Producer": metadatosPDF["productor"], "/Title": metadatosPDF["titulo"], "/Subject": metadatosPDF["asunto"], "/Keywords": metadatosPDF["palabras_clave"], "/CreationDate": fechaMod, "/ModDate": fechaMod, "/Creator": metadatosPDF["aplicacion"], "/CustomField": metadatosPDF["campos_personalizados"], } ) except Exception as ex: if verbose: print("Se ha producido un error al modificar/limpiar los metadatos del fichero PDF: {0}".format(getattr(ex, 'message', str(ex)))) return 105 # Guardar el nuevo fichero PDF, si no se ha indicado fichero de salida, se reemplaza el original if ficheroSalida != "": ficheroOrigen = ficheroSalida try: # Si existe el fichero de salida (sea el original o el que se creará) # Y no se ha añadido el parámetro "reemplazar", no se realizarán modificaciones if not os.path.exists(ficheroOrigen) or (os.path.exists(ficheroOrigen) and reemplazar): with open(ficheroOrigen, "wb") as f: writer.write(f) if verbose: print("Metadatos del fichero {0} modificados correctamente".format(ficheroOrigen)) return 0 else: if verbose: print("No se han realizado cambios, el fichero de salida existe y no se ha incluido el parámetro \"reemplazar\"") return 103 except PermissionError: if verbose: print("El fichero destino está abierto o no tiene permisos para escribir en la carpeta destino") return 101 except Exception as ex: if verbose: print("Se ha producido un error al generar el fichero PDF: {0}".format(getattr(ex, 'message', str(ex)))) return 102 else: if verbose: print("No existe el fichero PDF origen: {0}".format(ficheroOrigen)) return 100 # Procedimiento para limpiar (vaciar) los metadatos de un fichero PDF def LimpiarMetadatosPDF(ficheroOrigen, ficheroSalida, verbose, reemplazar): metadatosPDF = {'titulo': '', 'asunto': '','autor': '','productor': '','aplicacion': '','palabras_clave': '','campos_personalizados': ''} ModificarMetadatosPDF(ficheroOrigen, ficheroSalida, metadatosPDF, None, verbose, reemplazar) # Mostrar y preparar los argumentos que admite el programa por la línea de comandos def MostrarArgumentos(): # Iniciamos el programa, obteniendo los argumentos pasados por la línea de comandos # Conformamos los argumentos que admitirá el programa por la línea de comandos parser = argparse.ArgumentParser() parser.add_argument("-fo", "--fichero_PDF_Origen", type=str, required=True, help="Ruta y nombre del fichero PDF origen para obtener/modificar/limpiar los metadatos") parser.add_argument("-fd", "--fichero_PDF_Destino", type=str, required=False, help="Ruta y nombre del fichero PDF destino. Si no se indica, se reemplazará el original (si se incluye el parámetro \"reemplazar\")") parser.add_argument("-om", "--obtener", action="store_true", help="Obtener y mostrar los metadatos del fichero PDF") parser.add_argument("-mm", "--modificar", action="store_true", help="Modificar los metadatos del fichero PDF") parser.add_argument("-lm", "--limpiar", action="store_true", help="Limpiar (vaciar) los metadatos del fichero PDF") parser.add_argument("-r", "--reemplazar", action="store_true", help="Si existe el fichero destino se reemplazará, si no se añade este parámetro, si existe el fichero destino, no se realizarán modificaciones") parser.add_argument("-me", "--metadatos", required=False, type=str, help="Si se usa el parámetro \"modificar\" se le pasarán los metadatos a añadir en este parámetro, con el formato 'titulo=valor,asunto=valor,autor=valor,productor=valor,aplicacion=valor,palabras_clave=valor,campos_personalizados=valor'") parser.add_argument("-v", "--verbose", required=False, action="store_true", help="Se activará el modo verbose (mostrar por pantalla todos los resultados)") return parser.parse_args() # Procedimiento que ejecuta el resto de procedimientos para iniciar el programa def IniciarPrograma(): # Preparamos y mostramos los argumentos (si se indica) args = MostrarArgumentos() # Habilitar/deshabilitar modo verbose verbose = False if args.verbose: verbose = True # Habilitar/deshabilitar reemplazar reemplazar = False if args.reemplazar: reemplazar = True # Si se han pasado los metadatos a modificar, los obtenemos separados por ...=..., if args.metadatos: metadatosPDF = dict( pair.split('=') for pair in args.metadatos.split(',') ) # Si se pasa parámetro de obtención de metadatos if args.obtener: ObtenerMetadatosPDF(args.fichero_PDF_Origen, verbose) # Si se pasa parámetro de modificación de metadatos if args.modificar: if args.metadatos is None: if args.verbose: print ("Si usa el parámetro \"modificar\" debe añadir el parámetro \"metadatos\" para indicar los metadatos a añadir") return 107 else: if args.fichero_PDF_Destino: ModificarMetadatosPDF(args.fichero_PDF_Origen, args.fichero_PDF_Destino, metadatosPDF, None, verbose, reemplazar) else: ModificarMetadatosPDF(args.fichero_PDF_Origen, "", metadatosPDF, None, verbose, reemplazar) # Si se pasa parámetro de limpieza de metadatos if args.limpiar: if args.fichero_PDF_Destino: LimpiarMetadatosPDF(args.fichero_PDF_Origen, args.fichero_PDF_Destino, verbose, reemplazar) else: LimpiarMetadatosPDF(args.fichero_PDF_Origen, "", verbose, reemplazar) # Iniciamos el programa IniciarPrograma() |
La descarga del fichero metaPDF.py completo:
La aplicación metaPDF ha sido diseñada para poder ejecutarse desde otra aplicación, devolviendo los siguientes códigos de retorno según el resultado de su ejecución:
- 0: ejecución correcta, se eliminan/modifican/muestran los metadatos del fichero PDF.
- 100: no existe el fichero PDF origen.
- 101: el fichero destino está abierto o no tiene permisos para escribir en la carpeta destino.
- 102: se ha producido un error al generar el fichero PDF.
- 103: no se han realizado cambios en el PDF, el fichero de salida existe y no se ha incluido el parámetro «reemplazar».
- 1: error no capturado en la aplicación.
Probando la aplicación metaPDF en Python para limpiar, mostrar y modificar los metadatos de un fichero PDF
Se trata de una aplicación de línea de comandos (de consola), por lo que abriremos una ventana de MS-DOS (Símbolo de sistema) para el caso de Windows o una ventana de terminal de shell de comandos en el caso de Linux. El código fuente está probado y funciona tanto en equipos Windows como Linux.
Para ejecutar la aplicación metaPDF.py, necesitaremos tener instalado Python en el equipo. En el siguiente enlace explicamos cómo instalarlo en Linux:
Accederemos a la carpeta donde tengamos el fichero metaPDF.py, por ejemplo en D:\ProyectoA\Python\metaPDF:
1 |
cd D:\ProyectoA\Python\metaPDF |
Para ejecutar la aplicación, usaremos el siguiente comando:
1 |
python metaPDF.py -h |
Si recibimos este error:
1 2 3 4 |
Traceback (most recent call last): File "D:\ProyectoA\Python\metaPDF\metaPDF.py", line 5, in <module> from pypdf import PdfReader, PdfWriter ModuleNotFoundError: No module named 'pypdf' |
Indica que no tenemos instalado el módulo «pypdf», por lo que tendremos que instalarlo con el comando:
1 |
pip3 install pypdf |
Ahora sí nos funcionará la ejecución de la aplicación, por ejemplo, para mostrar los argumentos que soporta, ejecutaremos:
1 |
python metaPDF.py -h |

Mostrar los metadatos de un fichero pdf con metaPDF
Vamos a realizar una primera prueba, ejecutaremos la aplicación metaPDF con los argumentos necesarios para que obtenga y muestre por pantalla los metadatos del fichero pdf de ejemplo D:\Documentos\mi_pdf.pdf:
1 |
python metaPDF.py -om -fo D:\Documentos\mi_pdf.pdf -v |

Como podemos comprobar, la aplicación metaPDF funciona correctamente, mostrando los metadatos del fichero PDF:
Título: Documento PDF de prueba por proyectoa.com – Título
Asunto: Documento PDF de prueba por proyectoa.com – Asunto
Autor: ProyectoA
Creador: PDFCreator Version 1.5.1
Productor: GPL Ghostscript 9.05
Fecha creación: 2024-01-19 10:59:39+01:00
Fecha modificación: 2024-01-19 10:59:39+01:00
Número de páginas: 6
Podemos comprobar que son correctos, abriendo la aplicación Acrobat Reader y mostrando las propiedades del documento (Menú – Propiedades del documento):

La aplicación también permite que se muestren los metadatos del PDF pero en formato JSON, usando este comando:
1 |
python metaPDF.py -om -fo D:\Documentos\mi_pdf.pdf -v |
Es el mismo que el anterior, quitando el argumento -v (verbose), nos devolverá:
1 |
{"titulo": "Documento PDF de prueba por proyectoa.com - Título","asunto": "Documento PDF de prueba por proyectoa.com - Asunto","autor": "ProyectoA","creador": "PDFCreator Version 1.5.1","productor": "GPL Ghostscript 9.05","fecha_creacion": "2024-01-19 10:59:39+01:00","fecha_modificacion": "2024-01-19 10:59:39+01:00","numero_paginas": "6"} |
Limpiar los metadatos de un documento PDF con metaPDF
Como es sabido, los metadatos de los ficheros PDF pueden suponer un alto riesgo de fuga de información. A nivel de seguridad informática, en muchas ocasiones el usuario no es consciente de la información sensible que puede almacenarse en los metadatos (nombre completo del usuario, correo electrónico, DNI, nick de acceso al equipo, direcciones IP, nombres de impresoras de la organización, etc.). Por ello, es muy recomendable disponer de alguna herramienta que permita limpiar (vaciar) estos metadatos.
metaPDF permite realizar la limpieza de los metadatos de un fichero PDF. Para limpiar los metadatos del fichero pdf anterior (D:\Documentos\mi_pdf.pdf) y crear una copia del fichero con los metadatos limpios (en D:\Documentos\mi_pdf_limpio.pdf), ejecutaremos este comando:
1 |
python metaPDF.py -lm -fo D:\Documentos\mi_pdf.pdf -fd D:\Documentos\mi_pdf_limpio.pdf -v |
Si hemos indicado el argumento -v, nos mostrará el mensaje:
Metadatos del fichero D:\Documentos\mi_pdf_limpio.pdf modificados correctamente
En realidad, lo que ha hecho la aplicación metaPDF es crear una copia del fichero origen con los metadatos vacíos. El fichero origen no lo ha modificado. Si comprobamos los metadatos del fichero generado mi_pdf_limpio.pdf veremos que están vacíos:
1 |
python metaPDF.py -om -fo D:\Documentos\mi_pdf_limpio.pdf -v |

Si queremos limpiar los metadatos del fichero original directamente, sin generar una copia, usaremos el comando:
1 |
python metaPDF.py -lm -fo D:\Documentos\mi_pdf.pdf -v -r |
Quitando el fichero destino y añadiendo el argumento -r (reemplazar). Si hemos usado el argumento -v, nos mostrará el mensaje:
Metadatos del fichero D:\Documentos\mi_pdf_limpio.pdf modificados correctamente

Comprobaremos que, efectivamente, se han limpiado los metadatos del fichero PDF original D:\Documentos\mi_pdf.pdf :

La gran ventaja de que se trata de una aplicación de línea de comandos que admite argumentos es que se podría programar un script que usara la herramienta metaPDF para limpiar todos los metadatos de todos los ficheros PDF que se desee de forma automatizada. Sería suficiente con realizar un script que liste todos los PDF de una ubicación y vaya ejecutando la herramienta metaPDF por cada uno de ellos.
Modificar los metadatos de un fichero PDF con metaPDF
La herramienta metaPDF también permite modificar los metadatos de un fichero PDF, pasándoselos como argumento. Un ejemplo, para modificar los metadatos del fichero PDF D:\Documentos\mi_pdf.pdf, estableciéndole los siguientes metadatos:
- Título: Título generado con metaPDF.
- Asunto: Asunto generado con metaPDF.
- Autor: Autor generado con metaPDF.
- Productor: Productor generado con metaPDF.
- Aplicación: Aplicación generada con metaPDF.
Usaremos este comando:
1 |
python metaPDF.py -mm -fo D:\Documentos\mi_pdf.pdf -v -r -me "titulo=Título generado con metaPDF,asunto=Asunto generado con metaPDF,autor=Autor generado con metaPDF,productor=Productor generado con metaPDF,aplicacion=Aplicación generada con metaPDF,palabras_clave=,campos_personalizados=" |
Comprobaremos que, efectivamente, los metadatos del fichero PDF se han modificado por los pasados como argumento:

Descarga del código fuente y la aplicación portable para Windows y para Linux
Desde el siguiente enlace podéis descargar tanto el fichero de código fuente Python (metaPDF.py) como el ejecutable portable para Windows: metaPDF.exe y para Linux meetaPDF: