この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Pythonの公式CSV仕様と既存Daily Codeサンプルの検証記録を基に、架空のCSVで物理行数とレコード数の違いを解説します。P1〜P5の5視点による自己審査であり、独立AI審査ではありません。検証ステータス:🧪 既存サンプルのLinux/Python 3.13.5テスト記録は12件PASS。記事公開環境・Windows/macOSは未検証
CSVは4行なのにデータが2件?Pythonのcsv.readerで「物理行」と「レコード」を数え分ける
CSVファイルを開くと4行あるのに、取り込んだデータは2件しかない。これは必ずしも欠落や読み取り失敗ではありません。引用符で囲まれたセルの中に改行が入っていると、ファイル上の複数行がCSV上の1レコードになるためです。Python標準の csv.reader を使うと、改行を含むセルを壊さず、データ件数を数えられます。
どんなときに困るのか
問い合わせ一覧やアンケート結果をCSVに出すと、「備考」「自由記述」「作業メモ」などに改行が入ることがあります。監視担当がテキストエディタの行番号や wc -l の値だけを見て「取込件数が足りない」と判断すると、実際には正常なCSVを障害と誤認してしまいます。反対に、引用符が壊れたCSVを正常として扱うのも危険です。
この違いを理解するため、次の3つを区別します。
| 数える対象 | 今回の意味 | 注意点 |
|---|---|---|
| 物理行 | ファイル内の改行で区切られる表示上の行 | 引用符の内側にある改行も含む |
| CSVレコード | CSVパーサーが1件として返す行データ | 複数の物理行にまたがることがある |
| データ件数 | CSVレコードからヘッダー1件を除いた数 | ヘッダーの有無は業務ごとに確認する |
今回の教材は「ヘッダーが必ず1件あるUTF-8 CSV」を前提にしています。すべてのCSVにヘッダーがあるわけではないため、一般的なCSVの総レコード数と、ここで出す CSV_RECORDS は同じ意味ではありません。
まずは架空のCSVを見てみる
サンプルの demo.csv は、次の内容です。Aのメモ欄だけが2行になっています。
id,note A,"line1 line2" B,done
テキスト上では4行ですが、CSVの解釈ではヘッダー、A、Bの3レコードです。データだけならAとBの2件です。Aの note セルには line1 と line2 の間の改行が含まれています。引用符は、カンマや改行を含む値を一つのフィールドとして表すために使われます。
単純に1行ずつ文字列を分割すると、Aのメモの後半を新しいレコードと誤認する恐れがあります。CSVはただの「カンマで区切った行」の集合ではなく、引用符を解釈する必要がある形式です。自前の split(',') で取り込まない理由はここにあります。
準備と実行手順
追加パッケージは不要です。Python 3.9以降を想定しています。今回の実測はPython 3.13.5で行いました。コード、テスト、架空CSV、READMEは記事末尾のGitHubサンプルにまとめています。
サンプルフォルダを取得し、
audit_csv.py、demo.csv、test_audit_csv.pyを同じフォルダに置きます。ターミナルを開き、そのフォルダへ移動します。
次のコマンドを一つずつ実行します。
python3 --version python3 audit_csv.py demo.csv python3 -m unittest discover -p 'test_*.py' -v
Windowsで python3 が認識されない場合は、Pythonのインストール状態を確認し、py -3 で試してください。Windows上で同じコマンドを実行した実測はまだありません。PowerShellとBashでコマンドを混同せず、まずバージョン表示を確認します。
今回のLinux/Python 3.13.5で実測したCLI出力は次のとおりです。
STATUS=OK PHYSICAL_LINES=4 CSV_RECORDS=2 HEADER_COLUMNS=2 MULTILINE_RECORDS=1
PHYSICAL_LINES=4 は表示上の4行、CSV_RECORDS=2 はヘッダーを除いた2件を意味します。MULTILINE_RECORDS=1 は、複数の物理行にまたがったデータレコードが1件あるという意味です。数値が違っても、ただちに異常ではありません。
Pythonはどうやって区別しているのか
教材の中心は、Python標準ライブラリの csv.reader です。重要な部分だけを抜き出すと次のようになります。
import csv
with open('demo.csv', encoding='utf-8-sig', newline='') as file:
reader = csv.reader(file, strict=True)
header = next(reader)
records = sum(1 for row in reader)
print('データ件数:', records)
newline='' は、改行の扱いをCSVパーサーに任せるための指定です。Python公式ドキュメントもCSVファイルを開くときに指定するよう説明しています。utf-8-sig はUTF-8のBOMが付いたファイルも読めるようにするための指定です。文字コードを自動判定しているわけではなく、Shift_JIS等のファイルを読み取れる保証はありません。
next(reader) は最初のCSVレコードをヘッダーとして取り出します。その後の繰り返しで数えているのは、テキストの改行数ではなく、パーサーが返すデータレコードの数です。strict=True は不正なCSV形式に対してエラーを出しやすくする設定ですが、業務項目の値が正しいかまでは検査しません。
公式仕様には reader.line_num という属性もあります。名前だけ見ると「何件目のデータか」と思いがちですが、これは元の入力から読んだ物理行の位置です。引用符の中に改行があれば、レコード数と一致しません。教材の完全版では、直前の line_num と比較して、複数行にまたがるデータレコードを数えています。
1条件だけ変えると結果はどうなるか
Aのメモを1行に直した別名のCSVを作り、元の demo.csv はそのまま残します。
id,note A,line1 B,done
この場合の期待値(この変更後ファイル自体は今回未実行)は PHYSICAL_LINES=3、CSV_RECORDS=2、MULTILINE_RECORDS=0 です。データ件数は変わらず、物理行数だけが1減ります。この比較から、物理行数の増減だけでデータ増減を判断できないことが分かります。
なお、Windowsで多いCRLF改行でも、教材は \r\n を一つの物理改行として数えます。CRLFのケースは単体テストで実行済みです。ただし、文字コードや区切り文字が異なるすべてのCSVを自動判別するプログラムではありません。
失敗時は終了コードと理由を確認する
完全版は、CSVの中身や実ファイルパスを標準出力に表示せず、数値とエラー種別だけを出すようにしています。架空ファイルで試した場合の結果は次のとおりです。
| 状況 | 出力の例 | 終了コード | 確認すること |
|---|---|---|---|
| 正常なCSV | STATUS=OK | 0 | ヘッダーの有無と期待件数 |
| 列数が合わない | ISSUE=FIELD_COUNT_MISMATCH | 2 | カンマ、引用符、元データの形式 |
| 引用符が閉じていない・不正UTF-8 | ISSUE=PARSE_OR_ENCODING_ERROR | 2 | ファイル形式と文字コード |
| ファイルがない・読めない | ISSUE=READ_ERROR | 3 | 指定パス、権限、ファイル存在 |
| ファイルが1MiBを超える | ISSUE=TOO_LARGE | 3 | 教材のサイズ制限 |
今回のテストでは、正常・CRLF・BOM・末尾改行なし・空ファイル・列数不一致・引用符の閉じ忘れ・不正UTF-8・ファイル不存在・サイズ超過など12件すべて成功しました。python3 -m py_compile も成功しています。実行したのは隔離Linuxコンテナであり、Ubuntu本番やWordPressを操作した結果ではありません。
ファイルがないときは STATUS=READ_ERROR ISSUE=READ_ERROR を出して終了コード3となることをCLIで確認しました。自動処理へ組み込む場合は、出力に STATUS=OK があるかだけでなく、終了コードが0かどうかを判定してください。失敗しても後続工程を続けると、件数を確認できなかったこと自体が見落とされます。
実務へ持ち込む前に知っておくこと
今回の教材は1MiB以下のUTF-8 CSVをメモリへ読み込む、読み取り専用の検査です。巨大なファイルを扱う用途では、ストリーミング読み込みや別のサイズ制御が必要です。また、列数が一致しても「注文番号が重複していない」「日付が正しい」「金額が妥当」といった業務上の品質は証明できません。ヘッダー名の重複や型検査も別の課題です。
物理行数の比較は「取込件数がおかしい」と相談されたときの最初の切り分けに向いています。まず入力CSVの形式、ヘッダー、引用符内改行、文字コードを確かめ、次に実際のCSVレコード数を数えます。その後に、取り込み先での除外条件、重複除去、エラー行の扱いを確認するのが自然な順序です。
実顧客のCSVをそのままAIや外部サービスへ送信する必要はありません。今回のような架空の2件で原因を再現できれば、機密情報を使わずに仕組みを理解できます。ファイル内容をログに出さない設計でも、実運用で使う前にはログ保存先や権限を確認してください。
まとめ
CSVの「4行」と「2件」は両立します。引用符で囲まれたセル内の改行を、csv.reader は一つのレコードの一部として解釈するからです。CSVの件数を確認するときは、物理行と論理レコードを区別し、ヘッダーの扱いを明示しましょう。newline=''、文字コード指定、異常系の終了コードまでそろえると、取り込みトラブルの説明が具体的になります。
公式情報・一次情報
Python公式:csv — CSVファイルの読み書き —
csv.reader、newline=''、line_num、strictの仕様。Python公式:argparse — コマンドライン引数の処理。
Python公式:unittest — 単体テストの実行。
