Scripts y automatización de archivos

Dividir un CSV grande en archivos más pequeños sin perder la cabecera

Divide un CSV según el número de registros de datos, repite la cabecera en cada archivo de salida y deja el archivo original sin cambios. Usa un ejemplo sintético de siete registros para comprobar los límites y verificar que todos los registros se conservan en su orden original.

Ver el índice

La traducción se ha realizado con IA. Comprueba el código, las unidades y los valores junto con el original. La revisión por hablantes nativos de cada idioma aún no se ha completado. English

Para quién esEsta guía está dirigida a quienes necesitan dividir una exportación CSV en archivos más pequeños sin editar manualmente su contenido.

Preparación
  • Python 3.12, con un comando de terminal que inicie esa versión.
  • Un editor de texto que pueda guardar archivos UTF-8 y conservar texto multilínea entre comillas.
  • Una carpeta de trabajo con permiso para leer la entrada y suficiente espacio en disco para las salidas.
  • Solo se requiere la biblioteca estándar de Python: csv y pathlib.

01Define qué cuenta como una fila

El script crea archivos CSV numerados que contienen como máximo el número elegido de registros de datos. Cada archivo comienza con la misma cabecera. La cabecera no cuenta para el límite. Con un límite de 3 y 7 registros de datos, los archivos contienen 3, 3 y 1 registros de datos.

Un registro CSV no es necesariamente una línea física de texto. Un campo entre comillas puede contener un salto de línea. El módulo csv lee los registros según las reglas de comillas de CSV, por lo que una nota multilínea permanece asociada a su registro en lugar de convertirse en una fila separada.

02Crea la entrada sintética

Este conjunto de datos es sintético y fue escrito específicamente para este artículo. Guárdalo como sample.csv en tu carpeta de trabajo. Copia exactamente el salto de línea dentro de la nota entre comillas; está incluido deliberadamente para probar un caso que una división normal por líneas maneja de forma incorrecta.

csv
record_id,team,units,note
R001,Support,12,"starter, pack"
R002,Ops,7,repeat
R003,Support,9,"two
lines"
R004,Sales,5,normal
R005,Ops,11,normal
R006,Sales,6,normal
R007,Support,10,normal

Hay 4 columnas y 7 registros de datos. La coma de starter, pack pertenece a un solo campo. La nota correspondiente a R003 ocupa 2 líneas físicas, pero sigue siendo un solo campo. Los valores de units suman 60: 12 + 7 + 9 + 5 + 11 + 6 + 10.

03Prepara la carpeta de trabajo

  1. Coloca sample.csv y un nuevo script llamado split_large_csv.py en la misma carpeta de trabajo.
  2. Pega en el script el código Python completo de la siguiente sección.
  3. Mantén ROWS_PER_FILE en 3 para este ejemplo. El valor debe ser un entero positivo.
  4. Abre una terminal en la carpeta de trabajo y ejecuta el comando siguiente.
text
python split_large_csv.py

Las rutas relativas se resuelven desde el directorio de trabajo de la terminal, no automáticamente desde la ubicación del script. La carpeta principal outputs puede existir previamente, pero outputs/sample_parts no debe existir cuando comience esta ejecución.

04Divide los registros y verifica los archivos escritos

El divisor lee los registros de forma incremental y abre cada parte en modo de creación exclusiva. Después de escribirlos, vuelve a leer la fuente y las partes para comparar cabeceras, valores de campos, orden de los registros y recuentos. Los mensajes de éxito solo aparecen después de terminar esa comparación.

python
import csv
from pathlib import Path

SOURCE = Path("sample.csv")
OUTPUT_DIR = Path("outputs") / "sample_parts"
ROWS_PER_FILE = 3


def part_path(number: int) -> Path:
    return OUTPUT_DIR / f"part_{number:04d}.csv"


def split_csv() -> tuple[int, int]:
    if type(ROWS_PER_FILE) is not int or ROWS_PER_FILE < 1:
        raise ValueError("ROWS_PER_FILE must be a positive integer.")

    with SOURCE.open("r", encoding="utf-8-sig", newline="") as source:
        reader = csv.reader(source, strict=True)
        header = next(reader, None)
        if not header or any(not name.strip() for name in header):
            raise ValueError("Missing header or blank column name.")
        if len(set(header)) != len(header):
            raise ValueError("Duplicate column names.")

        OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
        OUTPUT_DIR.mkdir()  # Refuse to reuse an existing output path.
        total = 0
        parts = 0
        target = None
        try:
            for record_no, row in enumerate(reader, start=1):
                if len(row) != len(header):
                    raise ValueError(
                        f"Data record {record_no}: wrong number of fields."
                    )
                if total % ROWS_PER_FILE == 0:
                    if target is not None:
                        target.close()
                    parts += 1
                    target = part_path(parts).open(
                        "x", encoding="utf-8", newline=""
                    )
                    writer = csv.writer(target)
                    writer.writerow(header)
                writer.writerow(row)
                total += 1
        finally:
            if target is not None:
                target.close()

    return total, parts


def verify_csv(expected_rows: int, part_count: int) -> None:
    with SOURCE.open("r", encoding="utf-8-sig", newline="") as source:
        original = csv.reader(source, strict=True)
        header = next(original, None)
        seen = 0
        for number in range(1, part_count + 1):
            with part_path(number).open(
                "r", encoding="utf-8", newline=""
            ) as part:
                rows = csv.reader(part, strict=True)
                if next(rows, None) != header:
                    raise ValueError(f"Header mismatch in part {number}.")
                count = 0
                for row in rows:
                    if row != next(original, None):
                        raise ValueError(f"Data mismatch in part {number}.")
                    count += 1
                required = min(
                    ROWS_PER_FILE,
                    expected_rows - (number - 1) * ROWS_PER_FILE,
                )
                if count != required:
                    raise ValueError(f"Row count mismatch in part {number}.")
                seen += count
        if seen != expected_rows or next(original, None) is not None:
            raise ValueError("Overall row count mismatch.")


if __name__ == "__main__":
    total, parts = split_csv()
    verify_csv(total, parts)
    print(f"Verified {total} data records in {parts} files.")
    print(f"Output folder: {OUTPUT_DIR.as_posix()}")

La decodificación de entrada acepta UTF-8 con o sin una marca de orden de bytes inicial. La salida usa UTF-8 sin esa marca. El argumento newline permite que el módulo csv gestione los finales de registro y los saltos de línea incrustados.

05Comprueba manualmente el resultado esperado

Los archivos esperados se muestran a continuación. Sus totales de units son comprobaciones manuales independientes, no cálculos realizados por el divisor. Todos los archivos deben repetir record_id,team,units,note como cabecera.

Archivo dentro de outputs/sample_partsIDs de registroRegistros de datosTotal de units
part_0001.csvR001, R002, R003328
part_0002.csvR004, R005, R006322
part_0003.csvR007110

El texto esperado de la consola se muestra a continuación. Se obtuvo manualmente y no corresponde a un registro capturado de una ejecución.

text
Verified 7 data records in 3 files.
Output folder: outputs/sample_parts

Comprueba que 28 + 22 + 10 sea igual al total original de 60. Abre la primera parte en un editor de texto para inspeccionar la coma entre comillas y la nota multilínea. Contar únicamente las líneas visibles dará un número incorrecto de registros de datos.

06Comprueba antes de usar una entrada más grande

  1. Confirma que los 3 archivos tengan los mismos 4 campos de cabecera y que la cabecera aparezca solo una vez en cada archivo.
  2. Lee los archivos en orden numérico. Desde R001 hasta R007, cada registro debe aparecer una sola vez, sin registros faltantes ni repetidos.
  3. Confirma que el script indique que la verificación fue correcta. La comparación comprueba las cadenas de los campos, no solo los recuentos, por lo que tener recuentos iguales no es suficiente.
  4. Ejecuta el script de nuevo sin cambiar el destino. Debe detenerse con FileExistsError antes de escribir otra parte.

Prueba los límites en carpetas de trabajo separadas: 6 registros deben producir 2 partes de 3, no una tercera parte vacía. Una entrada que contenga solo la cabecera debe dejar una carpeta de salida vacía. Una entrada completamente vacía debe rechazarse. Estas expectativas se revisaron mediante inspección, no se ejecutaron aquí.

07Reconoce errores comunes

SíntomaQué comprobar
FileNotFoundErrorComprueba SOURCE y el directorio de trabajo de la terminal. Confirma que el nombre del archivo sea sample.csv y no sample.csv.txt.
FileExistsErrorRevisa el destino existente y selecciona después una carpeta nueva debajo de outputs. Incluso un destino existente vacío se rechaza.
UnicodeDecodeErrorConfirma la codificación de la fuente. No descartes los errores de decodificación; obtén o crea una copia correctamente decodificada antes de dividirla.
Cabecera ausente o duplicadaProporciona nombres de columna no vacíos. Los nombres duplicados exactamente iguales se rechazan; el resto del texto de la cabecera se conserva.
Número incorrecto de campos o csv.ErrorInspecciona los delimitadores, las comillas y los registros vacíos. Este script espera una entrada separada por comas con el uso estándar de comillas dobles.
Fallo durante la escritura o la verificaciónConsidera ese destino incompleto. Revisa el error y utiliza un destino nuevo para una ejecución corregida.

Un error tardío puede dejar archivos parciales. El script cierra el archivo actual, pero no revierte la carpeta ni elimina nada automáticamente.

08Comprende los límites antes de aumentar la escala

Después de que el ejemplo funcione correctamente en tu equipo, cambia SOURCE, elige un OUTPUT_DIR nuevo debajo de outputs y aumenta ROWS_PER_FILE. Un límite de registros no es un límite de tamaño en bytes: los campos largos pueden hacer que grupos con el mismo número de registros ocupen cantidades de espacio en disco muy diferentes.

El script conserva las cadenas de los campos ya interpretados, no los bytes originales, el estilo de comillas ni el formato de final de registro. No carga todo el conjunto de datos en memoria, pero los campos excepcionalmente grandes siguen requiriendo memoria y pueden superar el límite de tamaño de campo del analizador CSV. La verificación añade otra lectura de la fuente y las salidas. Mantén la fuente sin cambios durante todo el proceso; esto no es una instantánea bloqueada ni una copia de seguridad transaccional.

Registro de ejecución y verificación

2026-09-20 · ejemplo comprobado manualmente · objetivo: Python 3.12 · biblioteca estándar: csv, pathlib · sin ejecución

  • Se contaron manualmente 4 columnas y 7 registros de datos, tratando la nota multilínea entre comillas como un solo campo.
  • Se asignaron manualmente los registros a grupos de 3, 3 y 1.
  • Se calcularon manualmente los totales de grupo de 28, 22 y 10, con un total general de 60.
  • Se revisaron los nombres de salida, la creación exclusiva, la repetición de cabeceras y la lógica de comparación secuencial.
  • Se derivó el texto esperado de la consola a partir del ejemplo y del código.
Límites de la verificación
  • El autor de esta respuesta no ejecutó el código; no se probó ningún entorno de Python ni comportamiento del sistema de archivos.
  • Las entradas límite, los archivos mal formados y las colisiones de salida se consideraron únicamente mediante inspección.
  • No se probaron el rendimiento con archivos grandes, el uso de memoria, los cambios concurrentes en la fuente ni la recuperación tras escrituras interrumpidas.

Criterios de redacción y verificación de todo el sitio

Fuentes de referencia

Las explicaciones y los ejemplos son de elaboración propia. Puedes consultar los comportamientos y conceptos relacionados en las siguientes fuentes oficiales.