viernes, 17 de abril de 2026

DATOS. Manipulación

Datos semi-estructurados PDF (II)

De tabla-pdf a txt y a csv

Dado el objetivo de extraer los datos que contienen tablas de archivos .pdf (datos semi-estrucurados), lo normal es aplicar procedimientos que, en teoría, se ajustan al objetivo y no recurrir a la conversión de tabla-pdf a imagen para usar después un OCR. Por eso califiqué antes esta opción como "ultimo recurso", y por eso en esta entrada probaremos a obtener esos datos por otros medios, concretamente mediante la biblioteca PyMuPDF, de la cual ya hablamos en este blog [pendiente enlace a entrada].

Cierto que disponemos de varias opciones para la salida de los datos, pero por claridad expositiva voy a mostrar en primer lugar la que devuelve la mera visualización por pantalla (consola) de los datos que obtenemos con el script. De esta forma podremos observar mejor la lógica del código y las características de lo que nos devuelve.



'''
Este script utiliza la biblioteca PyMuPDF para acceder a un documento pdf, localizar las tablas
que contiene, extraerlo y mostrarlo por pantalla (CMD>
Este script ha sido desarrollado usando IA-Gemini como asistente de programación
'''

#0. Importar biblioteca

import fitz  # PyMuPDF

#Función para acceso y lectura de tablas del pdf------------------------------------

def extraer_tablas_pdf(ruta_pdf):
    
# 1. Abrir el documento pdf

    doc = fitz.open(ruta_pdf)
    
    print(f"Analizando documento: {ruta_pdf}\n")

# 2. Recorrer las páginas del documento...
    for num_pagina, pagina in enumerate(doc):

# ... buscando las tablas en cada página
        tabs = pagina.find_tables()

# Si se encuentra tabla en la página...        
        if tabs:
            print(f"--- Página {num_pagina + 1} ({len(tabs.tables)} tablas encontradas) ---")
#... se recorrer la tabla y...
            for i, tabla in enumerate(tabs.tables):
                
# 3. Se extrae el contenido de la tabla como una lista de listas
                datos = tabla.extract()            
                print(f"\nTabla {i + 1}:")
                for fila in datos:
# Limpiamos saltos de línea internos para facilitar la lectura por pantalla
                    fila_limpia = [str(celda).replace('\n', ' ') if celda else "" for celda in fila]
                    print(fila_limpia)
        else:
# Muestra las páginas que no contienen tablas
                print(f"Página {num_pagina + 1}: No se detectaron tablas.")
        pass
    doc.close()
# Fin de la función-------------------------------------------------------------------------------

# Script de llamada a la función----------------------------------------------------------------

# 4. Configuración de rutas
ruta = "TuArchivo.pdf" # Identificamos el archivo pdf

# 5. Llamada a la función pasando el parámetro (ruta del pdf)

extraer_tablas_pdf(ruta)


Te dejo que leas detenidamente este código para que lo interpretes con ayuda de los comentarios que contiene; también que lo pruebes sobre algun .pdf que contenga tablas, requisito este indispensable. Un uso más centrado en lo funcional que en lo expositivo eliminaría órdenes como print(f"Página {num_pagina + 1}: No se detectaron tablas."), o simplemente las comentaría, que es lo que te yo sugiero; pero aquí cumplen su función, por lo que son necesarias

Y ahora vamos a comprobar qué obtenemos mostrando, a modo de ejemplo, el contenido de la identificada como tabla 2

Al haber transformado el contenido en una lista de listas (como vemos en 3), podemos visualizar cada elemento textiual (indiferenciado entre etiqueta y campo) cada una de las sublistas (líneas de texto) que contiene la gran lista que es el conjunto de los datos obtenidos. Es un avance respecto al mero texto plano, pero no es aun lo deseado, ya que aquí es tratada como igual la etiqueta y el dato.

Vamos a probar con otra salida, la conversión del resultado de la extracción en un archivo .txt, lo que equipara el resultado al obtenido mediente el procedimiento OCR aplicado antes. Además de estudiar el resultado por comparación con el anterior y el referenciado, también podrás comprobar los cambios que conlleva esta propuesta respecto a la mera visualización por pantalla.


'''
Este script utiliza la biblioteca PyMuPDF para acceder a un documento pdf, localizar las tablas
que contiene y extraer sus datos.
Posteriormente se archiva el resultado como .txt
Este script ha sido desarrollado usando IA-Gemini como asistente de programación
'''
#0. Cargar la biblioteca necesaria

import fitz  # PyMuPDF

#Función de acceso a datos y creación de txt------------------------------------------------

def tablas_pdf_a_txt(ruta_pdf, ruta_txt):
# 1. Abrir el documento PDF
    doc = fitz.open(ruta_pdf)
# 2. Creación del archivo txt de salida
    with open(ruta_txt, "w", encoding="utf-8") as archivo_salida:
        archivo_salida.write(f"EXTRACCIÓN DE TABLAS - {ruta_pdf}\n")
        archivo_salida.write("=" * 50 + "\n\n")

        for num_pagina, pagina in enumerate(doc):
# 3. Buscar tablas en la página del pdf
            tabs = pagina.find_tables()
            
            if tabs.tables:
                archivo_salida.write(f"--- PÁGINA {num_pagina + 1} ---\n")
                
                for i, tabla in enumerate(tabs.tables):
                    archivo_salida.write(f"\n[Tabla {i + 1}]\n")
                    
# 4.Extraer los datos de la tabla
                    datos = tabla.extract()
                    
                    for fila in datos:
# Limpiar celdas: quitar saltos de línea y convertir None en vacío
                        fila_limpia = [str(celda).replace('\n', ' ').strip() if celda else "" for celda in fila]                      
# Unir la fila con tabuladores para mantener formato de columnas
                        linea = "\t".join(fila_limpia)
                        archivo_salida.write(linea + "\n")
                    
                    archivo_salida.write("-" * 30 + "\n")
                
                archivo_salida.write("\n")
        
        print(f"Proceso finalizado. Las tablas se han guardado en: {ruta_txt}")

    doc.close()

# Fin de la función-------------------------------------------------------------------------------------------
#Procedimiento de llamada a la función------------------------------------------------------------------------
# 5. Configuración de rutas
pdf_entrada = "mi_archivo.pdf"          # Cambia por tu archivo pdf (sólo el nombre y la extensión)
txt_salida = "tablas_extraidas.txt"     # Cambia por tu nombre de archivo.txt de salida

# 6. Llamada a la función
tablas_pdf_a_txt(pdf_entrada, txt_salida)   


También aquí implementamos alguna información funcionalmente innecesaría (pero que nos sirven para mejorar la comprensión del resultado), lo que distorsiona el logro del objetivo e incrementa el trabajo posterior de adaptación del .txt resultante, pero lo interesante es comprobar que la salida actual ...

... se parece más a la que obtuvimos mediante ocr, de modo que en este caso podemos optar indistintamente por cualquiera de los dos procedimientos, aunque lo esperable es optar por PyMuPDF por resultar menos costoso computacionalmente...

... y menos a la que el mismo script nos ofrece en el CMD y que pudimos ver antes. Las tres, además, se asemejan más a la mera visualización de la tabla (que te muestro debajo) que a lo que implica de estructura de datos, incluyendo la pérdida de referencia al contenido del último de sus cinco (que no cuatro) campos:

Para finalizar esta entrada vamos a modificar la salida anterior y converirla en .csv. Comprobarás que el script es muy parecido al anterior, pero que el resultado es muy diferente. Empecemos por mostrar el script.



'''
Este script utiliza la biblioteca PyMuPDF para acceder a un documento pdf, localizar las tablas
que contiene y extraer sus datos.
Posteriormente se archiva el resultado como .csv, accesible desde un servicio Hoja de cálculo (Excel o Calc)
Este script ha sido desarrollado usando IA-Gemini como asistente de programación
'''
#0. Importar las bibliotecas necesarias

import fitz  # PyMuPDF
import csv

#Función para extraer las tablas y su contenido-----------------------------------

def tablas_a_csv(pdf, archivo_csv_salida):

# 1. Abrir el documento PDF e iniciar variables
    doc = fitz.open(pdf)
    total_paginas = len(doc)
    tablas_encontradas = 0

# 2. Generar el archivo csv de salida
    with open(archivo_csv_salida, mode="w", newline="", encoding="utf-8-sig") as fichero:
        escritor_csv = csv.writer(fichero, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL)    
        
# 3. Recorrer las páginas
        for num_pagina in range(total_paginas):
            pagina = doc[num_pagina]
            tabs = pagina.find_tables() # Buscamos tablas en la página actual
# Si se encuentran tablas en la página actual...
            if tabs.tables:
#... se recorre la tabla y...
                for i, tabla in enumerate(tabs.tables):
                    
# 4. Extraemos el contenido de la tabla
                    datos = tabla.extract()
                    if datos:
                        tablas_encontradas += 1
# Escribimos el encabezado de identificación para la tabla
                        escritor_csv.writerow([f"--- TABLA {tablas_encontradas} (Página {num_pagina + 1}) ---"])  
                        for fila in datos:
# Limpiamos cada celda: quitar saltos de línea y espacios en blanco
                            fila_limpia = [str(celda).replace('\n', ' ').strip() if celda is not None else "" for celda in fila]
                            escritor_csv.writerow(fila_limpia)    
# Añadimos una línea en blanco para separar visualmente las tablas en el CSV
                        escritor_csv.writerow([])
# Generamos mensajes de finalización
    if tablas_encontradas > 0:
        print(f"Éxito: Se han extraído {tablas_encontradas} tablas en '{archivo_csv_salida}'.")
    else:
        print("No se detectaron tablas en ninguna página del documento.")

    doc.close()

#Fin de la función ---------------------------------------------------------------------------------
    
#  Procedimiento de llamada a función-----------------------------------------------------------

# 5. Identificación de rutas
pdf_entrada = "Mi_Archivo.pdf"         # Identificamos el archivo pdf de entrada [Cambia esto por tu archivo pdf (nombre + extensión entre comillas)]
csv_salida = "tablas_extraidas.csv"    # Identificamos el txt de salidad [Pon nombre de tu archivo txt de salida]

# 6. Llamada a la función
tablas_a_csv(pdf_entrada, csv_salida)


En cuanto al script, existen algunas diferencias con el anterior script, empezando por import csv y siguendo por la estructura que genera el archivo .csv (ver código asociado al comentario 2), pero el resto es muy parecido. Lo que no lo es tanto es el resultado.

Al tratarse de un .csv podemos visualizarlo desde Bloc de notas, pudiendo apreciar la similitud que mantiene con la visualización de los datos en pantalla (lista de listas)...

... con el consecuente procedimiento de ajuste y limpieza posterior que implica y que nos devuelve al conocido procedimiento ofimático; pero también podemos acceder a él desde un servicio de Hoja de cálculo (vg. Calc, donde observamos un resultado similar a este...

... que si bien no permite un tratamiento directo como tabla de datos (en el sentido de que no podemos considerarlos aun como datos estructurados), sí nos permite un tratamiento automatizado, cuanto menos para la definitiva conversión o incorporación en una base de datos. En esto proceso de automatización no podemos utilizar directamente una solución como esta, pero sí una similar, aunque para ellos es posible que sea necesario convertir previamente el .csv en .ods (para aplicar un script OOo Basic) o .xlsx (para aplicar un script Python basado en la biblioteca OpenPyXL.

No hay comentarios:

Publicar un comentario

Comenta esta entrada