大きな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/
