AIへCSVの整理を頼むときは、改行で区切った行数と、CSVとして読み取ったレコード数を分けます。自由記述の中に改行やカンマがあれば、文字列を改行・カンマで分割するだけでは列や件数が壊れます。受け渡す前に、CSVの設定と元のセル値を照合してください。
この記事では、架空の問い合わせメモ3件で、引用符内のカンマ・改行・二重引用符を確認します。Pythonの読み書きをローカルで実行した例です。AIサービス、CRMへの取込、Excelの表示、メール送信は検証していません。
目次
件数はCSVとして読んで数える
以下はidとnoteの2列です。見た目の改行は5行ですが、ヘッダーを除くデータは3レコードです。C02のnoteが2行にまたがっています。
id,note
C01,"初回,確認"
C02,"一行目
二行目"
C03,"担当者は""未定"""
表として読むと、値は次の3件です。「改行」は説明のための表示で、その文字をCSVの値へ足す指定ではありません。
表は横にスクロールできます
| ID | noteの値 | 注意する文字 |
|---|---|---|
| C01 | 初回,確認 | 値の中のカンマ1個 |
| C02 | 一行目と二行目の間に改行 | 値の中の改行1個 |
| C03 | 担当者は"未定" | 値の中の二重引用符2個 |
RFC 4180は、カンマや改行、二重引用符を含むフィールドの引用と、フィールド内の二重引用符を重ねて表す方法を説明しています。ただし、すべての出力元が同じ形式とは限らないため、実際の区切りや引用ルールを確認します。RFC 4180(新しいタブで開く)
区切りを分割する処理と、CSV解析を分ける
この例でテキストの行数からヘッダー1行を引くと4となり、3レコードと一致しません。各行をカンマでsplitする方法も、C01の値にあるカンマを列の区切りと誤認します。
だからといってC02の改行を先に削除すると、元のメモの値を変えてしまいます。壊れた件数を整えるために内容を削る前に、引用符を解釈できるCSVパーサーで読みます。
今回の検算では、UTF-8へ保存できる文字列、区切りはカンマ、引用符は二重引用符、レコード区切りとC02内の改行はCRLFとします。コード内の\r\nがCRLFです。上のコードブロックは構造を見せるための表示で、コピー後の改行コードまで保証するものではありません。
パーサーで読み、列数と値を確認する
Pythonのcsv.readerは、引用符などの設定に沿ってレコードを読みます。ファイルを開く場合はnewline=""を指定し、改行処理をcsvモジュールへ渡す方法が公式資料で説明されています。以下は実ファイルを開かず、メモリ上の文字列を同じ条件で読みます。Python 3.9公式:csv(新しいタブで開く)
import csv
import io
csv_text = (
'id,note\r\n'
'C01,"初回,確認"\r\n'
'C02,"一行目\r\n二行目"\r\n'
'C03,"担当者は""未定"""\r\n'
)
reader = csv.reader(
io.StringIO(csv_text, newline=""),
delimiter=",", quotechar='"', doublequote=True, strict=True,
)
header = next(reader)
assert header == ["id", "note"]
records = []
positions = []
previous_line = reader.line_num
for record in reader:
if len(record) != 2:
raise ValueError("expected two columns")
records.append(record)
positions.append((previous_line + 1, reader.line_num))
previous_line = reader.line_num
expected = [
["C01", "初回,確認"],
["C02", "一行目\r\n二行目"],
["C03", '担当者は"未定"'],
]
assert records == expected
assert len(records) == 3
assert positions == [(2, 2), (3, 4), (5, 5)]
print(records)
print(positions)
この例では、C01は物理行2、C02は物理行3~4、C03は物理行5を消費します。reader.line_numは読み込んだ行数を示し、返したレコード数とは別です。位置をレコード番号へ読み替えないようにします。
2列という条件は今回の契約です。CSVパーサーが読めたことだけで、業務上必要な列数やIDの一意性まで合格したとは扱いません。空のID、重複、欠損の意味、値の型は別に確認します。
書き戻した後も、解析したセル値を比較する
次のコードを上の続きとして実行すると、3件をCSVへ書き戻し、もう一度読み取ってヘッダーとセル値を照合できます。
output = io.StringIO(newline="")
writer = csv.writer(
output, delimiter=",", quotechar='"', doublequote=True,
lineterminator="\r\n", quoting=csv.QUOTE_MINIMAL,
)
writer.writerow(header)
writer.writerows(records)
rewritten = output.getvalue()
parsed_again = list(csv.reader(
io.StringIO(rewritten, newline=""),
delimiter=",", quotechar='"', doublequote=True, strict=True,
))
assert parsed_again == [header] + expected
今回の目的は、解析したセル値を保持することです。別の引用方法やレコード区切りを採用する出力では、同じセル値でもファイル全体のバイト列が同じとは限りません。原本とバイト単位の一致が必要な作業なら、書き戻し自体を別の変更として扱います。
逆に、件数と列数が同じでも内容が保たれたとは限りません。改行が空白へ変わっていないか、引用符が一つ消えていないか、カンマが別の記号になっていないかを、IDごとの値で確認してください。
AIへの依頼では、文章の書き換えを混ぜない
次の架空CSVについて、構造と件数を確認してください。
文字コードはUTF-8、区切りはカンマ、引用符は二重引用符です。
引用内のカンマ・改行・二重引用符を値として保持してください。
ヘッダーはid,noteの2列、データは3レコードを期待します。
出力:ヘッダー、解析した全レコード、レコード数、列数の不一致、確認した設定。
noteの要約、表記統一、改行除去、欠損補完はしないでください。
物理行数をデータ件数として扱わないでください。
不正な引用や列数の不一致は、推測で直さず元の位置とともに報告してください。
実行していない解析を検証済みと書かず、外部取込や送信もしないでください。
入力:[説明用のCSV]。
メモを要約したい場合も、構造を確認する工程と文章を変更する工程を分けます。元値、要約案、採用した変更を別に残すと、CSV解析の誤りと編集上の変更を取り違えにくくなります。
実際の取込先で確かめること
今回、掲載コードで3レコード・2列、各セルの期待値、C02のCRLF、引用符、物理行位置、書き戻し後の値を照合しました。壊れた引用や余分な列を含む例も、別の検査として確認しています。任意のCSVすべての妥当性を保証する検査ではありません。
CRMや表計算へ渡すときは、そのシステムの文字コード、区切り、改行、引用符、型の設定で試します。CSVとして正しく引用したことは、表計算の数式評価を防いだことでもありません。自由記述を文字として扱う設定や、IDの先頭ゼロの保持は別の確認項目です。
受け渡しの件数には、ヘッダーを含むか、空行をどう扱うか、除外したレコードがあるかも記録します。取込先の件数と一致しなければ、元データの改行を消す前に、CSV解析と取込設定へ戻って確認してください。
参考資料
- RFC 4180:Common Format and MIME Type for CSV Files(新しいタブで開く)
- Python 3.9:csv — CSV File Reading and Writing(新しいタブで開く)
公式資料の確認日:2026年10月20日。3件は独自の架空データです。掲載Pythonの読み書きをローカルで検査しました。AIサービス、実ファイルの業務取込、CRM、Excel表示、メール送信は未検証です。
