先に、答え。

CSVを読み込んだ後に、列数・列名・IDの条件を別々に検査します。パーサーがエラーを出さないことだけでは、期待した表であるとは判断できません。

この記事の内容

対象・検証範囲:CPython 3.12.14 / Linux。掲載サンプルと期待する出力を照合。標準ライブラリのみ。

読み込み成功と正常なデータは別

ヘッダーが3列なのに、あるレコードだけ2列しかないCSVでも、readerは行を返すことがあります。後から値を対応付けると、欠けた値を見逃す原因になります。

ここでは「ヘッダーが1行」「列名は空ではなく一意」「各レコードの列数が同じ」「id列は空ではなく一意」という条件を検査します。IDの重複が正当なデータには、そのまま適用しないでください。

不備をまとめて返すコード

空行は無視します。表示する番号は、ヘッダーを先頭とした読み取りレコードの番号であり、セル内改行がある場合の物理行番号ではありません。

import csv
import io

def validate_csv(text):
    errors, seen = [], set()
    reader = csv.reader(io.StringIO(text, newline=""), strict=True)
    try:
        header = next(reader, None)
        if not header or any(not name.strip() for name in header):
            return ["ヘッダーが空、または空の列名があります"]
        if len(set(header)) != len(header):
            return ["列名が重複しています"]
        if "id" not in header:
            return ["id列がありません"]
        key_index = header.index("id")
        for number, row in enumerate(reader, start=2):
            if not row:
                continue
            if len(row) != len(header):
                errors.append(f"レコード{number}: 列数が一致しません")
                continue
            key = row[key_index]
            if not key.strip():
                errors.append(f"レコード{number}: idが空です")
            elif key in seen:
                errors.append(f"レコード{number}: idが重複しています")
            else:
                seen.add(key)
    except csv.Error:
        errors.append("CSVの引用符などの構文を確認してください")
    return errors

bad = "id,name\n001,ノート\n002\n001,ペン\n,ファイル\n"
print("\n".join(validate_csv(bad)))
assert validate_csv("id,name\n001,ノート\n") == []
assert validate_csv("id,id\n001,002\n") == ["列名が重複しています"]
assert validate_csv('id,name\n001,"未終了')

期待する結果

レコード3: 列数が一致しません
レコード4: idが重複しています
レコード5: idが空です

問題がなければ空のリストを返します。変換の前にerrors = validate_csv(text)として実行し、エラーが1件でもあれば出力を作らず止める形に組み込めます。

実ファイルでは文字コードを明示して読み込んでください。文字コードのエラーはCSVの構造検査より先に起こるので、読み込み段階で別に処理します。BOM付きUTF-8にはutf-8-sigの例を使えます。

この検査に含まれないこと

「正常」と判定できるのは、ここで決めた条件を満たすことだけです。日付の実在、金額の範囲、参照先の存在、商品名の正しさなどは検査していません。strict=Trueも、あらゆるCSV仕様や業務ルールを網羅するバリデーターではありません。

このコードはidIDを区別し、001 001も別のキーにします。空白だけのキーは拒否しますが、値を勝手に整形しません。表記を揃えるなら元の値を保管し、検査条件と変換条件を分けてください。

自動処理へ入れるとき

検査で見つかった不備をerrors="ignore"などで無視して進める設計にはしません。元ファイルを残し、不備の種類と対象レコードを記録して、出力元の設定を見直します。

まず小さな正常データと壊したデータの両方で試します。その後、結合JSON変換の前段に検査を置くと、無言でデータを壊す経路を減らせます。

参考にした公式資料

掲載サンプルは記載のPython環境で実行確認しています。すべてのOS・入力データを保証するものではありません。 編集・検証方針