AIへCSVを渡す前の件数確認|引用内の改行とカンマを保持する

架空CSV3件をPythonで読み書きし、5物理行と3レコードを区別。引用内の改行・カンマ・二重引用符とセル値を照合します。

AIものさし編集部

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の値へ足す指定ではありません。

表は横にスクロールできます

IDnoteの値注意する文字
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解析と取込設定へ戻って確認してください。

参考資料

公式資料の確認日:2026年10月20日。3件は独自の架空データです。掲載Pythonの読み書きをローカルで検査しました。AIサービス、実ファイルの業務取込、CRM、Excel表示、メール送信は未検証です。