Tablas Word (.docx) (I)
Acceso a tablas simples
El trabajo con textos .docx ya ha sido tratado en otras entradas de este blog, pero desde la prespectiva de la composición de textos. Ahora nos interesa el proceso inverso: la obtención de datos. Empezaremos por la identificación de tablas simples (estructura tabular básica) y la obtención de los datos que contienen.
El documento que nos sirve de modelo no tiene más que ese interés. Obviamente los datos son inventados y carecen de relevancia para cualquier otro objetivo. Se trata de un texto en soporte .docx, que contiene unos breves párrafos de texto (datos no estructurados) y tres tablas de estructura simple (asimilables al formato BD: fila (registro) - columna (campo/variable), que se comportan inicialmente como datos semi-estructurados, aunque sólo en función del soporte (de presentarse en un formato estructurado en campos podrían considerarse datos estructurados).
El primer objetivo que nos proponemos ahora es automatizar identificación de las tablas (obviando el texto) y la extracción de los datos que contienen. Para ello utilizamos el siguiente script Python:
# 0. Importamos las bibliotecas necesarias
import pandas as pd
from docx import Document
import warnings # Importamos el módulo de avisos
# 1. Instrucción para ignorar todos los avisos técnicos de las librerías
warnings.filterwarnings("ignore")
# 2. Función para identificación de los tipos de datos para posterior tratamiento
def identificar_tipos_datos(df): # Se asume la posibilidad de distintos tipos de datos (fechas y numéricos)
for col in df.columns:
intentar_fecha = pd.to_datetime(df[col], errors='coerce') # Fechas
if intentar_fecha.notna().all():
df[col] = intentar_fecha
continue
intentar_num = pd.to_numeric(df[col], errors='coerce') # Numéricos
if intentar_num.notna().sum() > len(df) * 0.8:
df[col] = intentar_num
return df
# 3. Función para la extracción de datos de las tablas-docx
def extractor_universal(ruta_docx):
try:
doc = Document(ruta_docx)
for i, tabla in enumerate(doc.tables):
datos = []
for fila in tabla.rows:
datos.append([celda.text.strip() for celda in fila.cells])
if not datos: continue
cabecera = datos[0]
cuerpo = datos[1:]
df = pd.DataFrame(cuerpo, columns=cabecera)
df_estructurado = identificar_tipos_datos(df) # Llamada a la función 2
print(f"\n--- TABLA {i+1} ---") # Visualización de tablas en el CMD
print(df_estructurado.to_string(index=False))
print("-" * 40)
except Exception as e:
print(f"Error en el proceso: {e}")
# 4. Ejecución de la función principal
extractor_universal("tablas.docx")
Las pretensiones de este script son muchas, a pesar de lo limitado de su objetivo: se trata de:
- Identificar las tablas presentes en el documento. Al tratarse de un documento docx se hace uso de la biblioteca
python-docx - Se pretende crear un script de caracter universal, de ahí que se esperen distintos tipos de datos, los cuales se desean identificar para un posterior tratamiento que no se da en este caso. Esto explica la presencia de la función
def identificar_tipos_datos(df)que se relaciona con el uso de la biblitecaimport pandas as pd - Esto hace posible que se generen avisos no deseados y que carecen de utilidad inmediata, por eso se importa el módulo de control de avisos
import warningsy se inhibe su aparición en el CMD conwarnings.filterwarnings("ignore"), procedimiento que en otras ocasiones puede ser contraproducente, pero no en esta, ya que en realidad conocemos el contenido de las tablas
El núcleo central del script se plantea dentro del bucle for i, tabla in enumerate(doc.tables):, que es donde se obtiene el recuento de las tablas del documento, se recorren éstas, se extrae su contenido y se procesa, incluyendo su conversión de texto a diferentes tipos de datos. Además, finalmente, se muestran por pantalla.
Dado que con esto finaliza el script, se pierden en realidad posibilidades de tratamiento que sí están disponibles en función de la conversión de datos y de la participación de Pandas; pero ahora lo que nos interesaba era la obtención de los datos, sólo los datos de las tablas, y este objetivo está conseguido. En posteriores entradas profundizaremos en el desarrollo de este proceso.