Mostrando entradas con la etiqueta pdf. Mostrar todas las entradas
Mostrando entradas con la etiqueta pdf. Mostrar todas las entradas

domingo, 19 de abril de 2026

DATOS. Archivos PDF

Bibloteca PyMuPDF (V)

Obtención y generación de imágenes

Dado que los documentos .pdf, además de texto y tablas, también pueden contener gráficos y que PyMuPDF es una herramienta que permite trabajar con este tipo de archivos de forma integral, parece conveniente terminar esta serie de entradas tratando sobre el trabajo de PyMuPDF con imágenes.

En primer lugar vamos a extraer las imágenes de un archivo .pdf para comporbar empíricamente que esto es posible y aprender cómo hacerlo. El siguiente script es un ejemplo de ello.



# 0. Bibliotecas necesarias

import fitz  # PyMuPDF
import io
from PIL import Image # Librería opcional para procesar el formato si es necesario

# 1. Acceder al objeto documento
doc = fitz.open("mi_documento.pdf")		# ruta relativa/nombre del documento .pdf sobre el que se trabaja

total_imagenes = 0  					# Iniciar el contador de img del doc

print(f"Analizando documento: {doc.name}\n")

# 2. Recorrer cada página del doc
for num_pagina, pagina in enumerate(doc):
    lista_imagenes = pagina.get_images(full=True)    # Obtener lista de imágenes de la página actual
    
    if lista_imagenes:
        cantidad_en_pagina = len(lista_imagenes)
        print(f"Página {num_pagina + 1}: se encontraron {cantidad_en_pagina} imágenes.")
        total_imagenes += cantidad_en_pagina
        
# 3. Procesar cada imagen encontrada
        for indice_img, img in enumerate(lista_imagenes):
            xref = img[0]                             # XREF es el identificador único del objeto
            base_image = doc.extract_image(xref)
            imagen_bytes = base_image["image"]
            extension = base_image["ext"] 			  # png, jpeg, etc.
            
# 4. Guardar la imagen en el disco
            nombre_archivo = f"imagen_p{num_pagina+1}_{indice_img+1}.{extension}"
            with open(nombre_archivo, "wb") as f:
                f.write(imagen_bytes)
    else:
        print(f"Página {num_pagina + 1}: no contiene imágenes.")

# 5. Informe final
print("-" * 30)
if total_imagenes > 0:
    print(f"EXTRACCIÓN COMPLETADA: Se han guardado {total_imagenes} imágenes.")
else:
    print("INFORME: El documento no contiene imágenes integradas.")

doc.close()


Este script realiza dos funciones: extrae las imágenes que obtiene del documento y realiza el recuento parcial (por página) y total (todo el documento) de ellas. Las imágenes las copia como archivos .jpg en el directorio del script, y la información la imprime directamente en pantalla (cmd o shell). Por razones que no están del todo claras, pero que se relacionan con la forma en que fue creado el .pdf, este script extrae una imagen por cada página del documento aunque visualmente sólo existe una sobre (en la primera página). Dado que no es una cuestión que ahora resulta de interés y que podría implicar deternerse demasiado en cuestiones que actualmente son secundarias, he preferido no ahondar en cómo resolver este problema.

Tengo más interés en tratar una segunda cuestión relacionada con las imágenes y el uso de PyMuPDF, como es la funcionlidad que esta biblioteca nos ofrece de crear una imagen de cada una de las páginas del documento, cuestión esta que da respuesta a lo que dejé pendiente en esta entrada en que la que se abordó el acceso de datos de los archivos .pdf.



# 0. Importar biblitecas necesarias

import fitz  # PyMuPDF

# 1. Abrir el documento

pdf_path = "mi_documento.pdf"	#Intoduce aquí el nombre o la ruta relativa de tu archivo .pdf
doc = fitz.open(pdf_path)
total_paginas = len(doc)

print(f"Documento cargado: {pdf_path}")
print(f"El archivo tiene {total_paginas} páginas (del 1 al {total_paginas}).")

# 2. Bucle de solicitud de página a imagen

while True:
    print("\n" + "-"*40)
    entrada = input("Indica el número de página a extraer (o escribe 'salir' para finalizar): ").lower()

    if entrada == "salir":
        break

    if not entrada.isdigit():       # Validar que la entrada sea un número
        print("Error: Por favor, introduce un número válido.")
        continue
    num_pag = int(entrada)

# 3. Validar rango de páginas (ajustando a índice 0)

    if 1 <= num_pag <= total_paginas:
        pagina = doc[num_pag - 1]
        
# 4. Mejorar la resolución (Zoom de 2x)
    
        matriz = fitz.Matrix(2, 2)          # Por defecto se renderiza a 72 DPI. Multiplicamos por 2 para mayor nitidez.
        pix = pagina.get_pixmap(matrix=matriz)
        
# 5. Guardar la imagen en directorio del script (en formato .png)

        nombre_salida = f"captura_pagina_{num_pag}.png"
        pix.save(nombre_salida)
        
        print(f"¡Éxito! La página {num_pag} ha sido guardada como '{nombre_salida}'.")
    else:
        print(f"Error: La página {num_pag} no existe. El rango es 1-{total_paginas}.")

# 6. Cerrar recursos

doc.close()
print("Script finalizado.")
  

En este caso he desarrollado una especie de utilidad para para facilitar al usuario la conversión a imagen (.npg) de las páginas que desee del documento .pdf sobre el que decida trabajar. Esto supone complicar un poco más el script al introducir el procedimiento cíclico de input, pero da utilidad inmediata al script. Los archivos de imagen generados se guardan directa y automáticamente en la raiz de la ruta en que se encuentra el script.

DATOS. Archivos PDF

Bibloteca PyMuPDF (IV)

Acceso a pdf. Sólo tablas

Esta entrada es la complementaria de la anterior: si en aquella extraíamos sólo los párrafos no-tabla, en esta optamos por extraer sólo el texto de las tablas, primero a un archivo .txt (por mantener la mayor similitud posible con el script anterior), que cómo podrás comprobar tras la lectura del script que sigue, modifica lo que en origen fué un if not... por un if.. tal y como se expresa en el punto 5 del script.



# 0. Importamos bibliotecas

import fitz  # PyMuPDF

# 1. Acceder al objeto documento
doc = fitz.open("plataformas_educativas.pdf")

# Creamos el archivo .txt de salida
with open("solo_tablas_extraidas.txt", "w", encoding="utf-8") as archivo_txt:
    for pag in doc:
        archivo_txt.write(f"--- Tablas de la Página {pag.number + 1} ---\n")
        
# 2. Localizar las áreas de las tablas
        tabs = pag.find_tables()
        tab_rects = [table.bbox for table in tabs]

# 3. Obtener los bloques de texto de toda la página
        blocks = pag.get_text("blocks")

        for b in blocks:
            rect_bloque = fitz.Rect(b[:4])
            
# 4. Verificamos si el bloque está DENTRO de alguna tabla
            dentro_de_tabla = False
            for t_rect in tab_rects:
                if rect_bloque.intersects(t_rect): 
                    dentro_de_tabla = True
                    break
            
# 5. Escribir solo si SÍ está dentro de una tabla
            if dentro_de_tabla and b[6] == 0:
                archivo_txt.write(b[4] + "\n")

        archivo_txt.write("\n" + "="*30 + "\n\n")

doc.close()
print("Proceso finalizado: Se ha extraído únicamente el contenido de las tablas.")


Pero como estamos hablando de una estructura de tabla, lo más apropiado es generar como respuesta un archivo que se ajuste a esa misma estructura, lo que aquí concretamos como archivo .scv en el script que sigue:



# 0. Importar bibliotecas necesarias

import fitz  # PyMuPDF
import csv   # Biblioteca para manejar archivos CSV

# 1. Acceder al objeto documento
doc = fitz.open("plataformas_educativas.pdf")

# 2. Abrir el archivo CSV para escritura
with open("tablas_extraidas.csv", "w", newline="", encoding="utf-8") as archivo_csv:
    escritor = csv.writer(archivo_csv)

    for pag in doc:
# 3. Localizar las tablas en la página actual
        tabs = pag.find_tables()     
        for i, tabla in enumerate(tabs):
# Escribimos una fila vacía o un encabezado para separar tablas
            escritor.writerow([f"--- TABLA {i+1} - PÁGINA {pag.number + 1} ---"])
            
# 4. Extraer el contenido estructurado (lista de listas)
            contenido_tabla = tabla.extract()
           
# 5. Escribir todas las filas de la tabla en el CSV
            for fila in contenido_tabla:
                # Cada 'fila' ya es una lista con el texto de cada celda
                escritor.writerow(fila)
# Añadir una fila vacía para separar de la siguiente tabla
            escritor.writerow([])

doc.close()
print("Proceso finalizado: Las tablas se han guardado con éxito en 'tablas_extraidas.csv'")


El "éxito" de este script es localizar las tablas en el texto, lo cual es posible por su claridad formal; de hecho en una primera versión la tabla no contaba con demilitadores en toda su estructura y el script no fue capaz de identificarla como tabla. Tras una modificación de encuadre, el script funcionó perfectamente, así que aconsejo mejorar la tabla siempre que esto sea posible; en caso contrario habrá que hacer algunos ajustes en el script, lo que no siempre garantiza el éxito.

DATOS. Archivos PDF

Bibloteca PyMuPDF (III)

Acceso a pdf. Sólo textos

Aunque PyMuPDF está pensado para extraer (entre otras cosas) el texto del documento con independencia de en qué presentación se encuentre (como vimos en la entrada anterior), cuando en el documento se diferencian bloques de texto (párrafos) y tablas, es posible que deseemos diferenciar unos textos de otros. En este caso vamos a crear un script que da respuesta a esta necesidad.



#0. Importar la biblioteca

import fitz  # PyMuPDF

# 1 Acceder al objeto documento
doc = fitz.open("plataformas_educativas.pdf")

# Generar documento de salida (.txt)
with open("plataformas_txt2.txt", "w", encoding="utf-8") as archivo_txt:
    for pag in doc:
        archivo_txt.write(f"--- Página {pag.number + 1} ---\n")
        
# 1. Localizar las tablas en la página actual
        tabs = pag.find_tables()                     # Busca las tablas presentes en el texto
        tab_rects = [table.bbox for table in tabs]   # Obtenemos los rectángulos que delimitan las tablas

# 2. Obtener los bloques de texto (con independencia de su ubicación)
        blocks = pag.get_text("blocks")              # Cada 'bloque' devuelve (x0, y0, x1, y1, "texto", block_no, block_type)
        for b in blocks:
            rect_bloque = fitz.Rect(b[:4])           # Coordenadas del bloque de texto
            
# 3. Verificar si el bloque está dentro de alguna tabla
            dentro_de_tabla = False                  # Declara la variable para controlar presencia de texto
            for t_rect in tab_rects:
                if rect_bloque.intersects(t_rect):   # Si el bloque de texto toca el área de la tabla...
                    dentro_de_tabla = True           # Cambia el contenido de la variable y...
                    break                            # Se sale del ciclo (y no se "captura" el bloque de texto)
            
# 4. Escribir solo si no es parte de una tabla y es tipo texto (0)
            if not dentro_de_tabla and b[6] == 0:    # Opción contraria a la precedente (if not)
                archivo_txt.write(b[4] + "\n")       # Entonces sí se "capttura" el bloque de texto y se escribe en el .txt (almacen)

        archivo_txt.write("\n\n")                    # Añade salto de línea para diferenciar bloques de texto en el .txt

doc.close()                                          #Cerrar objeto Document

print("Proceso finalizado: Párrafos extraídos omitiendo áreas de tablas.")


Lee detenidamente el script y sus comentarios. Advertirás que la base de su funcionamiento es la identificación de los sectores de texto y de las estructuras de las tablas, ejecutando un proceso opcional, en función del objetivo: capturar (al .txt de saldida) los bloques de texto que no están asociados a la/s tabla/s.

sábado, 18 de abril de 2026

DATOS. Archivos PDF

Bibloteca PyMuPDF (II)

Acceso a documentos pdf

Dentro de los diferentes usos que puede tener la biblioteca PyMuPDF, la primera y más básica es la de facilitar el acceso a los documentos; a los documento .pdf, pero no sólo.

La instrucción para acceder a un archivo .pdf es muy simple: doc = fitz.open("archivo.pdf"), habiendo importado PyMuPDF como import fitz.

En realidad, lo que hacemos con esta instrucción es acceder al objeto, tal y como muestra la visualización de esta instrucción cuando los solicitamos print(doc) -> Document('ar_pdf.pdf'), pero a partir de esa instucción, podemos acceder al recuento del número de páginas (print(doc.page_count)), a los metadatos del documento (print(doc.metadata)) o asignar una de sus páginas a una variable (pag_1 = doc[0]), su contenido a otra (texto_1 = pag_1.get_text()) y solicitar la visualización de éste por pantalla (print(texto_1)), cosa que lograremos sí realmente la página en cuestión contiene texto.

Si queremos acceder al contenido completo del archivo (al texto), podremos usar un bucle que recorrar todas sus páginas utilizando la misma función pag.get_text()...


#Acceso a todas las páginas y a todo su contenido
for pag in doc:
    texto = pag.get_text()
    print(texto)

... pero si queremos almacenar este texto en un archivo .txt, deberemos crearlo (función with open(), recorrer las páginas del documento (for pag in doc:), extrayendo su contenido (texto = pag.get_text()) y manipular el archivo .txt como ya sabemos y aquí desarrolla el punto 4 del script.



#0. Importar la biblioteca

import fitz  # PyMuPDF

# 1 Acceder al objeto documento
doc = fitz.open("ar_pdf.pdf")

# 2. Abrir (o crear) el archivo TXT ('w' para modo escritura, 'encoding' para uso de las tildes)
with open("contenido_extraido.txt", "w", encoding="utf-8") as archivo_txt:
    
# 3. Acceso a todas las páginas y a su contenido
    for pag in doc:
        texto = pag.get_text()
        
# 4. Escribir el texto de la página en el archivo TXT
        archivo_txt.write(f"--- Página {pag.number + 1} ---\n")
        archivo_txt.write(texto)
        archivo_txt.write("\n\n")  # Añade espacio entre páginas

# 5. Cerrar el documento PDF
doc.close()

print("El contenido ha sido archivado con éxito en 'contenido_extraido.txt'")


Este script captaura TODO el texto que reconoce en el documento (con independencia de que se encuentre dentro de tablas o no), por lo que no es útil para diferenciar entre distintos tipos de presentación del texto; pero podemos crear scipt que se centren obtener el texto según su presentación. Eso en la próxima entrada.

DATOS. Archivos PDF

Bibloteca PyMuPDF (I)

Presentación

Inicio esta entrada con un reconocimiento de influencia en su autoría: estas notas parten de la devueltas por IA-Gemini en consulta realizada el día 16/04/2026. A partide de ahí se desarrollar un proceso personal de indagación e interpretación

PyMuPDF (de nombre import fitz) es una biblioteca Python para la manipulación de diferentes tipos de documento (vg. eBooks), entre los que destacan los documentos .pdf, que son sobre los que aquí se empleará.

Además de ciertas capacidades multiformato, permite la extracción de datos, manteniendo la estructura (columnas y párrafos) y el contenido (texto, tablas, imágenes y metadatos); también soporta la búsqueda de texto específico en el documento, la manipulación de páginas (insertar, rotar, eliminar o reordenar páginas), añadir elementos (anotaciones, marcas de agua, enlaces y formularios) y redactar (y eliminar) información sensible. En cuanto al trabajo con gráficos (renderizado) permite convertir páginas en imágenes.

Un módulo específico (PyMuPDF4LLM) permite la integración con la IA, facilitando la conversión de PDF en lenguajes de marcas (Markdown estructurado), muy útil para los modelos de lenguaje (LLM) y sistemas RAG.

Para trabajar con esta biblioteca necesitamos instalarla previamente pip install pymupdf, aunque te aconsejo que consultes estas páginas web, además de instalarla:

viernes, 17 de abril de 2026

DATOS. Tratamiento de datos

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.

jueves, 16 de abril de 2026

DATOS. Tratamiento de datos

Datos semi-estructurados PDF (I)

Tabla-Imagen

Con frecuencia una de las fuentes de datos estructurados son los documentos de texto, concretamente los documentos PDF. Realmente se trata de una fuente doblemente compleja: son realmente datos estructurados a nuestros ojos, pero precisan un tratamiento manual de tabulación (por ejemplo), pero están muy lejos de serlo para un ordenador, especialmente las "tablas" .pdf.

Antes de seguir y para que no queden dudas respecto al objetivo que se persigue aquí, diré que entiendo por manipulación o tratamiento de datos tabulados en documentos de texto el procedimiento cuyo objetivo último es la creación de estructuras asimilables a una tabla o base de datos que puede ser tratadamediante procedimientos de acceso y consulta a datos estructurados. Digo "objetivo último" porque realmente no siempre se puede garantizar su logro, al menos no de forma directa.

Para un tratamiento ofimático "clásico" no existe diferencia entre un documento .pdf y un documento .docx (o un documento .odt), aunque es posible manipular directamente estos últimos mediante procedimientos relativamente simples basados en los mismos servicios ofimáticos y/o en la combinación procesador-hoja de cálculo. Esta posibilidad se complica si las tablas están en un documento .pdf, pero puede ser una complicación salvable, especialmente si los datos no están sujetos a confidencialidad; si sí lo están va a depender de la suerte que tengamos en el procedo de captura de los datos: si es posible copia-pega, no vamos a tener más trabajo que si se tratara de tablas .docx (o casi), pero si no se dejan capturar de este modo no nos va a quedar más remedio que copiarlos manualmente o casi manualmente.

Pero la automatización se complica significativamente cuando la fuente radica en un documento .pdf, especialmente si estas tablas contienen información confidencial, que no es posible derivar a recursos especialidados, e incluso posiblemente las dificultades persistan en caso de optar por estos medios. De todas formas, en lo que a un SEO se refiere, ni por presupuesto ni por razones de confidencialidad podemos recurrir a ellos medios y la consecuencia es que o bien renunciamos a automatizar el acceso a estas tablas (lo más frecuente) o bien aplicamos procedimientos de automatización que sólo resuelven el problema de forma parcial y sin garantia de estar libres de error.

Deberemos, pues, estar dispuestos a realizar algún tipo de intervención ofimática complementaria como las que se explican en esta entrada. Gran parte de las dificultades se deben a la complejidad que para la digitalización tiene las tablas-pdf, las cuales que presentan diferentes comportamientos no siempre fáciles de predecir ni necesariamente invariables en el mismo documento. Es por ello que las tecnologías de automatización pueden fracasar total o parcialmente, sin que sea garantía de éxito ni la complejidad del recurso de automatización ni que sea actual.

Sobre estas cuestiones trabajaremos en posteriores entradas, pero en esta quiero presentar la opción que se viene a considerar como "el último recurso" y que resuelve parcialmente la automatización del acceso al contenido de las tablas. Me refiero a la conversión de éstas en una imagen y el posterior tratamiento de la imágen mediante técnicas OCR.

Aunque lo más ajustado a un procedimiento de automatización sería convertir automáticamente el .pdf en imagen, el coste de este procedimiento es suficientemente elevado en términos de recursos hardware y computacionales y de memoria como para que no me plantee esta opción como punto de partida, máxime cuando disponemos de un procedimiento tan sencillo como barato: crear manualmente la imagen mediante Imprimir pantalla, procedimiento este perfectamente funcional si la tarea afecta a un pequeño número de documentos. Otra cosa sería si el número de documentos fuera muy elevado y se requiera usar la fotocopiadora como scanner y replantear el trabajo como resposabilidad de equipo. En la imagen que sigue te muestro el resultado (anonimizado) de convertir en imagen la segunda tabla. De paso nos servirá para comprobar qué resulta al aplicar el procedimiento ocr

He de decir que, según IA-Gemnini existen opciones para automatizar esa captura de pantalla, pero no vamos a plantearlas ahora, optando por el procedimiento simple que presuponer un trabajo liviano sobre unos pocos documentos. De este modo nos podremos centrar en automatizar de la conversión de la tabla-imagen a texto mediante el siguiente script:


'''
Acceso a tablas convertidas en img manualmente mediante Imprimir pantalla
El script de Python usa pytesseract como OCR lo que implica tener instalado Tesseract
en el sistema.

Script desarrollado haciendo uso de IA-Gemini para mejoras de funcionamiento
implementadas a demanda específicada por el autor.

****************************************************************************************
--- FASES DEL PROCESO -------------------------
****************************************************************************************
'''

#0. Carga de bibliotecas ---------------------

import pytesseract
from PIL import Image
import os

# 1. Configuraciones y acceso a imagen -------------------------

# Configuración de Tesseract
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

# Configuración de las rutas de salida
nombre_carpeta = 'tablas'
nombre_archivo_txt = 'resultado_ocr.txt'

# Obtener la ruta del directorio donde se ejecuta el script
directorio_actual = os.path.dirname(os.path.abspath(__file__))
ruta_tablas = os.path.join(directorio_actual, nombre_carpeta)

# Crear la carpeta 'tablas' si no existe
if not os.path.exists(ruta_tablas):
    os.makedirs(ruta_tablas)

# Entrada nombre o ruta de img
nombre_imagen = input("Introduce el nombre del archivo de la imagen o su ruta completa (sin comillas): ").strip()

# 2. Procesamiento de OCR -----------------------------------

# Verificamos si el archivo existe antes de continuar
if not os.path.isfile(nombre_imagen):
    print(f"Error: El archivo '{nombre_imagen}' no se encuentra en la ruta especificada.")
else:
    try:
# Iniciamos el procesamiento OCR
        img = Image.open(nombre_imagen)
        texto = pytesseract.image_to_string(img, lang='spa')

# 4. Mostrar y guardar resultado -----------------------------

# Mostrar resultado en consola
        print("\nTexto extraído con éxito:")
        print("-" * 30)
        print(texto)
        print("-" * 30)

# Guardar resultado en archivo TXT usando el nombre de la imagen original para evitar sobreescritura
        nombre_base = os.path.splitext(os.path.basename(nombre_imagen))[0]
        ruta_final_txt = os.path.join(ruta_tablas, f"{nombre_base}.txt")
        
        with open(ruta_final_txt, 'w', encoding='utf-8') as f:
            f.write(texto)
        
        print(f"Archivo guardado en: {ruta_final_txt}")

    except Exception as e:
        print(f"Ocurrió un error al procesar la imagen: {e}")

Los comentarios que acompañan al script, redactados ex-profeso, hacen innecesario su comentario, pero no lo que resulta de su aplicación.

La ventaja que tiene este (que hemos llamado) "último recurso": la conversión de la tabla-pdf en imagen, es que, si la calidad de ésta es buena (y lo debe ser al proceder directamente del texto), el OCR funciona correctamente y transforma el contenido en texto accesible desde Bloc de notas y similares, sin errores ni omisiones. La imagen que sigue muestra la captura de pantalla de ese texto en lo que respecta a la misma tabla 2 que antes mostramos en pdf-img.

Puedes observar que realmente toda la información está ahí: las etiquetas de los campos, los datos, los que sí están (y que aquí ocultamos) y los que no. Ahora bien, esta imagen también muestrar de forma clara, que el resultado se asemeja más a lo que se ve, a la apariencia de lo que se ve que a los datos que muestra: realmente lo obtenido no es una estructura etiqueta-dato, son tres líneas que contienen ciertos textos, sin diferenciar qué papel juega cada uno de ellos, ni reconocer que falta una cuarta línea. Es evidente que estamos aun lejos de haber obtenido una tabla de datos o estructura que se le parezca; llegar a ello va a requerir un tratamiento específico a posteriori.

Esto hace más necesario aun guardar lo obtenido en, por ejemplo, un archivo de texto como .txt que nos permita darle ese tratamiento, al menos de momento mediante recursos ofimáticos, ya que es la forma más básica, cierto, pero también la solución más eficiente, rápida y segura disponible por ahora. Aquí, reitero, explico cómo.