Archivos CSV en Python
En esta lección
El CSV es el formato más sencillo para mover tablas de datos de un programa a otro: lo exportan Excel, Google Sheets, las tiendas online, los bancos y casi cualquier aplicación de gestión. En ficheros y JSON viste cómo leer y escribir un CSV básico. En esta lección vas a trabajar con CSV de verdad: entender sus reglas, procesar los datos (sumar, filtrar, agrupar), guardar resultados y abrir sin problemas los CSV que genera Excel en español.
Qué es un CSV
CSV significa comma-separated values, valores separados por comas. Es un archivo de texto plano en el que:
- Cada línea es una fila de la tabla.
- Las columnas se separan con un carácter llamado separador o delimitador, normalmente la coma.
- La primera fila suele ser la cabecera, con los nombres de las columnas.
fecha,tienda,producto,unidades,precio
2026-03-02,Santander,"Cuerda 9,8 mm",2,119.90
2026-03-02,Torrelavega,Pies de gato,1,89.00
Si un valor contiene el separador (como la coma de “9,8 mm”), se rodea de comillas dobles. Y si contiene comillas, se escriben dobles: "Modelo ""Pro""" significa Modelo "Pro". Por eso nunca debes leer un CSV con linea.split(","): rompería la cuerda en dos columnas. El módulo csv conoce estas reglas y las aplica por ti.
El punto y coma de Excel en español
En España la coma se usa como separador decimal (119,90). Para no liarse, Excel en español guarda los CSV con punto y coma como separador:
fecha;tienda;producto;unidades;precio
2026-03-02;Santander;Cuerda 9,8 mm;2;119,90
Más adelante verás cómo leer y escribir este formato.
Preparar los datos de ejemplo
Vamos a trabajar con las ventas de una cadena de tiendas de montaña. Para que el ejemplo funcione en cualquier sitio (también aquí en la web, que tiene su propio sistema de archivos virtual), primero creamos el archivo desde Python:
datos = """fecha,tienda,producto,categoria,unidades,precio
2026-03-02,Santander,"Cuerda 9,8 mm",escalada,2,119.90
2026-03-02,Torrelavega,Pies de gato,escalada,1,89.00
2026-03-03,Santander,Mochila 30 L,senderismo,3,54.50
2026-03-03,Laredo,Bastones,senderismo,4,32.00
2026-03-04,Torrelavega,Arnés,escalada,2,65.00
2026-03-04,Santander,Botas,senderismo,1,140.00
2026-03-05,Laredo,Pies de gato,escalada,2,89.00
"""
with open("ventas.csv", "w", encoding="utf-8", newline="") as f:
f.write(datos)
Leer con csv.reader
csv.reader recibe un archivo abierto y devuelve las filas una a una, cada una como una lista de cadenas:
import csv
with open("ventas.csv", encoding="utf-8", newline="") as f:
lector = csv.reader(f)
cabecera = next(lector) # la primera fila
print(cabecera)
for fila in lector:
print(fila)
break # solo la primera fila de datos
# ['fecha', 'tienda', 'producto', 'categoria', 'unidades', 'precio']
# ['2026-03-02', 'Santander', 'Cuerda 9,8 mm', 'escalada', '2', '119.90']
Fíjate en tres cosas:
newline="": deja que el módulocsvgestione los saltos de línea. Sin él, un valor entre comillas que contenga un salto de línea se lee mal, y al escribir en Windows aparecen filas en blanco entre medias.encoding="utf-8": para que las tildes y la ñ (“Arnés”) se lean bien en cualquier sistema.- Todo llega como texto:
'2'y'119.90'son cadenas. Para operar hay que convertirlas conint()yfloat().
next(lector) saca la cabecera antes del bucle, para que el for solo recorra datos.
DictReader: filas como diccionarios
Acceder por posición (fila[4]) es frágil: si alguien añade una columna, todos los índices cambian. csv.DictReader usa la cabecera como claves y te da cada fila como un diccionario:
with open("ventas.csv", encoding="utf-8", newline="") as f:
ventas = list(csv.DictReader(f))
print(len(ventas)) # 7
print(ventas[1]["producto"]) # Pies de gato
print(ventas[1])
# {'fecha': '2026-03-02', 'tienda': 'Torrelavega', 'producto': 'Pies de gato', 'categoria': 'escalada', 'unidades': '1', 'precio': '89.00'}
Convertimos list(...) dentro del with porque, al cerrarse el archivo, el lector ya no puede leer más. Para archivos pequeños y medianos, cargar todo en una lista es lo más cómodo. Para archivos enormes (millones de filas), es mejor procesar fila a fila dentro del for sin guardarlas todas.
Convertir los tipos una sola vez
Lo más limpio es convertir los valores numéricos nada más leer, en lugar de llenar el programa de float():
for v in ventas:
v["unidades"] = int(v["unidades"])
v["precio"] = float(v["precio"])
print(ventas[0]["unidades"] * ventas[0]["precio"]) # 239.8
Procesar los datos
Con la lista de diccionarios, todo lo que sabes de listas, diccionarios y comprensiones sirve para analizar los datos.
Sumar
facturacion = sum(v["unidades"] * v["precio"] for v in ventas)
unidades = sum(v["unidades"] for v in ventas)
print(f"Total: {facturacion:.2f} €, unidades: {unidades}")
# Total: 1068.30 €, unidades: 15
Filtrar
escalada = [v for v in ventas if v["categoria"] == "escalada"]
print(len(escalada)) # 4
caras = [v["producto"] for v in ventas if v["precio"] >= 100]
print(caras) # ['Cuerda 9,8 mm', 'Botas']
Agrupar
¿Cuánto ha facturado cada tienda? Es el clásico “agrupar y sumar”, que se resuelve con un diccionario acumulador. Con defaultdict(float) no hace falta comprobar si la clave ya existe (lo viste en la biblioteca estándar):
from collections import defaultdict
por_tienda = defaultdict(float)
for v in ventas:
por_tienda[v["tienda"]] += v["unidades"] * v["precio"]
for tienda, total in sorted(por_tienda.items(), key=lambda par: par[1], reverse=True):
print(f"{tienda:<12} {total:>8.2f} €")
# Santander 543.30 €
# Laredo 306.00 €
# Torrelavega 219.00 €
sorted(..., key=..., reverse=True) ordena de mayor a menor facturación, y el formato :<12 y :>8.2f alinea las columnas.
El producto más vendido
from collections import Counter
vendidos = Counter()
for v in ventas:
vendidos[v["producto"]] += v["unidades"]
print(vendidos.most_common(1)) # [('Bastones', 4)]
Escribir CSV
csv.writer: a partir de listas
writerow escribe una fila y writerows, varias de golpe. El módulo pone las comillas donde haga falta:
resumen = [["tienda", "facturacion"]]
resumen += [[tienda, round(total, 2)] for tienda, total in por_tienda.items()]
with open("resumen_tiendas.csv", "w", encoding="utf-8", newline="") as f:
escritor = csv.writer(f)
escritor.writerows(resumen)
with open("resumen_tiendas.csv", encoding="utf-8") as f:
print(f.read(), end="")
# tienda,facturacion
# Santander,543.3
# Torrelavega,219.0
# Laredo,306.0
Puedes escribir números directamente: writer los convierte a texto.
csv.DictWriter: a partir de diccionarios
Cuando tus datos ya son diccionarios, DictWriter es más claro. Necesita la lista de columnas en fieldnames, y writeheader() escribe la cabecera:
columnas = ["producto", "tienda", "importe"]
with open("ventas_escalada.csv", "w", encoding="utf-8", newline="") as f:
escritor = csv.DictWriter(f, fieldnames=columnas, extrasaction="ignore")
escritor.writeheader()
for v in escalada:
v["importe"] = round(v["unidades"] * v["precio"], 2)
escritor.writerow(v)
with open("ventas_escalada.csv", encoding="utf-8") as f:
print(f.read(), end="")
# producto,tienda,importe
# "Cuerda 9,8 mm",Santander,239.8
# Pies de gato,Torrelavega,89.0
# Arnés,Torrelavega,130.0
# Pies de gato,Laredo,178.0
Los diccionarios de escalada tienen más claves de las que queremos guardar (fecha, categoria…). Por defecto DictWriter lanzaría ValueError al encontrarlas; con extrasaction="ignore" simplemente las descarta. Y si a una fila le falta alguna columna, la deja vacía.
Observa también que la cuerda ha salido entre comillas, porque contiene una coma.
CSV de Excel en español
Cuando abras un CSV exportado desde Excel en español te encontrarás tres diferencias:
| Detalle | CSV “estándar” | Excel en español |
|---|---|---|
| Separador | , | ; |
| Decimales | 119.90 | 119,90 |
| Codificación | UTF-8 | UTF-8 con BOM (o Windows-1252 en versiones antiguas) |
El BOM (byte order mark) es una marca invisible que algunos programas ponen al principio de un archivo UTF-8. Si lo lees con encoding="utf-8", el BOM se queda pegado al nombre de la primera columna ('fecha') y fila["fecha"] da KeyError. La codificación utf-8-sig lo elimina al leer y lo añade al escribir.
Escribir un CSV que Excel abra bien
Si un compañero va a abrir tu archivo con doble clic en Excel, usa ;, decimales con coma y utf-8-sig. Así las tildes se verán bien y cada dato irá a su columna:
with open("para_excel.csv", "w", encoding="utf-8-sig", newline="") as f:
escritor = csv.writer(f, delimiter=";")
escritor.writerow(["tienda", "facturación"])
for tienda, total in por_tienda.items():
escritor.writerow([tienda, f"{total:.2f}".replace(".", ",")])
with open("para_excel.csv", encoding="utf-8-sig") as f:
print(f.read(), end="")
# tienda;facturación
# Santander;543,30
# Torrelavega;219,00
# Laredo;306,00
Leer un CSV de Excel
Para leerlo, indica el mismo separador y la misma codificación, y convierte los decimales cambiando la coma por un punto antes de usar float():
with open("para_excel.csv", encoding="utf-8-sig", newline="") as f:
for fila in csv.DictReader(f, delimiter=";"):
total = float(fila["facturación"].replace(",", "."))
print(fila["tienda"], total)
# Santander 543.3
# Torrelavega 219.0
# Laredo 306.0
Consejo: si no sabes qué separador usa un archivo, abre las primeras líneas con un editor de texto. Si te llega un CSV con caracteres raros como
éen lugar deé, prueba a abrirlo conencoding="cp1252", la codificación clásica de Windows.
CSV en memoria con io.StringIO
A veces el CSV no está en un archivo, sino en una cadena: la respuesta de una API, un texto pegado por el usuario… io.StringIO crea un “archivo” en memoria que el módulo csv puede leer igual que uno real:
import io
texto = "nombre;nota\nLucía;8,5\nIker;6\n"
for fila in csv.DictReader(io.StringIO(texto), delimiter=";"):
print(fila["nombre"], float(fila["nota"].replace(",", ".")))
# Lucía 8.5
# Iker 6.0
También sirve al revés: escribir un CSV en un StringIO y obtener el texto con getvalue(), por ejemplo para enviarlo como descarga desde una web.
Datos sucios
Los CSV reales casi nunca están perfectos: celdas vacías, texto donde debería haber un número, filas incompletas. Si conviertes sin cuidado, el programa se detiene en la primera fila mala. Lo habitual es capturar el error con lo que viste en excepciones, avisar y seguir:
sucio = """producto,unidades
Cuerda,2
Arnés,
Casco,tres
Bastones,4
"""
total = 0
for numero, fila in enumerate(csv.DictReader(io.StringIO(sucio)), start=2):
try:
total += int(fila["unidades"])
except ValueError:
print(f"Línea {numero}: unidades no válidas ({fila['unidades']!r})")
print("Unidades válidas:", total)
# Línea 3: unidades no válidas ('')
# Línea 4: unidades no válidas ('tres')
# Unidades válidas: 6
Empezamos a contar en 2 porque la línea 1 es la cabecera; así el aviso coincide con la línea que verías en el editor.
Cuando el CSV se queda pequeño: pandas
Para análisis de datos serios (miles de filas, gráficas, cruzar tablas), la herramienta estándar es pandas, una librería externa que se instala con pip (pip install pandas). Lee un CSV de Excel en una línea y agrupa en otra:
import pandas as pd
df = pd.read_csv("para_excel.csv", sep=";", decimal=",", encoding="utf-8-sig")
print(df["facturación"].sum()) # 1068.3
Aprende primero el módulo csv: entenderás qué hace pandas por debajo y lo tendrás disponible siempre, sin instalar nada.
Errores frecuentes
- Separar con
split(","): rompe los valores que llevan comas entre comillas. Usa siempre el módulocsv. - Olvidar
newline="": filas en blanco entre medias al escribir en Windows. - Operar con texto:
"2" * "119.90"daTypeError; convierte conint()yfloat(). KeyErroren la primera columna: es el BOM de Excel; abre conencoding="utf-8-sig".- Todo en una sola columna: el separador no es el que crees; prueba con
delimiter=";". float("119,90"): daValueError; cambia antes la coma por un punto.- Usar
"w"sobre el archivo original: lo vacías antes de leerlo. Guarda los resultados en otro archivo.
Resumen
| Tarea | Código |
|---|---|
| Abrir para leer | open(ruta, encoding="utf-8", newline="") |
| Filas como listas | csv.reader(f) y next(lector) para la cabecera |
| Filas como diccionarios | csv.DictReader(f) |
| Escribir listas | csv.writer(f).writerows(filas) |
| Escribir diccionarios | csv.DictWriter(f, fieldnames=[...]) + writeheader() |
| Ignorar claves sobrantes | DictWriter(..., extrasaction="ignore") |
| Separador punto y coma | delimiter=";" |
| CSV de Excel | encoding="utf-8-sig" y decimales con replace(",", ".") |
| CSV desde una cadena | csv.reader(io.StringIO(texto)) |
| Agrupar y sumar | defaultdict(float) o Counter |
Los CSV son perfectos para intercambiar datos, pero cuando necesitas buscar, actualizar y relacionar registros, lo que necesitas es una base de datos. En la siguiente lección la crearás sin instalar nada con SQLite.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.