メインコンテンツに移動

PythonでCSVを読み書きする方法|reader・writer・pandasを徹底解説

CSVは、表計算ソフト、データベース、業務システムなどの間で、表形式データを交換するためによく使われるテキスト形式です。一般的には項目をカンマで区切りますが、タブやセミコロンを区切り文字にする形式もあり、引用符や改行の扱いにもシステムごとの差があります。Pythonの公式ドキュメントも、CSVには提供元ごとの細かな形式差が存在すると説明しています。

Pythonでは、標準ライブラリのcsvモジュールを使ってCSVを読み書きできます。行をリストとして扱うreaderwriterに加え、列名をキーとする辞書として扱えるDictReaderDictWriterが用意されています。

2026年7月時点の最新安定版はPython 3.14.6です。本記事の基本コードはPython 3.10以降を想定していますが、csv.readercsv.writerなどの中心的な機能は、より古いPython 3でも利用できます。

1. CSVの基本構造を理解する

CSVを正しく処理するには、単純に文字列をカンマで分割するだけでは不十分です。区切り文字を含む値、引用符、セル内改行などの規則を理解する必要があります。

1.1 CSVとは

CSVはComma-Separated Valuesの略で、行と列からなるデータをテキストで表現する形式です。基本的には一行が一件のレコードを表し、一行内の複数項目をカンマで区切ります。

id,name,price 1,Python入門,3000 2,データ分析入門,2800

RFC 4180では、各行の項目数をそろえ、カンマを区切り文字として使用する一般的な形式が説明されています。ただし、このRFCは情報提供を目的とした文書であり、現実には多くのCSV方言が存在します。

1.2 ヘッダー行とは

CSVの最初の行には、各列の名前を記述することがあります。この行をヘッダー行と呼びます。

product_id,product_name,stock A001,商品A,10 A002,商品B,5

ヘッダーは必須ではありません。ヘッダーがないCSVでは、読み込み側が列の順番と意味をあらかじめ知っている必要があります。

A001,商品A,10 A002,商品B,5

1.3 カンマを含む値を表現する

セルの値自体にカンマが含まれる場合は、値全体を二重引用符で囲みます。

id,address 1,"東京都千代田区1-2,サンプルビル"

引用符で囲まれたカンマは、列の区切りではなく値の一部として扱われます。RFC 4180でも、カンマを含むフィールドは二重引用符で囲む形式が説明されています。

1.4 二重引用符を含む値を表現する

引用符で囲まれたフィールド内に二重引用符を含める場合は、二重引用符を二つ続けて記述します。

id,message 1,"彼は""Pythonを学びます""と言いました"

読み込み後の値は次の文字列になります。

彼は"Pythonを学びます"と言いました

RFC 4180では、引用符内の二重引用符を、もう一つの二重引用符でエスケープする形式が示されています。

1.5 単純なsplitを使わない

次のようにsplit(",")で分割すると、引用符で囲まれたカンマも分割されてしまいます。

line = '1,"東京,大阪",3000' print(line.split(","))

実行結果は意図した三列になりません。

['1', '"東京', '大阪"', '3000']

CSVを処理するときは、引用符や改行を理解できるcsv.readerを使用します。

2. csv.readerでCSVを読み込む

csv.readerは、CSVの各行をリストとして返します。列の順番が固定されている単純なCSV処理に適しています。

2.1 csv.readerの基本的な使い方

CSVファイルをopenで開き、ファイルオブジェクトをcsv.readerへ渡します。CSVファイルを開くときは、newline=""を指定することが公式に推奨されています。

import csv with open(    "products.csv",    "r",    encoding="utf-8",    newline="", ) as file:    reader = csv.reader(file)    for row in reader:        print(row)

次のCSVを読み込んだとします。

id,name,price 1,Python入門,3000 2,Java入門,2800

各行はリストとして取得されます。

['id', 'name', 'price'] ['1', 'Python入門', '3000'] ['2', 'Java入門', '2800']

2.2 ヘッダー行を読み飛ばす

最初の一行がヘッダーの場合は、nextを使って先に取得できます。

import csv with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.reader(file)    header = next(reader)    print(f"列名: {header}")    for row in reader:        print(row)

空ファイルの可能性がある場合は、next(reader, None)を使います。

header = next(reader, None) if header is None:    print("CSVファイルが空です")

2.3 インデックスで値を取得する

csv.readerが返す各行はリストなので、インデックスを使って値を取得します。

import csv with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.reader(file)    next(reader, None)    for row in reader:        product_id = row[0]        name = row[1]        price = int(row[2])        print(            product_id,            name,            price,        )

列の順番が変わると取得位置も変わります。列数が多い場合や、列の追加・削除が予想される場合はDictReaderが適しています。

2.4 読み込まれる値は文字列になる

csv.readerは、通常、各セルを文字列として返します。数値のように見える3000strです。QUOTE_NONNUMERICを使う場合を除き、自動的な型変換は行われません。

row = ["1", "Python入門", "3000"] print(type(row[0])) print(type(row[2]))

必要な列を明示的に変換します。

product_id = int(row[0]) price = int(row[2])

2.5 リストとして全件を読み込む

小さなCSVであれば、listを使って全行をまとめて読み込めます。

import csv with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    rows = list(csv.reader(file)) print(rows)

大容量ファイルでは、全件をリストへ入れるとメモリを多く使用します。通常はfor文で一行ずつ処理する方が安全です。

3. DictReaderでCSVを辞書として読み込む

csv.DictReaderは、各行を辞書として返します。ヘッダー名をキーとして値へアクセスできるため、業務データでは特に使いやすい方法です。

3.1 DictReaderの基本的な使い方

ヘッダー付きCSVをDictReaderへ渡すと、最初の行が辞書のキーとして使用されます。

import csv with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(file)    for row in reader:        print(row)

次のような辞書が取得されます。

{'id': '1', 'name': 'Python入門', 'price': '3000'} {'id': '2', 'name': 'Java入門', 'price': '2800'}

3.2 列名で値を取得する

インデックスではなく列名で値へアクセスできます。

import csv with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(file)    for row in reader:        product_id = row["id"]        name = row["name"]        price = int(row["price"])        print(            f"{product_id}: {name} "            f"{price}円"        )

列の順番が変更されても、列名が同じであればコードを変更する必要はありません。

3.3 fieldnamesを確認する

DictReader.fieldnamesから、CSVの列名一覧を取得できます。

import csv with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(file)    print(reader.fieldnames)

必要な列が存在するかを処理前に確認できます。

required_columns = {    "id",    "name",    "price", } actual_columns = set(    reader.fieldnames or [] ) missing_columns = (    required_columns - actual_columns ) if missing_columns:    raise ValueError(        "必要な列がありません: "        + ", ".join(            sorted(missing_columns)        )    )

3.4 ヘッダーがないCSVを読み込む

ヘッダーがない場合は、fieldnamesを明示的に指定します。

import csv column_names = [    "id",    "name",    "price", ] with open(    "products_without_header.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(        file,        fieldnames=column_names,    )    for row in reader:        print(row)

fieldnamesを指定した場合、CSVの一行目もデータとして処理されます。公式仕様でも、明示的に列名を渡した場合は最初の行を読み飛ばさないと説明されています。

3.5 列数の不一致を処理する

ヘッダーより多くの列がある場合、余分な値はrestkeyで指定したキーへリストとして格納できます。

import csv with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(        file,        restkey="_extra",        restval=None,    )    for row in reader:        if row.get("_extra"):            raise ValueError(                f"余分な列があります: "                f"{row['_extra']}"            )

ヘッダーより列が少ない行では、不足した値にrestvalが設定されます。これらの動作はDictReaderの公式仕様で定義されています。

4. csv.writerでCSVを書き込む

csv.writerは、リストやタプルなどの各要素を一行のCSVとして書き込みます。

4.1 writerの基本的な使い方

ファイルを"w"モードで開き、csv.writerを作成します。書き込み時もnewline=""を指定します。

import csv rows = [    ["id", "name", "price"],    [1, "Python入門", 3000],    [2, "Java入門", 2800], ] with open(    "products.csv",    "w",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(file)    for row in rows:        writer.writerow(row)

生成されるCSVは次のようになります。

id,name,price 1,Python入門,3000 2,Java入門,2800

4.2 writerowで一行を書き込む

writerowは、一行分のデータを書き込みます。

import csv with open(    "products.csv",    "w",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(file)    writer.writerow([        "id",        "name",        "price",    ])    writer.writerow([        1,        "Python入門",        3000,    ])

文字列以外の値は、基本的にstrへ変換されて書き込まれます。Noneは空文字列として書き込まれ、この変換は元に戻せないため注意が必要です。

4.3 writerowsで複数行を書き込む

writerowsを使うと、複数行をまとめて書き込めます。

import csv products = [    [1, "Python入門", 3000],    [2, "Java入門", 2800],    [3, "SQL入門", 2500], ] with open(    "products.csv",    "w",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(file)    writer.writerow([        "id",        "name",        "price",    ])    writer.writerows(products)

writerowsにはリストだけでなく、行を順番に返すイテラブルも渡せます。

4.4 カンマや改行を含む値を書き込む

csv.writerは、必要な値を自動的に引用符で囲みます。

import csv rows = [    [        "id",        "description",    ],    [        1,        "東京,大阪",    ],    [        2,        "1行目\n2行目",    ], ] with open(    "sample.csv",    "w",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(file)    writer.writerows(rows)

出力例は次のようになります。

id,description 1,"東京,大阪" 2,"1行目 2行目"

値を自分で引用符で囲む必要はありません。

4.5 既存ファイルを誤って上書きしない

"w"モードでは、既存ファイルの内容が消去されます。既存ファイルがある場合に失敗させたいときは"x"モードを使えます。

import csv with open(    "products.csv",    "x",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(file)    writer.writerow([        "id",        "name",    ])

ファイルがすでに存在するとFileExistsErrorが発生します。

5. DictWriterで辞書をCSVへ書き込む

csv.DictWriterを使うと、辞書のキーとCSV列を対応付けて書き込めます。

5.1 DictWriterの基本的な使い方

DictWriterでは、出力する列名と順番をfieldnamesで指定します。fieldnamesは必須です。

import csv products = [    {        "id": 1,        "name": "Python入門",        "price": 3000,    },    {        "id": 2,        "name": "Java入門",        "price": 2800,    }, ] fieldnames = [    "id",    "name",    "price", ] with open(    "products.csv",    "w",    encoding="utf-8",    newline="", ) as file:    writer = csv.DictWriter(        file,        fieldnames=fieldnames,    )    writer.writeheader()    writer.writerows(products)

5.2 writeheaderでヘッダーを書く

writeheaderは、fieldnamesをヘッダー行として出力します。

writer = csv.DictWriter(    file,    fieldnames=[        "id",        "name",        "price",    ], ) writer.writeheader()

出力されるヘッダーは次のとおりです。

id,name,price

ヘッダーが不要な場合はwriteheaderを呼び出しません。

5.3 辞書のキー順序に依存しない

出力される列順は、辞書内のキー順ではなく、fieldnamesで指定した順番です。

product = {    "price": 3000,    "id": 1,    "name": "Python入門", } fieldnames = [    "id",    "name",    "price", ]

この場合もCSVはid,name,priceの順で出力されます。列順を仕様として明示できる点がDictWriterの利点です。

5.4 不足するキーを処理する

辞書にfieldnamesのキーが存在しない場合は、初期状態では空文字列が出力されます。別の値を使う場合はrestvalを指定します。

writer = csv.DictWriter(    file,    fieldnames=[        "id",        "name",        "price",    ],    restval="未設定", )

たとえばpriceが存在しない辞書は、次のように出力されます。

1,Python入門,未設定

5.5 余分なキーを処理する

辞書にfieldnamesへ存在しないキーがあると、初期状態ではValueErrorが発生します。

product = {    "id": 1,    "name": "Python入門",    "price": 3000,    "category": "書籍", }

余分な項目を無視したい場合は、extrasaction="ignore"を指定できます。

writer = csv.DictWriter(    file,    fieldnames=[        "id",        "name",        "price",    ],    extrasaction="ignore", )

ただし、項目名の入力ミスまで無視する可能性があります。通常は初期値の"raise"で問題を検出する方が安全です。

6. 文字コードとExcelの文字化けへ対応する

CSVの文字化けは、Pythonの処理より、ファイルの文字コードと読み込むアプリケーションの解釈が一致していないことが原因です。

6.1 UTF-8で読み書きする

新しく作るCSVでは、基本的にUTF-8を使用すると扱いやすくなります。

import csv with open(    "products.csv",    "w",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(file)    writer.writerow([        "商品名",        "価格",    ])

読み込み時も同じ文字コードを指定します。

with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.reader(file)

Python公式ドキュメントでも、システム既定以外の文字コードを使用する場合は、openencoding引数を指定するよう説明されています。

6.2 Excel向けにUTF-8 BOMを付ける

Excelなど一部の表計算環境では、UTF-8の判定にBOMが必要になる場合があります。MicrosoftのPower Queryドキュメントでも、UTF-8はBOMがある場合に推測される動作が説明されています。

Pythonではutf-8-sigを指定すると、書き込み時にUTF-8 BOMを付けられます。

import csv with open(    "products_for_excel.csv",    "w",    encoding="utf-8-sig",    newline="", ) as file:    writer = csv.writer(file)    writer.writerow([        "商品名",        "価格",    ])    writer.writerow([        "Python入門",        3000,    ])

読み込み時にutf-8-sigを使うと、BOMを自動的に取り除けます。

6.3 Shift_JIS系CSVを読み込む

日本語の古い業務システムやWindows用ソフトでは、Shift_JIS系のCSVが使われることがあります。

import csv with open(    "legacy.csv",    encoding="cp932",    newline="", ) as file:    reader = csv.reader(file)    for row in reader:        print(row)

Windowsで作られた日本語CSVでは、厳密なshift_jisよりcp932で読み込める文字が多い場合があります。送信元の仕様を確認して指定してください。

6.4 UnicodeDecodeErrorを処理する

指定した文字コードとファイルの実際の文字コードが異なると、UnicodeDecodeErrorが発生することがあります。

import csv from pathlib import Path path = Path("products.csv") try:    with path.open(        encoding="utf-8",        newline="",    ) as file:        for row in csv.reader(file):            print(row) except UnicodeDecodeError as error:    raise ValueError(        "CSVをUTF-8で読み込めません。"        "文字コードを確認してください"    ) from error

errors="ignore"で不正文字を無条件に捨てると、商品名や顧客名などのデータが欠損する可能性があります。

6.5 読み書きの文字コードを仕様化する

「Excelで開けるCSV」のような曖昧な条件ではなく、次の項目を明確にします。

確認項目
文字コードUTF-8 BOM付き
区切り文字カンマ
改行CRLF
ヘッダーあり
引用符必要なセルのみ
空値空文字列
日付形式YYYY-MM-DD

CSVを受け渡すシステム間で仕様を共有すると、環境ごとの文字化けや列ずれを防ぎやすくなります。

7. 区切り文字・引用符・改行を設定する

Pythonのcsvモジュールでは、カンマ以外の区切り文字や、引用符の付け方を細かく設定できます。

7.1 delimiterを変更する

タブ区切りファイルを読み込む場合は、delimiter="\t"を指定します。

import csv with open(    "products.tsv",    encoding="utf-8",    newline="", ) as file:    reader = csv.reader(        file,        delimiter="\t",    )    for row in reader:        print(row)

セミコロン区切りなら次のように指定します。

reader = csv.reader(    file,    delimiter=";", )

delimiterは一文字で指定し、初期値はカンマです。

7.2 quotecharを変更する

フィールドを囲む引用符は、初期状態では二重引用符です。

reader = csv.reader(    file,    quotechar='"', )

独自形式で一重引用符を使う場合は変更できます。

reader = csv.reader(    file,    quotechar="'", )

一般的なCSVでは二重引用符を使用するため、送信元の仕様が明確な場合だけ変更します。

7.3 quotingで引用方法を指定する

quotingでは、どの項目を引用符で囲むかを指定できます。

import csv with open(    "all_quoted.csv",    "w",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(        file,        quoting=csv.QUOTE_ALL,    )    writer.writerow([        1,        "Python入門",        3000,    ])

出力は次のようになります。

"1","Python入門","3000"

代表的な設定は次のとおりです。

定数動作
QUOTE_MINIMAL必要な値だけ引用
QUOTE_ALLすべて引用
QUOTE_NONNUMERIC非数値を引用
QUOTE_NONE引用しない
QUOTE_NOTNULLNone以外を引用
QUOTE_STRINGS文字列を引用

QUOTE_NOTNULLQUOTE_STRINGSはPython 3.12で追加されました。

7.4 newline=""を指定する

CSVファイルを開くときは、読み込み・書き込みの両方でnewline=""を指定します。

with open(    "products.csv",    "w",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(file)

指定しない場合、引用符内の改行が正しく解釈されなかったり、Windowsで余分な改行が追加されたりする可能性があります。Python公式ドキュメントは、csvモジュール自身に改行処理を任せるためnewline=""を指定することを推奨しています。

7.5 lineterminatorを指定する

出力する行末文字はlineterminatorで指定できます。csv.writerの初期値は\r\nです。

writer = csv.writer(    file,    lineterminator="\n", )

OSに関係なくLFへ統一したい場合に使用できます。受け取り側が特定の改行形式を要求する場合は、その仕様へ合わせます。

8. CSVの値を型変換・検証する

CSVには型情報がありません。数値、日付、真偽値も文字列として保存されるため、読み込み後の変換と検証が必要です。

8.1 整数へ変換する

数値列はintで変換します。

import csv with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(file)    for row in reader:        try:            price = int(row["price"])        except ValueError as error:            raise ValueError(                f"価格が整数ではありません: "                f"{row['price']}"            ) from error

空文字列やカンマ付き数値は、そのままでは変換できません。

price = int(    row["price"].replace(",", "") )

8.2 小数をDecimalへ変換する

金額や精密な小数にはDecimalを利用できます。

from decimal import Decimal, InvalidOperation try:    rate = Decimal(row["rate"]) except InvalidOperation as error:    raise ValueError(        f"小数の形式が不正です: "        f"{row['rate']}"    ) from error

小数点記号や桁区切り文字は国・地域によって異なります。CSVの形式として事前に決めておく必要があります。

8.3 日付へ変換する

日付文字列はdatetime.strptimedate.fromisoformatで変換できます。

from datetime import date try:    published_on = date.fromisoformat(        row["published_on"]    ) except ValueError as error:    raise ValueError(        "日付はYYYY-MM-DD形式で"        "指定してください"    ) from error

CSVへ書き込むときも同じ形式へそろえます。

published_on.isoformat()

8.4 真偽値へ変換する

CSVには標準的な真偽値表現がありません。truefalse10yesnoなど、受け取る値を明確にします。

def parse_boolean(value: str) -> bool:    normalized = value.strip().lower()    if normalized in {        "true",        "1",        "yes",    }:        return True    if normalized in {        "false",        "0",        "no",    }:        return False    raise ValueError(        f"真偽値として解釈できません: "        f"{value}"    )

想定外の値を自動的にFalseへ変換すると、入力ミスを見逃す可能性があります。

8.5 dataclassへ変換する

検証後の行をデータクラスへ変換すると、業務データとして扱いやすくなります。

import csv from dataclasses import dataclass from decimal import Decimal from pathlib import Path @dataclass(frozen=True) class Product:    product_id: str    name: str    price: Decimal def load_products(    path: Path, ) -> list[Product]:    products: list[Product] = []    with path.open(        encoding="utf-8",        newline="",    ) as file:        reader = csv.DictReader(file)        for line_number, row in enumerate(            reader,            start=2,        ):            try:                product = Product(                    product_id=row["id"].strip(),                    name=row["name"].strip(),                    price=Decimal(                        row["price"]                    ),                )            except (                KeyError,                ValueError,            ) as error:                raise ValueError(                    f"{line_number}行目が不正です"                ) from error            products.append(product)    return products

CSV入出力と、アプリケーション内部のデータ型を分離できます。

9. CSVを更新・追記・結合する

CSVファイルにはデータベースのような行更新機能がありません。通常は全件を読み込み、変更後の内容を書き直します。

9.1 CSVへ行を追記する

既存ファイルの末尾へ行を追加する場合は、"a"モードを使用します。

import csv with open(    "products.csv",    "a",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(file)    writer.writerow([        3,        "SQL入門",        2500,    ])

ヘッダーを再度書かないように注意します。

9.2 新規ファイルだけヘッダーを書く

ファイルが存在しない場合や空の場合だけ、ヘッダーを書き込めます。

import csv from pathlib import Path path = Path("products.csv") needs_header = (    not path.exists()    or path.stat().st_size == 0 ) with path.open(    "a",    encoding="utf-8",    newline="", ) as file:    writer = csv.DictWriter(        file,        fieldnames=[            "id",            "name",            "price",        ],    )    if needs_header:        writer.writeheader()    writer.writerow({        "id": 3,        "name": "SQL入門",        "price": 2500,    })

複数プロセスが同時に追記する場合は、ファイルロックなどの追加設計が必要です。

9.3 特定の行を更新する

CSVを直接部分更新するのではなく、読み込んだ行を変更して新しいファイルへ書き出します。

import csv from pathlib import Path source = Path("products.csv") temporary = Path("products.tmp") with source.open(    encoding="utf-8",    newline="", ) as input_file, temporary.open(    "w",    encoding="utf-8",    newline="", ) as output_file:    reader = csv.DictReader(        input_file    )    if reader.fieldnames is None:        raise ValueError(            "ヘッダーがありません"        )    writer = csv.DictWriter(        output_file,        fieldnames=reader.fieldnames,    )    writer.writeheader()    for row in reader:        if row["id"] == "2":            row["price"] = "2900"        writer.writerow(row) temporary.replace(source)

一時ファイルへの書き込みが成功してから元ファイルを置き換えます。

9.4 複数のCSVを結合する

同じ列を持つ複数CSVを一つへまとめられます。

import csv from pathlib import Path input_paths = [    Path("sales_01.csv"),    Path("sales_02.csv"), ] output_path = Path("sales_all.csv") fieldnames = [    "date",    "product",    "amount", ] with output_path.open(    "w",    encoding="utf-8",    newline="", ) as output_file:    writer = csv.DictWriter(        output_file,        fieldnames=fieldnames,    )    writer.writeheader()    for input_path in input_paths:        with input_path.open(            encoding="utf-8",            newline="",        ) as input_file:            reader = csv.DictReader(                input_file            )            for row in reader:                writer.writerow(row)

各ファイルの列名と形式が一致しているかを事前に検証します。

9.5 重複行を除去する

一意なIDを使って重複を除去できます。

import csv unique_rows: dict[    str,    dict[str, str], ] = {} with open(    "products.csv",    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(file)    for row in reader:        product_id = row["id"]        if product_id in unique_rows:            raise ValueError(                f"IDが重複しています: "                f"{product_id}"            )        unique_rows[product_id] = row

最後の行を優先する場合は、エラーを発生させずに上書きします。どの行を残すかは業務仕様で決めます。

10. CSV読み書きのエラーを処理する

CSV処理では、ファイル不存在、文字コード、列数、数値変換、不正な引用符など、複数種類のエラーが発生します。

10.1 FileNotFoundErrorを処理する

存在しないCSVを開くとFileNotFoundErrorが発生します。

import csv from pathlib import Path path = Path("products.csv") try:    with path.open(        encoding="utf-8",        newline="",    ) as file:        rows = list(            csv.reader(file)        ) except FileNotFoundError as error:    raise RuntimeError(        f"CSVファイルがありません: "        f"{path}"    ) from error

ファイルが必須なのか、存在しない場合は空データとして扱うのかを決めます。

10.2 csv.Errorを処理する

CSV形式の解析中に問題が検出されると、csv.Errorが発生する場合があります。

import csv from pathlib import Path path = Path("products.csv") with path.open(    encoding="utf-8",    newline="", ) as file:    reader = csv.reader(        file,        strict=True,    )    try:        for row in reader:            print(row)    except csv.Error as error:        raise ValueError(            f"{reader.line_num}行付近の"            f"CSV形式が不正です"        ) from error

strict=Trueを指定すると、不正なCSV入力でcsv.Errorを発生させます。

10.3 行番号をエラーへ含める

reader.line_numから、現在までに読み込んだ物理行数を取得できます。

for row in reader:    try:        price = int(row[2])    except (        IndexError,        ValueError,    ) as error:        raise ValueError(            f"{reader.line_num}行目の"            "価格が不正です"        ) from error

引用符内に改行があるCSVでは、一件のレコードが複数の物理行へまたがるため、line_numとレコード件数は同じとは限りません。

10.4 列数を検証する

csv.readerを使う場合は、期待する列数を確認します。

EXPECTED_COLUMNS = 3 for row in reader:    if len(row) != EXPECTED_COLUMNS:        raise ValueError(            f"{reader.line_num}行目の"            f"列数が不正です: "            f"{len(row)}"        )

RFC 4180では、各行のフィールド数を同じにする一般的な形式が説明されています。

10.5 エラー行だけを別ファイルへ出力する

大量データの取り込みでは、一件の不正データで全処理を停止せず、正常行とエラー行を分ける方法があります。

import csv with open(    "input.csv",    encoding="utf-8",    newline="", ) as input_file, open(    "errors.csv",    "w",    encoding="utf-8",    newline="", ) as error_file:    reader = csv.DictReader(        input_file    )    error_writer = csv.writer(        error_file    )    error_writer.writerow([        "line",        "reason",        "data",    ])    for line_number, row in enumerate(        reader,        start=2,    ):        try:            price = int(row["price"])            if price < 0:                raise ValueError(                    "価格が負数です"                )        except (            KeyError,            ValueError,        ) as error:            error_writer.writerow([                line_number,                str(error),                repr(row),            ])            continue        # 正常データを処理する

個人情報や機密情報をエラーファイルへそのまま保存しないようにします。

11. pathlib・StringIO・WebデータでCSVを扱う

CSVはローカルファイルだけでなく、文字列、HTTP応答、メモリ上のデータとしても処理できます。

11.1 pathlibでファイルパスを扱う

Pathを使うと、OSに依存しにくい形でファイルパスを構築できます。

import csv from pathlib import Path path = (    Path("data")    / "products.csv" ) with path.open(    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(file)    for row in reader:        print(row)

Pathはファイルの存在確認、ディレクトリ作成、拡張子変更などにも利用できます。

11.2 StringIOでCSV文字列を読み込む

CSV形式の文字列はio.StringIOを使ってファイルのように扱えます。

import csv from io import StringIO csv_text = """id,name,price 1,Python入門,3000 2,Java入門,2800 """ file_like = StringIO(csv_text) reader = csv.DictReader(file_like) for row in reader:    print(row)

テストやHTTP応答本文の処理に便利です。

11.3 CSV文字列を生成する

StringIOcsv.writerで書き込むと、CSV文字列を生成できます。

import csv from io import StringIO output = StringIO(    newline="" ) writer = csv.writer(output) writer.writerow([    "id",    "name",    "price", ]) writer.writerow([    1,    "Python入門",    3000, ]) csv_text = output.getvalue() print(csv_text)

Web APIからCSVを返したり、メールへCSVを添付したりする処理で利用できます。

11.4 HTTPからCSVを取得する

標準ライブラリのurllib.requestでCSVを取得できます。

import csv from io import StringIO from urllib.request import urlopen url = (    "https://example.com/"    "products.csv" ) with urlopen(    url,    timeout=10, ) as response:    charset = (        response.headers        .get_content_charset()        or "utf-8"    )    csv_text = (        response        .read()        .decode(charset)    ) reader = csv.DictReader(    StringIO(csv_text) ) for row in reader:    print(row)

応答サイズ、HTTPステータス、文字コード、タイムアウトを確認して処理します。

11.5 テスト用CSVをメモリ上で作る

StringIOを使えば、実際のファイルを作成せずCSV処理をテストできます。

import csv from io import StringIO def read_prices(    csv_text: str, ) -> list[int]:    reader = csv.DictReader(        StringIO(csv_text)    )    return [        int(row["price"])        for row in reader    ] def test_read_prices() -> None:    csv_text = """id,price 1,3000 2,2800 """    assert read_prices(        csv_text    ) == [        3000,        2800,    ]

ファイルシステムへ依存しないため、テストを高速かつ安定して実行できます。

12. 大容量・圧縮CSVを処理する

数百万行以上のCSVでは、全件をメモリへ読み込まず、一行ずつ処理することが重要です。

12.1 一行ずつ処理する

csv.readerDictReaderはイテレーターとして利用できるため、一行ずつ読み込めます。

import csv from pathlib import Path path = Path("large.csv") total = 0 with path.open(    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(file)    for row in reader:        total += int(            row["amount"]        ) print(total)

この方法では、全レコードをリストとして保持しません。

12.2 必要な列だけを保存する

標準のcsvモジュールは各行の全列を解析しますが、処理後に必要な値だけを保持できます。

customer_totals: dict[    str,    int, ] = {} for row in reader:    customer_id = row["customer_id"]    amount = int(row["amount"])    customer_totals[customer_id] = (        customer_totals.get(            customer_id,            0,        )        + amount    )

元の全行を保存せず、集計結果だけをメモリへ保持します。

12.3 gzip圧縮CSVを読み込む

標準ライブラリのgzipを使って、.csv.gzを直接読み込めます。

import csv import gzip with gzip.open(    "products.csv.gz",    "rt",    encoding="utf-8",    newline="", ) as file:    reader = csv.DictReader(file)    for row in reader:        print(row)

圧縮したまま保存できるため、ディスク使用量や転送量を減らせます。

12.4 gzip圧縮CSVを書き込む

gzip.openをテキスト書き込みモードで開きます。

import csv import gzip with gzip.open(    "products.csv.gz",    "wt",    encoding="utf-8",    newline="", ) as file:    writer = csv.DictWriter(        file,        fieldnames=[            "id",            "name",            "price",        ],    )    writer.writeheader()    writer.writerow({        "id": 1,        "name": "Python入門",        "price": 3000,    })

受け取り側がgzip圧縮CSVへ対応しているかを確認します。

12.5 フィールドサイズ制限を確認する

非常に長い一つのセルがある場合、csv.field_size_limitで現在の最大フィールドサイズを確認・変更できます。

import csv current_limit = (    csv.field_size_limit() ) print(current_limit)

必要に応じて変更できます。

csv.field_size_limit(    10_000_000 )

信頼できないCSVへ無制限に大きな値を許可すると、メモリ消費が増えるため注意します。

13. pandasでCSVを読み書きする

表形式データの集計、欠損値処理、列型変換などを行う場合は、pandasのread_csvto_csvが便利です。

13.1 read_csvで読み込む

pandas.read_csvはCSVをDataFrameへ読み込みます。

import pandas as pd dataframe = pd.read_csv(    "products.csv",    encoding="utf-8", ) print(dataframe)

列名、データ型、欠損値などがpandasの形式として管理されます。

13.2 必要な列と型を指定する

usecolsで必要な列だけを読み込み、dtypeで列型を指定できます。

import pandas as pd dataframe = pd.read_csv(    "products.csv",    usecols=[        "id",        "name",        "price",    ],    dtype={        "id": "string",        "name": "string",        "price": "Int64",    }, )

usecolsを指定すると、不要な列を読み込まないため、解析時間とメモリ使用量を減らせる場合があります。dtypeでは列ごとにデータ型を指定できます。

13.3 欠損値の扱いを指定する

pandasは、空文字列、NaNN/ANULLなどの一般的な値を欠損値として解釈します。

独自の欠損値を追加できます。

dataframe = pd.read_csv(    "products.csv",    na_values=[        "未設定",        "-",    ], )

文字列のNULLをそのまま残したい場合は、設定を変更します。

dataframe = pd.read_csv(    "products.csv",    keep_default_na=False, )

13.4 to_csvで書き込む

DataFrame.to_csvでCSVへ出力できます。

dataframe.to_csv(    "products_output.csv",    index=False,    encoding="utf-8", )

index=Falseを付けない場合は、DataFrameのインデックスも列として出力されます。

Excel向けにBOM付きUTF-8を使う場合は次のようにします。

dataframe.to_csv(    "products_excel.csv",    index=False,    encoding="utf-8-sig", )

13.5 chunksizeで分割して読み込む

大容量CSVでは、chunksizeを指定して一定行数ずつ処理できます。read_csvは、ファイルをチャンクに分けて反復処理する機能を提供しています。

import pandas as pd total = 0 for chunk in pd.read_csv(    "large_sales.csv",    chunksize=100_000, ):    total += chunk["amount"].sum() print(total)

pandasはgzip、bz2、zip、xz、zstdなど複数の圧縮形式を、拡張子から推測して読み込むこともできます。

14. 実務で安全なCSV処理を書く

CSVは単純な形式に見えますが、外部入力、個人情報、表計算ソフト、同時更新などを考慮する必要があります。

14.1 CSV Formula Injectionへ注意する

利用者が入力した値をCSVへ出力し、Excelなどで開く場合、=+-@などで始まる値が数式として解釈される可能性があります。これはCSV InjectionまたはFormula Injectionと呼ばれます。

数式を一切許可しないCSVであれば、危険な値を拒否する方法があります。

DANGEROUS_PREFIXES = (    "=",    "+",    "-",    "@",    "\t",    "\r",    "\n", ) def reject_formula_cell(    value: str, ) -> str:    if value.startswith(        DANGEROUS_PREFIXES    ):        raise ValueError(            "表計算ソフトで数式として"            "解釈される可能性があります"        )    return value

値を加工して無害化する方法は表計算ソフトや再保存後の動作によって異なるため、すべての用途に共通する完全な対策はありません。人が閲覧する資料なら、CSVではなく安全に生成したXLSXを使う方法も検討します。

14.2 数字だけのコードを文字列として扱う

郵便番号、商品コード、電話番号、社員番号などは、数字だけでも計算対象ではありません。

postal_code 0012345

intへ変換すると先頭のゼロが失われます。

postal_code = row["postal_code"]

pandasでは文字列型を明示します。

dataframe = pd.read_csv(    "customers.csv",    dtype={        "postal_code": "string",        "phone_number": "string",    }, )

14.3 一時ファイルから安全に置き換える

書き込み途中でプログラムが停止すると、元のCSVが途中までの内容になる可能性があります。

import csv from pathlib import Path target = Path("products.csv") temporary = target.with_suffix(    ".csv.tmp" ) with temporary.open(    "w",    encoding="utf-8",    newline="", ) as file:    writer = csv.writer(file)    writer.writerows(rows) temporary.replace(target)

一時ファイルの作成場所、ファイル権限、同時更新についても設計します。

14.4 CSV仕様を検証する

受け取ったCSVに対し、次の項目を検証します。

検証項目確認内容
文字コード指定された文字コードか
ヘッダー必須列が存在するか
列数行ごとに一致するか
データ型整数・日付などへ変換可能か
必須値空文字でないか
最大長異常に長くないか
重複IDが重複していないか
件数許容件数以内か

構文上読み込めることと、業務データとして正しいことは別です。

14.5 CSV処理を関数やクラスへ分離する

ファイル入出力、型変換、検証、業務処理を一つのループへ詰め込むと、テストしにくくなります。

from dataclasses import dataclass from decimal import Decimal @dataclass(frozen=True) class Product:    product_id: str    name: str    price: Decimal def parse_product(    row: dict[str, str], ) -> Product:    product_id = row["id"].strip()    name = row["name"].strip()    if not product_id:        raise ValueError(            "idは必須です"        )    if not name:        raise ValueError(            "nameは必須です"        )    price = Decimal(        row["price"]    )    if price < 0:        raise ValueError(            "priceは0以上です"        )    return Product(        product_id=product_id,        name=name,        price=price,    )

CSV以外の入力形式へ変更する場合も、業務モデルと検証処理を再利用しやすくなります。

15. CSV関連の関数・形式の違い

最後に、混同されやすいCSV処理方法を個別に比較します。

15.1 readerとDictReaderの違い

csv.readerは各行をリスト、DictReaderは各行を辞書として返します。

列数が少なく、順番が固定されたデータにはreader、列名を使って安全に処理したい場合はDictReaderが適しています。

比較項目csv.readercsv.DictReader
一行の型list[str]dict[str, str]
値の取得インデックス列名
ヘッダー処理手動自動
列順変更への強さ低い比較的高い
処理速度単純わずかに処理が増える
主な用途小規模・固定形式業務データ

15.2 writerとDictWriterの違い

csv.writerはリストやタプル、DictWriterは辞書を行として書き込みます。

既に列順が決まった配列を持つ場合はwriter、データを項目名で管理している場合はDictWriterが適しています。

比較項目csv.writercsv.DictWriter
入力リスト・タプル辞書
列順入力順fieldnames
ヘッダーwriterowで記述writeheader
不足項目列数次第restval
余分な項目そのまま列になるエラー・無視を選択
主な用途単純な行出力項目名付きデータ

15.3 csvモジュールとpandasの違い

標準のcsvモジュールは、依存パッケージなしで一行ずつCSVを処理できます。pandasは、CSVを表として読み込み、集計、結合、欠損値処理などを行えます。

比較項目csvモジュールpandas
追加インストール不要必要
基本単位一行DataFrame
型推論基本なしあり
集計・結合自分で実装豊富
大容量処理一行ずつ処理chunksize
主な用途入出力・連携分析・加工

単純なCSV変換や取り込みにはcsv、複雑な表データ加工にはpandasが選びやすいでしょう。

15.4 CSVとJSONの違い

CSVは行と列からなる平面的なデータに適しています。JSONは辞書、リスト、入れ子構造を表現できます。

比較項目CSVJSON
構造行と列オブジェクト・配列
入れ子不向き対応
型情報ほぼなし数値・真偽値など
表計算ソフト開きやすいそのままでは表にならない
容量比較的小さいキー名の分だけ増える
主な用途表データ交換API・設定・構造化データ

注文と注文明細のような入れ子構造にはJSON、一覧表として交換するデータにはCSVが向いています。

15.5 CSVとExcelファイルの違い

CSVはテキスト形式で、一つの表を表現します。Excelの.xlsxは、複数シート、書式、数式、グラフ、画像などを保持できます。

比較項目CSVExcel
形式テキストZIPベースの文書形式
複数シート不可可能
セル書式不可可能
数式文字列として存在可能正式に対応
画像・グラフ不可可能
Python標準機能csvで対応外部ライブラリが必要
主な用途システム間交換人向け帳票・分析

データ連携にはCSV、書式付きの人向け資料にはopenpyxlなどを使ったExcelファイルが適しています。

おわりに

PythonでCSVを読み込む基本はcsv.readerまたはcsv.DictReader、書き込む基本はcsv.writerまたはcsv.DictWriterです。ファイルを開くときは、文字コードとnewline=""を明示することが重要です。

単純な行データにはreaderwriter、列名を使って処理したい業務データにはDictReaderDictWriterが向いています。集計、欠損値処理、大量の列変換が必要な場合は、pandasのread_csvto_csvを利用できます。

CSVには型情報がないため、整数、日付、真偽値などは読み込み後に検証・変換します。また、Excelで開くCSVでは文字コードやFormula Injectionにも注意し、受け取り側の環境を含めたCSV仕様を明確にすることが、安定したデータ連携につながります。

LINE Chat