Scripts de Python para Unir Archivos de Excel

Últimamente me ha tocado trabajar con muchos reportes en Excel: descargarlos, unirlos en una sola hoja, organizarlos en diferentes pestañas o combinarlos entre sí. Hacerlo a mano suele ser tardado y, la verdad, me da un poco de flojera 😅. Por eso prefiero resolverlo con scripts sencillos en Python: rápidos, efectivos y fáciles de reutilizar. Aquí te comparto 3 casos prácticos que uso mucho y que te pueden ahorrar bastante tiempo:

  1. Unir muchos archivos en una sola hoja (apilar filas).
  2. Unir varias hojas de un mismo libro.
  3. Combinar por una llave (tipo VLOOKUP/BuscarV).

Requisitos

  • Python 3.10+
  • Librerías: pandas y openpyxl

Instala en Windows/Mac/Linux: post que te guía paso a paso

Bash
pip install pandas openpyxl

Tip Windows: si pip no funciona, prueba py -m pip install pandas openpyxl.
Si no tienes Python, descárgalo desde python.org (marca “Add Python to PATH” al instalar).

1) Unir muchos archivos en una sola hoja

¿Cuándo usarlo?

Tienes carpetas con reportes mensuales/diarios con las mismas columnas y quieres un solo archivo consolidado.

Script: unir_archivos_en_una_hoja.py

  • Lee todos los .xlsx de una carpeta
  • Toma una hoja (por nombre o índice)
  • Agrega columna _archivo_origen para rastrear
  • Normaliza encabezados
Python
# unir_archivos_en_una_hoja.py
import pandas as pd
from pathlib import Path

CARPETA = Path("data_excel")        # carpeta con tus .xlsx
HOJA = "Hoja1"                      # o índice: 0
SALIDA = "consolidado.xlsx"

def normaliza_cols(cols):
    return [str(c).strip().lower().replace(" ", "_") for c in cols]

dfs = []
for x in CARPETA.glob("*.xlsx"):
    try:
        df = pd.read_excel(x, sheet_name=HOJA)  # o sheet_name=0
        df.columns = normaliza_cols(df.columns)
        df["_archivo_origen"] = x.name
        dfs.append(df)
    except Exception as e:
        print(f"[WARN] No pude leer {x.name}: {e}")

if not dfs:
    raise SystemExit("No encontré archivos .xlsx o no se pudieron leer.")

consolidado = pd.concat(dfs, ignore_index=True)

# Opcional: elimina duplicados por columnas clave
# consolidado = consolidado.drop_duplicates(subset=["folio", "fecha"])

consolidado.to_excel(SALIDA, index=False)
print(f"Listo: {SALIDA} ({len(consolidado)} filas)")

Cómo usar:

  1. Crea la carpeta data_excel y mete tus .xlsx.
  2. Ajusta HOJA si tus archivos usan otro nombre de hoja.
  3. Corre: python unir_archivos_en_una_hoja.py.

2) Unir varias hojas de un mismo libro

¿Cuándo usarlo?

Tienes un solo archivo con datos repartidos en varias hojas y quieres todo en una.

Script: unir_hojas_de_un_libro.py

  • Lee todas las hojas de un libro
  • Apila todo en una sola tabla
  • Agrega columna _hoja_origen
Python
# unir_hojas_de_un_libro.py
import pandas as pd

ENTRADA = "reporte_multihojas.xlsx"
SALIDA = "reporte_unico.xlsx"

xls = pd.ExcelFile(ENTRADA)
dfs = []
for hoja in xls.sheet_names:
    df = pd.read_excel(xls, sheet_name=hoja)
    df.columns = [str(c).strip().lower().replace(" ", "_") for c in df.columns]
    df["_hoja_origen"] = hoja
    dfs.append(df)

unico = pd.concat(dfs, ignore_index=True)
unico.to_excel(SALIDA, index=False)
print(f"Listo: {SALIDA} ({len(unico)} filas, {len(xls.sheet_names)} hojas)")

3) Combinar por llave (tipo VLOOKUP)

¿Cuándo usarlo?

Tienes dos tablas: por ejemplo, pagos y alumnos. Quieres unirlas por una clave (ej. matricula).

Script: combinar_por_llave.py

  • Une left (izquierda conserva todo) o inner (solo coincidencias)
  • Normaliza tipos de datos para evitar “no coincide”
Python
# combinar_por_llave.py
import pandas as pd

IZQ = "pagos.xlsx"      # tabla base
DER = "alumnos.xlsx"    # tabla a cruzar
HOJA_IZQ = 0
HOJA_DER = 0
LLAVE_IZQ = "matricula"
LLAVE_DER = "matricula"
MODO = "left"           # left / inner / right / outer
SALIDA = "pagos_con_datos.xlsx"

def limpia(df):
    df = df.copy()
    df.columns = [str(c).strip().lower().replace(" ", "_") for c in df.columns]
    return df

df_izq = limpia(pd.read_excel(IZQ, sheet_name=HOJA_IZQ))
df_der = limpia(pd.read_excel(DER, sheet_name=HOJA_DER))

# Normaliza llave a string (muy importante)
df_izq[LLAVE_IZQ] = df_izq[LLAVE_IZQ].astype(str).str.strip()
df_der[LLAVE_DER] = df_der[LLAVE_DER].astype(str).str.strip()

unido = pd.merge(
    df_izq, df_der,
    left_on=LLAVE_IZQ, right_on=LLAVE_DER,
    how=MODO, validate="m:1"  # cada pago mapea a un alumno
)

unido.to_excel(SALIDA, index=False)
print(f"Listo: {SALIDA} ({len(unido)} filas)")

Si tu llave viene con ceros a la izquierda (ej. “00123”), mantenerla como texto evita que Excel los quite.

Errores comunes y cómo evitarlos

  • “Worksheet name is invalid” → revisa sheet_name correcto.
  • Llave no coincide → normaliza tipo str y strip().
  • Duplicados inesperados → revisa validate="m:1" o 1:1 en merge para detectar errores.
  • Celdas vacías → usa fillna("") o dropna(subset=["col"]).
  • Encoding raro en CSV → usa encoding="utf-8-sig".

Script “todo en uno” por argumentos

Útil si quieres correrlo con parámetros sin editar el archivo.

Python
# excel_cli.py
import argparse, sys
from pathlib import Path
import pandas as pd

def normaliza_cols(cols): return [str(c).strip().lower().replace(" ", "_") for c in cols]

def unir_archivos(carpeta, hoja, salida):
    dfs=[]
    for x in Path(carpeta).glob("*.xlsx"):
        try:
            df = pd.read_excel(x, sheet_name=hoja)
            df.columns = normaliza_cols(df.columns)
            df["_archivo_origen"] = x.name
            dfs.append(df)
        except Exception as e:
            print(f"[WARN] {x.name}: {e}")
    if not dfs: sys.exit("Sin data.")
    pd.concat(dfs, ignore_index=True).to_excel(salida, index=False)

def unir_hojas(entrada, salida):
    xls = pd.ExcelFile(entrada)
    dfs=[]
    for hoja in xls.sheet_names:
        df = pd.read_excel(xls, sheet_name=hoja)
        df.columns = normaliza_cols(df.columns)
        df["_hoja_origen"] = hoja
        dfs.append(df)
    pd.concat(dfs, ignore_index=True).to_excel(salida, index=False)

def combinar(izq, der, hoja_izq, hoja_der, llave_izq, llave_der, how, salida):
    def limpia(df):
        df = df.copy(); df.columns = normaliza_cols(df.columns); return df
    a = limpia(pd.read_excel(izq, sheet_name=hoja_izq))
    b = limpia(pd.read_excel(der, sheet_name=hoja_der))
    a[llave_izq] = a[llave_izq].astype(str).str.strip()
    b[llave_der] = b[llave_der].astype(str).str.strip()
    pd.merge(a,b,left_on=llave_izq,right_on=llave_der,how=how,validate="m:1").to_excel(salida, index=False)

if __name__ == "__main__":
    p = argparse.ArgumentParser(description="Utilidades para unir Excel")
    sub = p.add_subparsers(dest="cmd", required=True)

    a = sub.add_parser("apilar", help="Unir varios archivos en una hoja")
    a.add_argument("--carpeta", required=True)
    a.add_argument("--hoja", default=0)
    a.add_argument("--salida", default="consolidado.xlsx")

    b = sub.add_parser("unir_hojas", help="Unir todas las hojas de un libro")
    b.add_argument("--entrada", required=True)
    b.add_argument("--salida", default="reporte_unico.xlsx")

    c = sub.add_parser("combinar", help="Combinar por llave (merge)")
    c.add_argument("--izq", required=True)
    c.add_argument("--der", required=True)
    c.add_argument("--hoja_izq", default=0)
    c.add_argument("--hoja_der", default=0)
    c.add_argument("--llave_izq", required=True)
    c.add_argument("--llave_der", required=True)
    c.add_argument("--how", default="left", choices=["left","inner","right","outer"])
    c.add_argument("--salida", default="salida.xlsx")

    args = p.parse_args()
    if args.cmd == "apilar":
        unir_archivos(args.carpeta, args.hoja, args.salida)
    elif args.cmd == "unir_hojas":
        unir_hojas(args.entrada, args.salida)
    elif args.cmd == "combinar":
        combinar(args.izq, args.der, args.hoja_izq, args.hoja_der, args.llave_izq, args.llave_der, args.how, args.salida)