Mostrando entradas con la etiqueta PyMuPDF. Mostrar todas las entradas
Mostrando entradas con la etiqueta PyMuPDF. 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.