PythonでCSVを読み書きする方法|reader・writer・pandasを徹底解説
CSVは、表計算ソフト、データベース、業務システムなどの間で、表形式データを交換するためによく使われるテキスト形式です。一般的には項目をカンマで区切りますが、タブやセミコロンを区切り文字にする形式もあり、引用符や改行の扱いにもシステムごとの差があります。Pythonの公式ドキュメントも、CSVには提供元ごとの細かな形式差が存在すると説明しています。
Pythonでは、標準ライブラリのcsvモジュールを使ってCSVを読み書きできます。行をリストとして扱うreaderとwriterに加え、列名をキーとする辞書として扱えるDictReaderとDictWriterが用意されています。
2026年7月時点の最新安定版はPython 3.14.6です。本記事の基本コードはPython 3.10以降を想定していますが、csv.readerやcsv.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は、通常、各セルを文字列として返します。数値のように見える3000もstrです。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公式ドキュメントでも、システム既定以外の文字コードを使用する場合は、openのencoding引数を指定するよう説明されています。
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_NOTNULL | None以外を引用 |
QUOTE_STRINGS | 文字列を引用 |
QUOTE_NOTNULLとQUOTE_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.strptimeやdate.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には標準的な真偽値表現がありません。true、false、1、0、yes、noなど、受け取る値を明確にします。
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文字列を生成する
StringIOへcsv.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.readerとDictReaderはイテレーターとして利用できるため、一行ずつ読み込めます。
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_csvとto_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は、空文字列、NaN、N/A、NULLなどの一般的な値を欠損値として解釈します。
独自の欠損値を追加できます。
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.reader | csv.DictReader |
|---|---|---|
| 一行の型 | list[str] | dict[str, str] |
| 値の取得 | インデックス | 列名 |
| ヘッダー処理 | 手動 | 自動 |
| 列順変更への強さ | 低い | 比較的高い |
| 処理速度 | 単純 | わずかに処理が増える |
| 主な用途 | 小規模・固定形式 | 業務データ |
15.2 writerとDictWriterの違い
csv.writerはリストやタプル、DictWriterは辞書を行として書き込みます。
既に列順が決まった配列を持つ場合はwriter、データを項目名で管理している場合はDictWriterが適しています。
| 比較項目 | csv.writer | csv.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は辞書、リスト、入れ子構造を表現できます。
| 比較項目 | CSV | JSON |
|---|---|---|
| 構造 | 行と列 | オブジェクト・配列 |
| 入れ子 | 不向き | 対応 |
| 型情報 | ほぼなし | 数値・真偽値など |
| 表計算ソフト | 開きやすい | そのままでは表にならない |
| 容量 | 比較的小さい | キー名の分だけ増える |
| 主な用途 | 表データ交換 | API・設定・構造化データ |
注文と注文明細のような入れ子構造にはJSON、一覧表として交換するデータにはCSVが向いています。
15.5 CSVとExcelファイルの違い
CSVはテキスト形式で、一つの表を表現します。Excelの.xlsxは、複数シート、書式、数式、グラフ、画像などを保持できます。
| 比較項目 | CSV | Excel |
|---|---|---|
| 形式 | テキスト | ZIPベースの文書形式 |
| 複数シート | 不可 | 可能 |
| セル書式 | 不可 | 可能 |
| 数式 | 文字列として存在可能 | 正式に対応 |
| 画像・グラフ | 不可 | 可能 |
| Python標準機能 | csvで対応 | 外部ライブラリが必要 |
| 主な用途 | システム間交換 | 人向け帳票・分析 |
データ連携にはCSV、書式付きの人向け資料にはopenpyxlなどを使ったExcelファイルが適しています。
おわりに
PythonでCSVを読み込む基本はcsv.readerまたはcsv.DictReader、書き込む基本はcsv.writerまたはcsv.DictWriterです。ファイルを開くときは、文字コードとnewline=""を明示することが重要です。
単純な行データにはreaderとwriter、列名を使って処理したい業務データにはDictReaderとDictWriterが向いています。集計、欠損値処理、大量の列変換が必要な場合は、pandasのread_csvとto_csvを利用できます。
CSVには型情報がないため、整数、日付、真偽値などは読み込み後に検証・変換します。また、Excelで開くCSVでは文字コードやFormula Injectionにも注意し、受け取り側の環境を含めたCSV仕様を明確にすることが、安定したデータ連携につながります。
EN
JP
KR