CSVは誰も好きではないけれど誰もが使うフォーマットの一つです。1970年代から存在しており、 今でも地球上のすべてのスプレッドシートアプリのデフォルトエクスポート形式であり、今もあなたのデータパイプラインの どこかに潜んでいるはずです。フォーマットは見た目は単純です — カンマで区切られた値だけ — しかし正しく解析するのは驚くほど間違えやすいです。実際の動作、複雑になる場所、そして足を撃たずに PythonとJavaScriptで処理する方法を見ていきましょう。

CSVとは何か

CSVはComma-Separated Values(カンマ区切り値)の略です。CSVファイルはプレーンテキストです — バイナリエンコードなし、メタデータなし、行と列以外の構造なし。各行はレコードで、行内の各値は デリミター(通常はカンマ)で区切られ、最初の行は慣例的に列名を持つヘッダー行です。 実際のCSVはこんな感じです:

text
order_id,customer,product,quantity,unit_price,shipped
1001,Alice Nguyen,USB-C Hub,2,34.99,true
1002,Bob Martinez,Mechanical Keyboard,1,129.00,false
1003,Alice Nguyen,HDMI Cable,3,12.50,true
1004,Carol Smith,Webcam 1080p,1,79.95,false

それだけです — 6列、4データ行、1ヘッダー。アングルブラケットなし、波括弧なし、(まだ)クオートなし。 このシンプルさがまさにCSVが生き続ける理由です:地球上のほぼすべてのツールがそれを開き、読み、生成できます。 Excel、Google Sheets、PostgreSQLのCOPYコマンド、pandas、Rのread.csv() — すべてCSVをそのまま対応しています。トレードオフは、フォーマットにほとんど構造がないことです: 型なし、ネストなし、スキーマなし。すべての値はあなたが別途決定するまで文字列です。

RFC 4180 — 実際にはあまり適用されていない標準

仕様があります:2005年に発行された RFC 4180。 CRLF行末、ダブルクォートのエスケープ、埋め込まれたカンマの処理などを定義しています。 しかし、RFC 4180は情報提供目的であり、標準ではありません。既存の慣行を記述しているのであって、 規定しているわけではありません。誰も従う必要はなく、実際にはほぼ誰もそれを正確に従っていません。

結果はCSVダイアレクトの混乱です。CRLFの代わりにLFを使うファイル、最初の行がヘッダーかどうかわからないファイル、 末尾の改行があるファイルとないファイル、ExcelによってUTF-8 BOMが付加されたファイルがあります。 CSVのWikipedia記事 には実際にどこが変わるかについての良いまとめがあります。 最も安全なアプローチ:自分で作成していないCSVファイルについて何も仮定せず、カンマを単純に分割するのではなく 常に適切なパーサーを使用すること。

クォートルール:値の中にカンマが現れる場合

ここで「カンマで単純に分割する」が崩壊します。値にカンマが含まれている場合はどうなりますか? または改行は?またはダブルクォートは?RFC 4180 — そして実際のほとんどのパーサー — フィールドをダブルクォートで ラップすることでこれを処理します。ルールの完全なセットを以下に示します:

  • フィールドにカンマ、改行、またはダブルクォートが含まれる場合、フィールド全体をダブルクォートでラップする
  • フィールドにダブルクォートが含まれる場合、それを二重にする"")ことでエスケープする
  • クォートされたフィールドは複数行にまたがることができる — 改行は値の一部になる
  • クォート内の空白は重要であり、保持されなければならない
text
order_id,customer,notes,unit_price
1005,David Lee,"Wants gift wrapping, express shipping",45.00
1006,Emma Brown,"Said: ""please handle with care""",89.99
1007,Frank Wu,"Address:
123 Main St
Apt 4B",15.50

この例では:Davidのnotesにカンマが含まれているのでクォートされています。Emmaのノートには ダブルクォートが含まれているので、外側のクォート内で二重になっています。Frankの住所は複数行にまたがります — 改行は値の一部です。どの言語でも素朴なline.split(',')はこれら3つを完全に壊してしまいます。 だから本物のパーサーが必要なのです。

CSV解析の黄金律:手動でカンマを分割しない。常に専用のCSV解析ライブラリを使用する。 クォート、エスケープシーケンス、複数行フィールド、エンコーディングを処理します — 単純なsplitではどれも正しくできません。

デリミターのバリアント:タブ、セミコロン、パイプ

名前にもかかわらず、CSVはカンマを使う必要はありません。いくつかの一般的なバリアントは異なるデリミターを使用し、 実際にはすべてに遭遇するでしょう:

  • TSV(タブ区切り値)\tをデリミターとして使用。バイオインフォマティクス(BLAST出力、VCFファイル)、データベースエクスポート、値自体にカンマが頻繁に含まれる場所で一般的
  • セミコロン区切り — カンマが小数点記号であるロケール(ドイツ、フランス、EUの大部分)でのExcelのデフォルト。ヨーロッパの同僚からのCSVを開いて1つの巨大な列が得られた場合、これが理由
  • パイプ区切り|を使用。タブがメインフレームシステムによって削除される可能性がある従来の銀行や保険のデータエクスポートで一般的
  • 固定幅 — 技術的にはCSVではありませんが、同じカテゴリによくまとめられます。列はデリミターで区切られるのではなく、固定幅にパディングされます

ほとんどのCSVパーサーでは、デリミターを明示的に指定できます。他の人が使用するCSVを書く際は、 デリミターを文書化してください。自分で作成していないものを読む場合は、仮定する前に最初の数行を確認してください。 CSVフォーマッターは非標準のデリミターを持つファイルを検査して再フォーマットするのに役立ちます。

PythonでのCSV解析

Pythonの標準ライブラリには csvモジュール が含まれており、本当に優れています。最もよく使う2つのクラスは、行をリストとしてアクセスするためのcsv.readerと、 行を辞書としてアクセスするためのcsv.DictReader(ほとんどの場合こちらが欲しいもの)です。

python
import csv

# csv.reader — each row is a list of strings
with open('orders.csv', 'r', encoding='utf-8', newline='') as f:
    reader = csv.reader(f)
    header = next(reader)          # consume the header row
    for row in reader:
        order_id, customer, product, quantity, price, shipped = row
        print(f"Order {order_id}: {quantity}x {product} for {customer}")

# csv.DictReader — each row is an OrderedDict keyed by the header
with open('orders.csv', 'r', encoding='utf-8', newline='') as f:
    reader = csv.DictReader(f)
    for row in reader:
        if row['shipped'] == 'false':
            print(f"Pending: {row['order_id']} — {row['customer']}")

常に含めるべき2つのこと:encoding='utf-8'(またはファイルが実際に使用するもの)、 とnewline=''。2番目のものは重要です — csvモジュールは独自の改行処理を行い、 生のバイトが必要です。newline=''なしでは、Windowsで余分な空白行が得られる可能性があります。

書き込みもcsv.writerで同様に簡単です:

python
import csv

orders = [
    {'order_id': 1008, 'customer': 'Grace Kim', 'product': 'Laptop Stand', 'quantity': 1, 'unit_price': 49.99, 'shipped': False},
    {'order_id': 1009, 'customer': 'Henry Park', 'product': 'USB-C Hub', 'quantity': 2, 'unit_price': 34.99, 'shipped': True},
]

with open('new_orders.csv', 'w', encoding='utf-8', newline='') as f:
    fieldnames = ['order_id', 'customer', 'product', 'quantity', 'unit_price', 'shipped']
    writer = csv.DictWriter(f, fieldnames=fieldnames)

    writer.writeheader()
    writer.writerows(orders)

# To use a different delimiter (e.g. tab-separated)
with open('new_orders.tsv', 'w', encoding='utf-8', newline='') as f:
    writer = csv.writer(f, delimiter='\t')
    writer.writerow(['order_id', 'customer', 'product'])
    writer.writerow([1008, 'Grace Kim', 'Laptop Stand'])

csvモジュールはすべてのクォートを自動的に処理します — フィールドにカンマや改行が含まれている場合、 考えることなくダブルクォートでラップします。これがライブラリを使用するポイントです。

JavaScript / Node.jsでのCSV解析

JavaScriptには組み込みのCSVパーサーがありません。誘惑されがちなのはこれをすることです:

js
// ❌ Don't do this — breaks immediately on quoted fields
const rows = csvText.split('\n').map(line => line.split(','));

値にカンマ、クォート内の改行、またはエスケープされたダブルクォートが含まれた瞬間に失敗します。 実際のものには、ライブラリを使用してください。 PapaParseが定番です — 高速で、すべてのエッジケースを処理し、ブラウザとNode.jsの両方で動作し、大きなファイルのストリーミングをサポートします。

js
import Papa from 'papaparse';
import fs from 'fs';

// Parse a CSV string
const csvText = fs.readFileSync('orders.csv', 'utf8');

const result = Papa.parse(csvText, {
  header: true,          // first row becomes object keys
  skipEmptyLines: true,  // ignore blank lines at end of file
  dynamicTyping: true,   // converts "true"/"false" to booleans, numbers to numbers
});

console.log(result.data);
// [
//   { order_id: 1001, customer: 'Alice Nguyen', product: 'USB-C Hub', quantity: 2, unit_price: 34.99, shipped: true },
//   { order_id: 1002, customer: 'Bob Martinez', product: 'Mechanical Keyboard', quantity: 1, unit_price: 129, shipped: false },
//   ...
// ]

// Check for parse errors
if (result.errors.length > 0) {
  console.error('Parse errors:', result.errors);
}

// Generate CSV from an array of objects
const orders = [
  { order_id: 1008, customer: 'Grace Kim', product: 'Laptop Stand', quantity: 1, unit_price: 49.99 },
  { order_id: 1009, customer: 'Henry Park', product: 'USB-C Hub', quantity: 2, unit_price: 34.99 },
];

const csvOutput = Papa.unparse(orders);
fs.writeFileSync('export.csv', csvOutput, 'utf8');

dynamicTypingオプションは便利ですが知っておく価値があります — "34.99"のようなものを 自動的に数値に変換します。それは通常欲しいものですが、order_idのようなフィールドがCSVの数値だが 文字列として欲しかった場合、驚くかもしれません。厳格な文字列出力が必要な場合はオフにしてください。

CSVと他のフォーマット間のクイック変換には、CSVからJSONツールが ブラウザでコードなしで最も一般的なケースを処理します — ワンオフのデータ変換に便利です。 XMLベースのシステムにデータを送る必要がある場合はCSVからXMLもあります。

確実に遭遇する一般的な落とし穴

良いパーサーを使っても、CSVにはいくつかのよく知られた地雷があります。最もよく出てくるものを以下に示します:

  • ExcelのBOMバイト。Excelが「UTF-8」としてCSVをエクスポートする場合、UTF-8 BOM (16進数のEF BB BF、または文字として\ufeff)を付加することがよくあります。 これにより最初の列ヘッダーがorder_idではなくorder_idのように見えます。Pythonでは、 自動的に削除するためにutf-8の代わりにencoding='utf-8-sig'でファイルを開いてください。 PapaParseはそれを透明に処理します。
  • CRLF対LF行末。RFC 4180はCRLF(\r\n)を指定していますが、Unixツールは LF(\n)を生成し、古いMacファイルはCR(\r)のみを使用します。 これがPythonのcsvモジュールがnewline=''を必要とする理由です — 3つすべてを内部で処理します。生のバイトを読んで手動で分割している場合は、\rを 明示的に削除する必要があります。
  • エンコーディングの問題。CSVはHTMLの<meta charset>や XMLの<?xml encoding="..."?>とは異なり、独自のエンコーディングを宣言する方法がありません。 ExcelはUTF-8ではなくWindows-1252(別名CP1252)でファイルを保存することが多く、アクセント文字が壊れます。 éの代わりにéのような文字が見える場合、UTF-8ファイルがLatin-1としてデコードされているか、 その逆です。ファイルを作成する側とアウトオブバンドで常にエンコーディングを確立してください。
  • 日付のように見える数値。Excelは開くときや保存するときに1-203/04のような 値を静かに日付に変換します。製品コードやバージョン番号をエクスポートする場合は、Excelでシングルクォートを付けて ('1-2)これを防いでください — またはファイルを作成する側にそうするよう伝えてください。
  • 末尾のカンマ。一部のエクスポーターは各行の末尾にカンマを付加し、 幻の空の列を作成します。堅牢なパーサーはそれを無視しますが、単純な分割は余分な空の文字列要素を作成します。

おかしそうなファイルを扱っている場合、CSVバリデーターで 処理しようとする前にファイルが整形式かどうかを素早く確認し、エンコーディングや構造的な問題を指摘できます。

CSV対JSON対Excel — 何をいつ使うか

これら3つのフォーマットは実際には多くの重複がありますが、それぞれに明確な得意分野があります:

  • CSVを使う場合:フラットな表形式データをシステム間で移動する場合 — データベースエクスポート、 分析パイプライン、スプレッドシートインポート、一括データ読み込み。普遍的にサポートされ、サイズが小さく、 gitで trivially 差分できます。制約:フラットです。ネストなし、型なし、関係なし。
  • JSONを使う場合:データが階層的またはスキーマが重要な場合。複数の明細のある注文、 ネストされたオブジェクトを持つ設定ファイル、APIレスポンス — これらは自然にJSONです。CSVはデータを 非正規化したり独自のネスト規則を考案するよう強制します。 JSON仕様はクリーンで 曖昧さがありません;フォーマットは型(数値、ブール値、null、配列、オブジェクト)を保持します。
  • Excel (.xlsx)を使う場合:出力が機械ではなく人間のための場合。書式設定、数式、 複数のシート、チャート — ビジネスユーザーが最終消費者なら、Excelが正しい選択である場合が多いです。 システム間の交換フォーマットとして決して使わないでください。 OOXML仕様 は巨大でバージョン間でフォーマットが脆弱です。

実用的なヒューリスティック:スプレッドシートでデータを自然に見るなら、CSVでおそらく大丈夫です。 ツリーやコードエディターで見るなら、JSONを使用してください。フィルタリングとソートをするビジネス関係者に送るなら、 Excelを使用してください。

まとめ

CSVは設計上シンプルで、実際には予想外に難しいです。フォーマットには強制された標準がなく、 複数のデリミターバリアントで出荷され、あらゆる手動解析の試みを破るクォートルールに依存しています。 解決策は常に同じです:本物のパーサーを使用する(Pythonの組み込みcsvモジュール、または JavaScriptのPapaParse)、常にエンコーディングを明示的に指定し、ExcelのBOMバイトに注意してください。 信頼できるパーサーを配置すれば、CSVは実際には素晴らしいフォーマットです — 生成が速く、任意のテキストエディタで 検査しやすく、どこでもサポートされています。CSVファイルの日常業務には、 CSVからJSONCSVフォーマッター、および CSVバリデーターツールがコードを1行も書かずに一般的な操作を処理します。