備考欄に改行を入れたCSVは、ファイルの一行が一件とは限りません。見出しを除くと三行あるのに、データは二件という形も正常です。行数だけを数えて処理件数と比べると、取り込み漏れと誤解してしまいます。
Pythonでは行を先に区切らずcsv.readerやcsv.DictReaderへ渡します。引用符の中のカンマや改行を、CSVの規則に従って一つの値として読み取るためです。
二件のデータが四行になる例
次の例は、見出し一行、改行を含む備考が二行、通常の備考が一行という構成です。文字列内の改行を含めたまま読み、書き戻した結果も比べます。データは練習用の架空の内容です。
import csv
from io import StringIO
text = 'ID,備考\nA01,"一行目\n二行目"\nA02,連絡待ち\n'
reader = csv.DictReader(StringIO(text, newline=""))
rows = list(reader)
print("物理行数:", len(text.splitlines()))
print("データ件数:", len(rows))
print("読んだ行数:", reader.line_num)
assert rows[0]["備考"] == "一行目\n二行目"
buffer = StringIO(newline="")
writer = csv.DictWriter(buffer, fieldnames=["ID", "備考"])
writer.writeheader()
writer.writerows(rows)
again = list(csv.DictReader(StringIO(buffer.getvalue(), newline="")))
print("再読込一致:", rows == again)
assert rows == again
assert len(rows) == 2
出力は物理行数4、データ件数2、読んだ行数4です。再読込一致はTrueになります。splitlinesはここでは物理行数を説明するために使っています。CSVを読み込む前の加工には使っていません。
掲載例はWindows・Python 3.12.10で実行しました。最初の備考に含まれる改行を保持し、書き出して読み戻した二件の辞書が一致することを確かめています。実際の問い合わせデータは使っていません。
実ファイルはnewlineを指定して開く
ファイルから読む場合は、openにencodingとnewline=""を指定し、開いたファイルをcsv.DictReaderへ渡します。newlineの処理をcsv側へ任せる形です。文字コードは入力元の仕様に合わせます。
read().split("\n")のように先に改行で分け、さらに各行をカンマで割る方法では、引用符内の改行やカンマを扱えません。文字として入っているカンマを列の境界だと判断してしまうためです。
書き出し側もcsv.writerやcsv.DictWriterを使うと、引用符が必要な値を扱えます。自前で値をカンマで連結すると、備考にカンマや引用符が入ったときに列構成が変わるおそれがあります。
エラーの行番号と件数を分ける
reader.line_numはそこまでに読んだ入力上の行数です。データの何件目かを示す番号ではありません。改行を含むレコードを読み終えた時点では、そのレコードの終わりの行を示します。
利用者へ修正を依頼するなら、取り込み順の番号と、入力上の行番号を区別して記録します。IDがある場合は、そのIDも確認の手がかりになります。ただし、共有ログへ備考の全文や個人情報を複写する必要があるかは別に判断します。
件数確認にはCSVとして解釈したレコードの数を使います。見出しだけ、空ファイル、改行のある備考、カンマのある備考を小さな入力へ入れ、想定どおりに数えられるかを確認してから本番データへ進めます。
ファイルを開く基本はPythonのファイル読み書き、読み込んだ数値の検算はCSV集計の基本で確認できます。
参考資料
newlineとline_numの意味は、Pythonのcsv公式資料を参照しました。資料確認日は2026年11月1日です。
