Leer y generar archivos Excel con Python usando openpyxl
openpyxl es la biblioteca de referencia para trabajar con archivos Excel .xlsx (formato OOXML) en Python. Permite leer, escribir, formatear celdas, crear gráficos, aplicar fórmulas, trabajar con múltiples hojas y automatizar la generación de reportes complejos sin necesidad de tener Excel instalado.
Instalación
pip install openpyxl
# Para leer archivos con imágenes o compatibilidad extendida:
pip install openpyxl[optional]
Leer un archivo Excel existente
from openpyxl import load_workbook
# Cargar libro
wb = load_workbook("ventas.xlsx")
# Listar hojas
print("Hojas:", wb.sheetnames)
# Acceder a la hoja activa
ws = wb.active
# Leer datos
print(f"Dimensiones: {ws.dimensions}")
print(f"Filas con datos: {ws.max_row}")
print(f"Columnas con datos: {ws.max_column}")
# Leer celda individual
celda = ws['B3']
print(f"B3: {celda.value} (tipo: {celda.data_type})")
# Leer fila completa
for celda in ws[2]: # fila 2
print(f" {celda.coordinate}: {celda.value}")
# Iterar todas las filas con datos
for fila in ws.iter_rows(min_row=2, values_only=True):
fecha, producto, cantidad, precio = fila
print(f"{fecha} | {producto} | {cantidad} | {precio}")
Leer como lista de diccionarios
from openpyxl import load_workbook
def excel_a_diccionarios(ruta, hoja=None):
wb = load_workbook(ruta, read_only=True, data_only=True)
ws = wb[hoja] if hoja else wb.active
cabeceras = [celda.value for celda in next(ws.iter_rows(max_row=1))]
datos = []
for fila in ws.iter_rows(min_row=2, values_only=True):
if any(v is not None for v in fila): # saltar filas vacías
datos.append(dict(zip(cabeceras, fila)))
return datos
ventas = excel_a_diccionarios("ventas.xlsx")
for v in ventas[:3]:
print(v)
# {'fecha': datetime(2026, 1, 15), 'producto': 'Laptop', 'cantidad': 5, 'precio': 999.99}
Crear un nuevo archivo Excel
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from datetime import date
wb = Workbook()
ws = wb.active
ws.title = "Reporte de Ventas"
# ── Cabeceras con estilo ────────────────────────────────
cabeceras = ["Fecha", "Producto", "Categoría", "Cantidad", "Precio", "Total"]
for col, texto in enumerate(cabeceras, 1):
celda = ws.cell(row=1, column=col, value=texto)
celda.font = Font(bold=True, color="FFFFFF", size=11)
celda.fill = PatternFill(fill_type="solid", fgColor="2E5984")
celda.alignment = Alignment(horizontal="center", vertical="center")
# Ajustar altura de cabecera
ws.row_dimensions[1].height = 20
# ── Datos ────────────────────────────────────────────────
datos = [
(date(2026, 1, 10), "Laptop Pro", "Electrónica", 3, 1299.99),
(date(2026, 1, 11), "Monitor 4K", "Electrónica", 5, 549.00),
(date(2026, 1, 12), "Teclado Mec.", "Periféricos", 10, 89.95),
(date(2026, 1, 13), "Mouse Logitech","Periféricos", 15, 49.99),
(date(2026, 1, 14), "SSD 1TB", "Almacenamiento",8, 119.00),
]
# Colores alternos de fila
color_par = "EBF3FA"
color_impar = "FFFFFF"
for i, (fecha, producto, cat, cant, precio) in enumerate(datos, 2):
total = f"=D{i}*E{i}"
fila = [fecha, producto, cat, cant, precio, total]
color = color_par if i % 2 == 0 else color_impar
for j, valor in enumerate(fila, 1):
celda = ws.cell(row=i, column=j, value=valor)
celda.fill = PatternFill(fill_type="solid", fgColor=color)
celda.alignment = Alignment(vertical="center")
# ── Formatos de número ───────────────────────────────────
for fila in range(2, len(datos) + 2):
ws[f"E{fila}"].number_format = '"€"#,##0.00'
ws[f"F{fila}"].number_format = '"€"#,##0.00'
ws[f"A{fila}"].number_format = 'DD/MM/YYYY'
# ── Fila de totales ─────────────────────────────────────
fila_total = len(datos) + 2
ws[f"D{fila_total}"] = f"=SUM(D2:D{fila_total-1})"
ws[f"F{fila_total}"] = f"=SUM(F2:F{fila_total-1})"
ws[f"E{fila_total}"] = "TOTAL"
for col in [4, 5, 6]:
celda = ws.cell(row=fila_total, column=col)
celda.font = Font(bold=True)
celda.fill = PatternFill(fill_type="solid", fgColor="2E5984")
celda.font = Font(bold=True, color="FFFFFF")
# ── Anchos de columna automáticos ────────────────────────
anchos = [12, 20, 15, 10, 12, 12]
for col, ancho in enumerate(anchos, 1):
ws.column_dimensions[get_column_letter(col)].width = ancho
# ── Congelar primera fila ────────────────────────────────
ws.freeze_panes = "A2"
# ── Bordes ──────────────────────────────────────────────
borde_fino = Border(
left=Side(style='thin'), right=Side(style='thin'),
top=Side(style='thin'), bottom=Side(style='thin')
)
for fila in ws.iter_rows(min_row=1, max_row=fila_total, min_col=1, max_col=6):
for celda in fila:
celda.border = borde_fino
wb.save("reporte_ventas.xlsx")
print("Archivo Excel generado: reporte_ventas.xlsx")
Crear gráficos
from openpyxl import Workbook
from openpyxl.chart import BarChart, LineChart, PieChart, Reference
from openpyxl.chart.series import SeriesLabel
wb = Workbook()
ws = wb.active
ws.title = "Datos"
# Datos para el gráfico
datos = [
("Mes", "Ventas", "Objetivo"),
("Enero", 15000, 12000),
("Febrero", 18500, 14000),
("Marzo", 22000, 16000),
("Abril", 19000, 17000),
("Mayo", 25500, 18000),
("Junio", 28000, 20000),
]
for fila in datos:
ws.append(fila)
# ── Gráfico de barras ────────────────────────────────────
grafico_barras = BarChart()
grafico_barras.type = "col"
grafico_barras.title = "Ventas vs Objetivo 2026"
grafico_barras.y_axis.title = "Euros (€)"
grafico_barras.x_axis.title = "Mes"
grafico_barras.width = 18
grafico_barras.height = 12
# Serie de ventas
datos_ventas = Reference(ws, min_col=2, min_row=1, max_row=len(datos))
grafico_barras.add_data(datos_ventas, titles_from_data=True)
# Serie de objetivo
datos_obj = Reference(ws, min_col=3, min_row=1, max_row=len(datos))
grafico_barras.add_data(datos_obj, titles_from_data=True)
# Categorías (meses)
cats = Reference(ws, min_col=1, min_row=2, max_row=len(datos))
grafico_barras.set_categories(cats)
ws.add_chart(grafico_barras, "E2")
# ── Gráfico de líneas ────────────────────────────────────
ws2 = wb.create_sheet("Líneas")
for fila in datos:
ws2.append(fila)
grafico_lineas = LineChart()
grafico_lineas.title = "Tendencia de Ventas"
grafico_lineas.style = 10
grafico_lineas.y_axis.title = "Ventas (€)"
datos_l = Reference(ws2, min_col=2, min_row=1, max_row=len(datos))
grafico_lineas.add_data(datos_l, titles_from_data=True)
cats_l = Reference(ws2, min_col=1, min_row=2, max_row=len(datos))
grafico_lineas.set_categories(cats_l)
ws2.add_chart(grafico_lineas, "E2")
wb.save("graficos_ventas.xlsx")
print("Gráficos generados")
Trabajar con múltiples hojas
from openpyxl import Workbook, load_workbook
# Crear múltiples hojas
wb = Workbook()
meses = ["Enero", "Febrero", "Marzo", "Abril"]
for mes in meses:
ws = wb.create_sheet(title=mes)
ws.append(["Fecha", "Ventas", "Gastos", "Beneficio"])
# ... añadir datos
# Eliminar la hoja por defecto vacía
del wb["Sheet"]
# Hoja de resumen que referencia otras hojas
ws_resumen = wb.create_sheet("Resumen", 0) # posición 0 = primera
ws_resumen["A1"] = "Resumen Anual"
for i, mes in enumerate(meses, 2):
ws_resumen[f"A{i}"] = mes
ws_resumen[f"B{i}"] = f"={mes}!B2" # referencia a otra hoja
wb.save("reporte_anual.xlsx")
# Copiar hoja
from copy import copy
wb2 = load_workbook("reporte_anual.xlsx")
ws_orig = wb2["Enero"]
ws_copia = wb2.copy_worksheet(ws_orig)
ws_copia.title = "Enero_Copia"
wb2.save("con_copia.xlsx")
Aplicar validación de datos
from openpyxl import Workbook
from openpyxl.worksheet.datavalidation import DataValidation
wb = Workbook()
ws = wb.active
# Lista desplegable
dv_lista = DataValidation(
type="list",
formula1='"Electrónica,Ropa,Alimentación,Hogar,Deportes"',
allow_blank=True,
showDropDown=False,
showErrorMessage=True,
errorTitle="Valor inválido",
error="Selecciona una categoría de la lista"
)
ws.add_data_validation(dv_lista)
dv_lista.add(ws["C2:C100"]) # aplicar al rango
# Validación numérica (precio > 0)
dv_precio = DataValidation(
type="decimal",
operator="greaterThan",
formula1="0",
showErrorMessage=True,
errorTitle="Precio inválido",
error="El precio debe ser mayor que 0"
)
ws.add_data_validation(dv_precio)
dv_precio.add(ws["E2:E100"])
wb.save("formulario_validado.xlsx")
Reportes automáticos desde base de datos
import sqlite3
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill
from openpyxl.utils import get_column_letter
from datetime import datetime
def generar_reporte_sqlite(ruta_db, consulta, ruta_excel, titulo="Reporte"):
conn = sqlite3.connect(ruta_db)
cursor = conn.execute(consulta)
wb = Workbook()
ws = wb.active
ws.title = titulo[:30]
# Cabeceras
cabeceras = [desc[0] for desc in cursor.description]
for col, nombre in enumerate(cabeceras, 1):
c = ws.cell(row=1, column=col, value=nombre.replace('_', ' ').title())
c.font = Font(bold=True, color="FFFFFF")
c.fill = PatternFill(fill_type="solid", fgColor="1A5276")
# Datos
for fila_num, fila in enumerate(cursor.fetchall(), 2):
for col_num, valor in enumerate(fila, 1):
ws.cell(row=fila_num, column=col_num, value=valor)
# Anchos automáticos
for col in ws.columns:
max_len = max(
(len(str(c.value)) if c.value is not None else 0)
for c in col
)
ws.column_dimensions[get_column_letter(col[0].column)].width = min(max_len + 3, 40)
# Metadatos
wb.properties.creator = "Python openpyxl"
wb.properties.created = datetime.now()
conn.close()
wb.save(ruta_excel)
print(f"Reporte generado: {ruta_excel} ({ws.max_row - 1} filas)")
generar_reporte_sqlite(
"base_datos.db",
"SELECT fecha, producto, cantidad, precio FROM ventas WHERE año = 2026",
"reporte_2026.xlsx",
"Ventas 2026"
)
Recurso adicional
Para convertir archivos Excel a PDF, CSV, ODS u otros formatos sin necesidad de programar, usa KaijuConverter — gratis y sin registro.
Conversiones relacionadas
Conversiones frecuentes del catálogo: