大きなCSVを「100万行ずつ分けたい」「50MB以下にしたい」という場面は珍しくありません。

ただし、CSVは見た目の改行とデータ上の1レコードが一致しないことがあります。単純にテキストの改行位置で切ると、正しいCSVを壊す可能性があります。

CSVは1行=1レコードとは限らない

RFC 4180では、カンマ・ダブルクォート・改行を含むフィールドをダブルクォートで囲む形式が示されています。

たとえば、

id,memo
1,"1行目
2行目"
2,"通常のメモ"

では、画面上は3行以上に見えても、データレコードは2件です。

このため、LF や CRLF を見つけるたびにファイルを分割すると、1つのレコードの途中で切ってしまうことがあります。

行数で分ける

「5万レコードずつ」のように分ける方法です。

扱いやすく、インポート先の件数制限を避けたい場合に向いています。

重要なのは、物理行数ではなくCSVとして解析したレコード数で数えることです。

ファイルサイズで分ける

メール添付やシステム取込の上限に合わせて「25MB未満」などにする方法です。

サイズ基準の場合も、目標バイト数を超えた瞬間に文字列を切るのではなく、直前のレコード境界でファイルを確定します。

文字コードによって1文字のバイト数も変わるため、文字数=ファイルサイズではありません。

列の値で分ける

「店舗別」「年月別」「担当者別」のように、特定列の値ごとに分ける方法です。

後工程で別々に処理するなら、行数均等よりこちらの方が実務的なことがあります。

ただし、ファイル名にそのまま列値を使うと、/ や : などOSで使えない文字が混ざる場合があります。ファイル名用の正規化が必要です。

ヘッダーをどうするか

分割後のCSVを個別に使うなら、通常は各ファイルへヘッダーを付けた方が扱いやすくなります。

ただし、取込先が「最初のファイルにだけヘッダー」と指定しているケースもあるため、相手側仕様を優先します。

分割後に必ず確認すること

分割できたら終わりではありません。

  • 全ファイルのレコード件数合計=元ファイルの件数か
  • ヘッダーを除いたデータ件数が一致するか
  • 列数が途中で変わっていないか
  • 引用符が閉じているか
  • 文字コードが意図せず変わっていないか

を確認します。

大容量CSVは「切ること」より、切った後も元データと同じ内容か証明できることの方が重要です。

実際に「レコード数」で分割する例

「5万件ずつ」のように分けたい場合は、CSVをCSVとして解析できる処理を使います。単純なテキスト分割は避けます。

たとえばPythonが使える環境なら、標準の csv モジュールで次のように分割できます。例はUTF-8 BOM付きCSVを5万レコードごとに分け、各ファイルへヘッダーを付け直す形です。

import csv
from pathlib import Path

src = Path("input.csv")
rows_per_file = 50000

with src.open("r", encoding="utf-8-sig", newline="") as f:
    reader = csv.reader(f)
    header = next(reader)

    part = 0
    writer = None
    out = None

    for i, row in enumerate(reader):
        if i % rows_per_file == 0:
            if out:
                out.close()
            part += 1
            out = Path(f"part_{part:03}.csv").open(
                "w", encoding="utf-8-sig", newline=""
            )
            writer = csv.writer(out)
            writer.writerow(header)

        writer.writerow(row)

    if out:
        out.close()

newline="" でCSVパーサーに改行処理を任せるため、引用符内のセル内改行を単純な行末として切りにくくなります。

この例は入力文字コードをUTF-8系と仮定しています。Shift_JIS系など別の文字コードなら、元ファイルと取込先の仕様を確認してから encoding を変更します。

分割前と分割後で何を照合するか

|確認項目|分割前|分割後|
|---|---:|---:|
|データ件数|100,000|50,000 + 50,000|
|ヘッダー|1回|各ファイルに1回|
|列数|8列|全ファイル8列|
|文字コード|UTF-8 BOM付き|UTF-8 BOM付き|
|先頭・末尾ID|確認|連続しているか確認|

件数だけでなく、最初と最後のレコード、列数、文字コードまで確認すると、途中欠落や二重出力を見つけやすくなります。

参考

  • RFC 4180
    https://www.rfc-editor.org/info/rfc4180/
おすすめの記事