Archivos .docx
Acceso a tablas y textos
Dado que ya hemos hablado de los archivos de texto .docx (por ejemplo, aquí), resulta un poco complicado plantear esta entrada sin que resulte repetitiva, aunque el intento no carece de sentido. En aquel momento hablamos de los documentos .docx desde una perspectiva de generación de los mismos (aunque se trataran también otros temas), y ahora nuestro interés se centra en la obtención de datos de documentos ya creados, fundamentalmente datos textuales y contenidos contenidos en tablas, lo que equivale a decir datos no estructurados y datos que aspiran a ser considerados como datos estructurados, pero que aun no alcanzan este nivel de organización (datos semi-estructurados).
La biblioteca python-docx, ya presentada en relación con la temática antes señalada será también la herramienta que nos ayude a acceder al contenido de los documentos .docx, que son una evolución compleja de los .doc primitivos, implementando la arquitectura XML, lo que facilita el manejo del contenido mediate script Python, que se hace precisamente manejando esos componentes xml.
A nosotros lo que realmente nos importa ahora, desde la perspectiva de esta subsección, es el acceso al contenido, diferenciando si este se presenta en formato de tabla (mejor tabla-formulario), que, como dije, se considera como dato semi-estructurado, aunque no siempre lo sea, frente al texto no-tabla (párrafos), que se consideran datos no estructurados. Respecto a las tablas, dado que también se emplean para formatear la presentación del contenido, en algunas ocasiones éste no es en realidad un dato semi-estructurado; se trata de un segmento textual no estructuardo y como tal debe ser tratado. A veces esta diferencia es de detalle, lo que da lugar a confusiones en el planeamiento de cómo debe ser tratado en los procesos de limpieza y de análisis.
No es esta una cuestión que sólo afecte a los documentos .docx, pero lo trataremos aquí para avanzar propuestas, dado que es una cuestión de importancia dentro de la subsección en la que nos encontramos en este punto.Pero antes de abordar esta cuestión resolvamos primero las más básicas: el acceso al contenido en su conjunto y el acceso al contenido de forma diferenciada: tablas y textos.
import docx
import os
def leer_contenido_docx(ruta_archivo):
if not os.path.exists(ruta_archivo): # Verificar si el archivo existe
print(f"Error: El archivo '{ruta_archivo}' no existe.")
return
doc = docx.Document(ruta_archivo) # Cargar el documento
print(f"--- INICIO DEL CONTENIDO: {ruta_archivo} ---\n")
for elemento in doc.element.body: # Acceso secuencial a todos los elementos del cuerpo del documento
if isinstance(elemento, docx.oxml.text.paragraph.CT_P): # 1. Si el elemento es un párrafo (CT_P)
p = docx.text.paragraph.Paragraph(elemento, doc)
if p.text.strip(): # Evitar mostrar líneas vacías si se desea
print(f"[PÁRRAFO]: {p.text}")
elif isinstance(elemento, docx.oxml.table.CT_Tbl): # 2. Si el elemento es una tabla (CT_Tbl)
tabla = docx.table.Table(elemento, doc)
print(f"\n[TABLA INICIO - {len(tabla.rows)} filas x {len(tabla.columns)} columnas]")
for fila in tabla.rows:
contenido_fila = [celda.text.strip() for celda in fila.cells] # Extraemos el texto de cada celda
print(" | ".join(contenido_fila))
print("[TABLA FIN]\n")
print(f"\n--- FIN DEL DOCUMENTO ---")
# --- Llamada a la función ---
if __name__ == "__main__":
nombre_archivo = r"Directorio_de_mi_archivo.docx" # Aquí la ruta de tu_archivo.docx'
leer_contenido_docx(nombre_archivo)
Este script permite acceder a un documento .docx, identifica su contenido, diferenciando tablas de párrafos y muestra todo ese contenido en el CMD señalando la diferencia entre el procedente de una tabla del identificado como párrafo. Esta diferenciación ya señala la posibilidad de acceder a datos semi-estructurado (tablas y tablas-formulario) y a datos no estructurados (párrafos) de forma diferenciada. El siguiente script permite el acceso a las tablas del documento y a su contenido. A partir de aquí podríamos desarrollar procedimientos específicos de acceso a datos concretos. La idea a posteriori es facilitar la transformación de estos datos semi-estructurados en datos estructurados, para tratarlo con los procedimiento del organización y limpieza, y de análisis acordes con los datos de esa naturaleza.
import docx
import os
#Función principal: exploración de las tablas del documento
def leer_tablas_docx(ruta_archivo):
if not os.path.exists(ruta_archivo):
print(f"Error: El archivo '{ruta_archivo}' no existe.")
return
doc = docx.Document(ruta_archivo)
print(f"--- RELACIÓN DE TABLAS ENCONTRADAS ---")
tablas_limpias = []
for i, tabla in enumerate(doc.tables):
# Extraemos el nombre/título desde las propiedades XML de la tabla (tblPr)
nombre_tabla = None
try:
# Buscamos en el elemento XML de la tabla el tag de título (tblCaption)
nombre_tabla = tabla._element.xpath('.//w:tblCaption/@w:val')
if nombre_tabla:
nombre_tabla = nombre_tabla[0]
else:
# Intentamos con la descripción si el título falla
nombre_tabla = tabla._element.xpath('.//w:tblDescription/@w:val')
if nombre_tabla: nombre_tabla = nombre_tabla[0]
except:
nombre_tabla = None
if not nombre_tabla:
nombre_tabla = f"Tabla_{i+1}"
tablas_limpias.append((nombre_tabla, tabla))
print(f"ID: {i+1} | Nombre en Navegador: {nombre_tabla}")
print("-" * 40 + "\n")
for nombre, tabla in tablas_limpias: # Mostramos el contenido de las tablas
print(f"\n[CONTENIDO TABLA]: {nombre}")
print("-" * (18 + len(nombre)))
for fila in tabla.rows:
textos_celdas = [] # Usamos un set para evitar celdas duplicadas en caso de celdas combinadas
for celda in fila.cells:
texto = celda.text.strip().replace('\n', ' ')
textos_celdas.append(texto)
print(" | ".join(textos_celdas))
print("-" * 20)
# --- Llamada a la función ---
if __name__ == "__main__":
nombre_archivo = r"ruta_de_tu_archivo.docx" # Aquí tu ruta de archivo
leer_tablas_docx(nombre_archivo) # Llamada a la función
Mediante este otro script mostramos los párrafos (datos no estructurados), los cuales posteriormente deberan ser tratados con los procedimientos correspondiente. Es interesante observar que cada párrafo queda identificado por su posición, lo que puede facilitar su localización, aunque como procedimiento es poco funcional si pensamos en una obtención masiva de determinado contenido. De momento, algo es algo.
import docx
import os
def leer_texto_no_tabular(ruta_archivo):
if not os.path.exists(ruta_archivo): # Verificar si el archivo existe
print(f"Error: El archivo '{ruta_archivo}' no existe.")
return
doc = docx.Document(ruta_archivo) # Cargar el documento
print(f"--- INICIO DEL CONTENIDO NO TABULAR: {ruta_archivo} ---\n")
contador_parrafos = 0 # doc.paragraphs solo itera por los párrafos
for i, para in enumerate(doc.paragraphs):
texto = para.text.strip() # Limpiamos espacios en blanco al inicio y final
if texto: # Omitir párrafos vacíos para una salida más limpia en CMD
contador_parrafos += 1
print(f"[{contador_parrafos}]: {texto}") # Mostramos el índice del párrafo para facilitar el seguimiento
print("-" * 10) # Separador visual opcional
if contador_parrafos == 0:
print("No se encontró texto fuera de tablas en este documento.")
print(f"\n--- FIN DEL DOCUMENTO (Total párrafos: {contador_parrafos}) ---")
# --- Llamada a la función ---
if __name__ == "__main__":
nombre_archivo = r"ruta_de_mi_documento.docx" # Aquí tu archivo
leer_texto_no_tabular(nombre_archivo) # Llamada a la función






