▷ Manipular Excel con Python — Leer, escribir y formatear con `openpyxl 2026

Manipular Excel con Python — Leer, escribir y formatear con `openpyxl

Excel sigue dominando el mundo de los datos. En oficinas, contabilidad, ventas, marketing, recursos humanos — los .xlsx están por todos lados. Y en algún momento te toca automatizar el trabajo con ellos: leer 200 ficheros, generar un informe mensual, formatear una plantilla, exportar resultados de un script.

La buena noticia: Python lo hace fácil. La librería openpyxl te permite leer, escribir, formatear, añadir fórmulas y casi todo lo que harías a mano en Excel — pero programable, repetible y sin errores humanos.

En esta entrada te enseño los patrones que de verdad usas: leer datos, generar un fichero nuevo, copiar de plantilla, aplicar estilos básicos, fórmulas, y los errores típicos que te muerden cuando empiezas.

Instalar openpyxl

pip install openpyxl

💡 ¿Aún no tienes el reflejo de crear un venv antes? Mira entornos virtuales con venv. Cinco comandos que te ahorran meses de problemas.

openpyxl lee y escribe ficheros .xlsx (Excel moderno). No lee .xls antiguos (para eso necesitarías xlrd o convertir antes). Y no ejecuta macros VBA — solo manipula la hoja en sí.

Leer un Excel

Lo más típico: tienes un fichero con datos y los quieres procesar.

from openpyxl import load_workbook

wb = load_workbook("ventas.xlsx")
hoja = wb.active                       # primera hoja activa

print(hoja.title)                      # "Sheet1" o lo que se llame
print(hoja.max_row, hoja.max_column)   # 100 5  → 100 filas, 5 columnas

# Leer una celda concreta
print(hoja["A1"].value)
print(hoja.cell(row=2, column=3).value)  # equivalente a "C2"

# Recorrer filas
for fila in hoja.iter_rows(min_row=2, values_only=True):
    print(fila)
# (1, 'Camiseta', 19.99, '2026-04-01', 'tienda-online')
# (2, 'Pantalón', 39.99, '2026-04-02', 'tienda-fisica')
# ...

Dos modos de iterar:

  • hoja.iter_rows(values_only=True) te da tuplas con los valores.
  • hoja.iter_rows() (sin values_only) te da objetos Cell — útil cuando necesitas formato, fórmula, etc.

Acceder a hojas concretas

Si el fichero tiene varias hojas:

wb = load_workbook("informe.xlsx")

print(wb.sheetnames)                   # ["Resumen", "Detalle", "Datos"]

resumen = wb["Resumen"]
detalle = wb["Detalle"]

# Crear una hoja nueva
nueva = wb.create_sheet("Calculos", index=0)   # al principio

📥 Llévate el cheatsheet de Python (gratis)

PDF de 6 páginas con lo esencial: tipos, condicionales, bucles, estructuras de datos, funciones y los errores que más vas a cometer. Para tener al lado mientras programas.

Sin spam. Te apuntas a la lista, descargas el cheatsheet y recibes contenido de Python cada semana.

Crear un Excel desde cero

Caso común: tu script genera un informe.

from openpyxl import Workbook

wb = Workbook()
hoja = wb.active
hoja.title = "Ventas"

# Cabecera
hoja.append(["Producto", "Unidades", "Precio"])

# Datos
hoja.append(["Camiseta", 10, 19.99])
hoja.append(["Pantalón", 5, 39.99])
hoja.append(["Gorra", 8, 14.50])

wb.save("ventas_2026.xlsx")

hoja.append(lista) añade una fila al final. Es la forma más rápida de generar tablas.

💡 ¿Datos vienen de una API o JSON? Mira JSON en Python y luego los vuelcas con hoja.append() en bucle.

Escribir celdas concretas

Para puntos específicos:

hoja["A1"] = "Informe mensual"
hoja["B5"] = 1234.56
hoja.cell(row=10, column=2, value="Total")

hoja["A1"] = X y hoja.cell(row=R, column=C, value=X) son equivalentes.

Fórmulas

Sí, puedes meter fórmulas reales que Excel evalúa al abrir el fichero:

hoja["B2"] = 10
hoja["B3"] = 20
hoja["B4"] = 30
hoja["B5"] = "=SUM(B2:B4)"   # ← fórmula como string

wb.save("con_formulas.xlsx")

Cuando abras el .xlsx en Excel, B5 mostrará 60. Mientras el fichero esté solo en disco (sin abrirse en Excel), el valor calculado no está — solo la fórmula.

⚠️ Ojo: si lees hoja["B5"].value justo después de escribirlo en Python, te devolverá el string "=SUM(B2:B4)", no 60. Para leer valores calculados de un fichero ya abierto y guardado en Excel, necesitas load_workbook("archivo.xlsx", data_only=True).

Estilos: poner bonita la salida

Esto es donde openpyxl empieza a hacer cosas que ahorran horas a mano:

from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side

wb = Workbook()
hoja = wb.active

hoja.append(["Producto", "Unidades", "Precio"])

# Cabecera en negrita, fondo amarillo, centrada
for celda in hoja[1]:
    celda.font = Font(bold=True, color="000000")
    celda.fill = PatternFill("solid", fgColor="FFD539")
    celda.alignment = Alignment(horizontal="center")

# Datos
hoja.append(["Camiseta", 10, 19.99])
hoja.append(["Pantalón", 5, 39.99])

# Anchura de columnas
hoja.column_dimensions["A"].width = 20
hoja.column_dimensions["B"].width = 12
hoja.column_dimensions["C"].width = 12

wb.save("ventas_estilo.xlsx")

Tres clases que cubren el 95% de los casos:

  • Font(bold, italic, color, size) — fuente.
  • PatternFill("solid", fgColor=...) — fondo de celda.
  • Alignment(horizontal, vertical) — alineación.

Los colores van en hex sin #: "FFD539", "3781A9".

Casos reales típicos

Combinar varios CSV en un solo Excel con varias hojas

from openpyxl import Workbook
import csv
from pathlib import Path

wb = Workbook()
wb.remove(wb.active)   # quita la hoja por defecto

for csv_path in Path("datos").glob("*.csv"):
    hoja = wb.create_sheet(csv_path.stem)   # nombre de hoja = nombre fichero sin ext
    with csv_path.open(encoding="utf-8") as f:
        for fila in csv.reader(f):
            hoja.append(fila)

wb.save("consolidado.xlsx")

💡 csv_path.stem te da el nombre del fichero sin extensión. Para esto y otras operaciones con rutas: pathlib vs os.path.

Filtrar filas y exportar a Excel

from openpyxl import load_workbook, Workbook

origen = load_workbook("ventas.xlsx")["Datos"]
destino_wb = Workbook()
destino = destino_wb.active

# Cabecera
destino.append([c.value for c in origen[1]])

# Solo filas con precio > 50
for fila in origen.iter_rows(min_row=2, values_only=True):
    if fila[2] and fila[2] > 50:
        destino.append(fila)

destino_wb.save("ventas_premium.xlsx")

Generar resumen con totales y formato de moneda

from openpyxl import Workbook
from openpyxl.styles import Font

wb = Workbook()
hoja = wb.active

hoja.append(["Producto", "Cantidad", "Precio", "Total"])
datos = [("Camiseta", 10, 19.99), ("Pantalón", 5, 39.99), ("Gorra", 8, 14.50)]

for nombre, cantidad, precio in datos:
    hoja.append([nombre, cantidad, precio, f"=B{hoja.max_row + 1}*C{hoja.max_row + 1}"])

# Ojo al cálculo del número de fila al meter la fórmula:
# hoja.max_row + 1 → la fila que se va a crear con append()

# Total general en última fila
fila_total = hoja.max_row + 1
hoja.cell(row=fila_total, column=1, value="TOTAL")
hoja.cell(row=fila_total, column=4, value=f"=SUM(D2:D{hoja.max_row})")

# Formato negrita en total
for celda in hoja[fila_total]:
    celda.font = Font(bold=True)

# Formato de número (moneda)
for celda in hoja["C"]:
    celda.number_format = "#,##0.00 €"
for celda in hoja["D"]:
    celda.number_format = "#,##0.00 €"

wb.save("resumen.xlsx")

number_format es la cadena de formato de Excel. Algunos útiles:

  • "#,##0" — entero con separador de miles
  • "#,##0.00" — dos decimales
  • "#,##0.00 €" — con símbolo de euro
  • "0.00%" — porcentaje
  • "yyyy-mm-dd" — fecha ISO

Errores típicos al usar openpyxl

# 1. Confundir filas-1-indexadas con 0-indexadas
hoja.cell(row=0, column=0)   # ❌ openpyxl es 1-indexado, no 0
hoja.cell(row=1, column=1)   # ✓

# 2. Olvidar wb.save() — no se escribe nada en disco
wb = Workbook()
hoja = wb.active
hoja["A1"] = "hola"
# ❌ falta wb.save("file.xlsx")

# 3. Reabrir fichero con fórmulas y leer string en vez de valor
wb = load_workbook("con_formula.xlsx")
print(wb.active["B5"].value)   # "=SUM(B2:B4)" si Excel no lo recalculó

# Si quieres el valor calculado:
wb = load_workbook("con_formula.xlsx", data_only=True)
print(wb.active["B5"].value)   # 60   (siempre que el fichero se haya abierto y guardado en Excel)

# 4. Fechas y horas en pandas/datetime mezcladas con strings
hoja["A1"] = "2026-04-19"   # se guarda como string
from datetime import date
hoja["A2"] = date(2026, 4, 19)   # se guarda como fecha real de Excel ✓

# 5. Hojas con nombre con caracteres prohibidos
wb.create_sheet("Datos:Mes")   # ❌ Excel no permite : en nombres

# 6. Pasar pandas DataFrame directamente a hoja.append
hoja.append(df)   # ❌ append espera lista, no DataFrame
for _, fila in df.iterrows():
    hoja.append(fila.tolist())   # ✓

# o para esto, mejor:
df.to_excel("salida.xlsx", index=False)   # pandas sabe escribir Excel directamente

💡 Si tu mundo es datos, lo más productivo suele ser usar pandas para procesar y to_excel/read_excel para entrada/salida, y solo bajar a openpyxl cuando necesites formato granular o macros raras.

Resumen

Operación Sintaxis
Abrir load_workbook("file.xlsx")
Crear nuevo Workbook()
Hoja activa wb.active
Hoja por nombre wb["Nombre"]
Celda por coord hoja["A1"] o hoja.cell(row, col)
Iterar filas hoja.iter_rows(values_only=True)
Añadir fila hoja.append([...])
Estilo Font, PatternFill, Alignment
Formato número celda.number_format = "#,##0.00 €"
Anchura columna hoja.column_dimensions["A"].width = 20
Guardar wb.save("salida.xlsx")
Leer valores calculados load_workbook(..., data_only=True)

¿Te ha valido esto?

Si te ha resultado útil, llévate el cheatsheet de Python en PDF — 6 páginas con tipos, condicionales, bucles, estructuras de datos, funciones y los errores típicos. Para tener al lado mientras programas. Gratis.

Sin spam. Email + cheatsheet + un correo por semana con tutoriales nuevos.

Tu siguiente paso

Si has llegado hasta aquí, ya puedes automatizar el 80% de las cosas que se hacen a mano en Excel: generar informes, consolidar varios ficheros, aplicar formato consistente, calcular totales. Es una de las habilidades que en cualquier oficina te convierte en “ese que hace cosas que parecen magia”.

Si quieres aprender Python desde la base hasta proyectos reales con archivos, datos y APIs, en El Pythonista lo enseño paso a paso.

Un abrazo,
Oscar

¿Quieres aprender Python en orden, no a saltos?

Esto que has leído es solo una pieza. En El Pythonista lo verás todo encadenado: 11 módulos, 37+ horas de vídeo, 734 actividades y un proyecto real (MovieTracker) que crece contigo desde la primera variable hasta el deploy a producción.

Ver el curso completo →

37+ horas · 734 actividades · Proyecto real · Acceso de por vida · 14 días de garantía

Compartir

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicar un comentario