フォルダー内のファイル一覧をCSVで作成する
サブフォルダーを走査し、ファイルパス、拡張子、サイズ、更新日時を表として記録します。元のファイルと既存の結果をそのまま保持したまま、まず4つの小さなサンプルファイルで確認します。
CSVをデータレコード数で分割し、すべての出力ファイルに同じヘッダーを繰り返して、元のファイルは変更しません。7レコードの合成例を使って境界を確認し、すべてのレコードが元の順序のまま保持されることを検証します。
この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English
対象読者このガイドは、CSVエクスポートの内容を手作業で編集せずに小さなファイルへ分割する必要がある人向けです。
このスクリプトは、選択した最大データレコード数までを含む番号付きCSVファイルを作成します。各ファイルの先頭には同じヘッダーが入ります。ヘッダーは上限数に含めません。上限が3でデータレコードが7件の場合、各ファイルには3件、3件、1件のデータレコードが入ります。
CSVレコードは、物理的なテキスト1行と一致するとは限りません。引用符で囲まれたフィールドには改行を含めることができます。csvモジュールはCSVの引用規則に従ってレコードを読み取るため、複数行のメモも別の行になるのではなく、元のレコードに含まれたままになります。
このデータセットは合成データで、この記事のために作成したものです。作業フォルダーにsample.csvとして保存してください。引用符で囲まれたnote内の改行はそのままコピーしてください。これは、通常の行単位の分割では正しく処理できないケースを意図的に含めています。
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
列は4個、データレコードは7件あります。starter, pack内のカンマは1つのフィールド内に含まれます。R003のnoteは物理的には2行にまたがりますが、1つのフィールドのままです。unitsの値の合計は60です: 12 + 7 + 9 + 5 + 11 + 6 + 10。
python split_large_csv.py相対パスはスクリプトの場所から自動的に解決されるのではなく、ターミナルの作業ディレクトリを基準に解決されます。親フォルダーoutputsはすでに存在していても構いませんが、この実行を開始するときにoutputs/sample_partsが存在していてはいけません。
分割処理ではレコードを順次読み込み、各パートを排他的作成モードで開きます。書き込み後に元ファイルと各パートを再度読み取り、ヘッダー、フィールド値、レコード順序、件数を比較します。成功メッセージは、その比較が完了してから表示されます。
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()}")
入力のデコードは、先頭のバイトオーダーマークの有無にかかわらずUTF-8を受け付けます。出力はそのマークを付けないUTF-8です。newline引数により、csvモジュールがレコード終端と埋め込み改行を処理できます。
期待されるファイルを以下に示します。unitsの合計は分割スクリプトが計算する値ではなく、手作業によるクロスチェックです。すべてのファイルでrecord_id,team,units,noteをヘッダーとして繰り返す必要があります。
| outputs/sample_parts内のファイル | レコードID | データレコード数 | Units合計 |
|---|---|---|---|
| part_0001.csv | R001, R002, R003 | 3 | 28 |
| part_0002.csv | R004, R005, R006 | 3 | 22 |
| part_0003.csv | R007 | 1 | 10 |
期待されるコンソール表示を以下に示します。これは手作業で導出したもので、実際の実行ログを取得したものではありません。
Verified 7 data records in 3 files.
Output folder: outputs/sample_parts28 + 22 + 10が元の合計60と一致することを確認してください。最初のパートをテキストエディターで開き、引用符で囲まれたカンマと複数行のnoteを確認します。見た目の行数だけを数えると、データレコード数を誤って数えることになります。
境界条件は別々の作業フォルダーでテストしてください。6レコードなら3件ずつの2パートになり、空の3番目のパートは作成されないはずです。ヘッダーだけの入力では空の出力フォルダーが残るはずです。完全に空の入力は拒否されるはずです。これらの期待動作はコードを確認して判断したもので、ここでは実行していません。
| 症状 | 確認すること |
|---|---|
| FileNotFoundError | SOURCEとターミナルの作業ディレクトリを確認します。ファイル名がsample.csv.txtではなくsample.csvであることを確認してください。 |
| FileExistsError | 既存の出力先を確認し、outputs配下の新しいフォルダーを選択します。既存の出力先が空でも拒否されます。 |
| UnicodeDecodeError | 元ファイルのエンコーディングを確認します。デコードエラーを無視せず、正しくデコードされたコピーを取得または作成してから分割してください。 |
| ヘッダーの欠落または重複 | 空ではない列名を指定します。完全に同一の重複列名は拒否されますが、それ以外のヘッダーテキストは保持されます。 |
| フィールド数の誤りまたはcsv.Error | 区切り文字、引用符、空レコードを確認します。このスクリプトは標準的な二重引用符による引用を使用したカンマ区切り入力を想定しています。 |
| 書き込み中または検証中の失敗 | その出力先は未完成として扱ってください。エラーを確認し、修正後の実行では新しい出力先を使用します。 |
処理の後半でエラーが発生すると、不完全なファイルが残る場合があります。スクリプトは現在のファイルを閉じますが、フォルダー全体をロールバックしたり自動削除したりはしません。
この例が自分の環境で正常に動作したら、SOURCEを変更し、outputs配下に新しいOUTPUT_DIRを選び、ROWS_PER_FILEを増やします。レコード数の上限はバイトサイズの上限ではありません。長いフィールドがあると、同じレコード数のグループでもディスク使用量が大きく異なる場合があります。
このスクリプトが保持するのは解析されたフィールド文字列であり、元のバイト列、引用形式、レコード終端形式ではありません。データセット全体を一度に読み込むことはありませんが、異常に大きいフィールドは依然としてメモリを必要とし、CSVパーサーのフィールドサイズ上限を超える可能性があります。検証では元ファイルと出力をもう一度読み取ります。処理中は元ファイルを変更しないでください。これはロックされたスナップショットでもトランザクション型バックアップでもありません。
2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · 標準ライブラリ: csv, pathlib · 未実行
説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。