4日目のゴール
4日目のテーマは
「CSVを“きれいなデータだけ”前提で扱うのをやめて、エラー行や欠損値があっても、落ちずに処理できるようになること」 です。
1〜3日目で、あなたはすでに
CSVを読む・書く
DictReader / DictWriter で列名ベースで扱う
集計・ソート・フィルタで“実務っぽい処理”を書く
ところまで来ています。
4日目ではここに、
ファイル読み込み時のエラー処理
壊れた行・おかしな値を「スキップする」発想
「スキップした件数」をメッセージとして出す
という、「現場で本当に必要になる CSV 処理の現実対応」を足していきます。
まずは前提:実務のCSVはきれいじゃない
「全部ちゃんと入っている」は幻想だと思っておく
教科書に出てくるCSVは、だいたいこうです。
id,name,age,department,salary
1,Taro,25,Sales,300000
2,Hanako,30,HR,320000
3,Ken,22,Sales,250000
でも、実務で渡されるCSVは、こんな感じになりがちです。
id,name,age,department,salary
1,Taro,25,Sales,300000
2,Hanako,,HR,320000
3,Ken,abc,Sales,250000
4,Naomi,35,,400000
5,Shin,28,Engineering,
年齢が空欄
年齢が「abc」みたいな文字列
部署が空欄
給与が空欄
こういう「おかしな行」が混ざっているのが普通です。
今日やるのは、
「こういう行があっても、アプリを落とさずに処理を続ける」
という考え方です。
数値変換でエラーが出るパターンを体験する
まずはあえて「落ちるコード」を書いてみる
さっきのような「汚れた employees_dirty.csv」を読み込んで、
年齢と給与の平均を出すコードを書いてみます。
import csv
def calc_average_from_dirty():
with open("employees_dirty.csv", "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
total_age = 0
total_salary = 0
count = 0
for row in reader:
age = int(row["age"])
salary = int(row["salary"])
total_age += age
total_salary += salary
count += 1
print("件数:", count)
print("平均年齢:", total_age / count)
print("平均給与:", total_salary / count)
Pythonこのまま実行すると、int(row["age"]) のところで ValueError が出ます。
理由はシンプルで、""(空文字)や "abc" は int() に変換できないからです。
ここで大事なのは、
「現実のCSVは、int() できない値が普通に混ざる」
という感覚を持つことです。
try / except で「変換できない行」をスキップする
「落とさない」ことを最優先にする
次に、「変換できない行はスキップする」ように書き換えます。
import csv
def calc_average_from_dirty_safe():
with open("employees_dirty.csv", "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
total_age = 0
total_salary = 0
count = 0
skipped_rows = 0
for row in reader:
try:
age = int(row["age"])
salary = int(row["salary"])
except ValueError:
skipped_rows += 1
print("変換できない行をスキップしました:", row)
continue
total_age += age
total_salary += salary
count += 1
if count == 0:
print("有効なデータがありませんでした。")
return
print("有効な件数:", count)
print("スキップした行数:", skipped_rows)
print("平均年齢:", total_age / count)
print("平均給与:", total_salary / count)
Pythonここで深掘りしたいポイントは四つです。
try: ... except ValueError: で「数値変換に失敗した行だけを捕まえている」こと。
失敗した行は continue で「この1行だけ飛ばして、次の行に進んでいる」こと。
スキップした行数を skipped_rows で数えて、最後に報告していること。
有効な件数が 0 のときは、平均を出さずにメッセージを出していること。
つまり、
「全部きれいに処理する」のではなく
「処理できる行だけを使い、できなかった行は数えておく」
という現実的なスタンスに変わっています。
必須項目が空欄の行をスキップする
「値がない」パターンもちゃんと見る
次は、「空欄」のパターンです。
例えば、部署や給与が空欄の行は
集計対象から外したい、というケース。
import csv
def calc_average_salary_with_required_fields():
with open("employees_dirty.csv", "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
total_salary = 0
count = 0
skipped_rows = 0
for row in reader:
if row["salary"] == "" or row["department"] == "":
skipped_rows += 1
print("必須項目が空のためスキップ:", row)
continue
try:
salary = int(row["salary"])
except ValueError:
skipped_rows += 1
print("給与が数値でないためスキップ:", row)
continue
total_salary += salary
count += 1
if count == 0:
print("有効なデータがありませんでした。")
return
print("有効な件数:", count)
print("スキップした行数:", skipped_rows)
print("平均給与:", total_salary / count)
Pythonここでの重要ポイントは、
「空欄チェック」と「数値変換エラー」を分けて扱っていること。
空欄のときは if で弾き、変換エラーは try / except で弾いていること。
どちらも「スキップした理由」をメッセージとして出していること。
この「スキップの理由を分けておく」習慣は、
後でデータ提供元にフィードバックするときにも役立ちます。
「エラー行だけを別CSVに書き出す」ミニアプリ
スキップした行を“捨てずに残す”という発想
実務では、
「おかしな行をスキップするだけでなく、別ファイルに残しておきたい」
というニーズもよくあります。
例えば、
employees_dirty.csv を読み込む
年齢と給与が両方とも数値の行だけを「clean_employees.csv」に書き出す
それ以外の行は「error_employees.csv」に書き出す
という処理を考えてみます。
import csv
def split_clean_and_error_rows(input_file, clean_file, error_file):
with open(input_file, "r", encoding="utf-8", newline="") as f_in:
reader = csv.DictReader(f_in)
fieldnames = reader.fieldnames
clean_rows = []
error_rows = []
for row in reader:
try:
age = int(row["age"])
salary = int(row["salary"])
except (ValueError, TypeError):
error_rows.append(row)
continue
row["age"] = age
row["salary"] = salary
clean_rows.append(row)
with open(clean_file, "w", encoding="utf-8", newline="") as f_clean:
writer_clean = csv.DictWriter(f_clean, fieldnames=fieldnames)
writer_clean.writeheader()
for row in clean_rows:
row_to_write = {
"id": row["id"],
"name": row["name"],
"age": str(row["age"]),
"department": row["department"],
"salary": str(row["salary"]),
}
writer_clean.writerow(row_to_write)
with open(error_file, "w", encoding="utf-8", newline="") as f_error:
writer_error = csv.DictWriter(f_error, fieldnames=fieldnames)
writer_error.writeheader()
for row in error_rows:
writer_error.writerow(row)
print(f"正常行: {len(clean_rows)} 件 → {clean_file}")
print(f"エラー行: {len(error_rows)} 件 → {error_file}")
Pythonここで深掘りしたいポイントは、かなり実務的です。
reader.fieldnames で「元のCSVのヘッダー(列名)」をそのまま使っていること。
正常行とエラー行を、それぞれ別のリストにためていること。
正常行は数値に変換してから、書き出すときに文字列に戻していること。
エラー行は「そのまま」別CSVに書き出していること。
これで、
「きれいなデータだけを使って集計しつつ、
おかしな行は後で確認できるように残しておく」
という、かなり“現場っぽい”動きになります。
ファイル自体のエラーもちゃんと扱う
ファイルがない・壊れているときに落とさない
ここまで「行レベル」のエラーを見てきましたが、
「ファイルレベル」のエラーもあります。
ファイルが存在しない
権限がなくて開けない
中身がJSONやExcelで、CSVじゃない
こういうときに、
アプリがドカンと落ちるのは避けたいですよね。
import csv
def safe_read_employees(filename):
try:
with open(filename, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
rows = list(reader)
except FileNotFoundError:
print(f"ファイルが見つかりません: {filename}")
return []
except PermissionError:
print(f"ファイルにアクセスできません(権限エラー): {filename}")
return []
except OSError as e:
print("ファイルの読み込み中にエラーが発生しました。")
print("詳細:", e)
return []
print(f"{filename} から {len(rows)} 行読み込みました。")
return rows
Pythonここでの重要ポイントは、
try: ... except ... で「ファイルを開くところ」全体を囲んでいること。
エラーの種類ごとにメッセージを変えていること。
エラー時には空のリスト [] を返して、呼び出し側で「データなし」として扱えるようにしていること。
これで、呼び出し側はこう書けます。
rows = safe_read_employees("employees_dirty.csv")
if not rows:
print("処理を中止します。")
return
Python「ファイルがないなら、ないなりに止まる」
という、落ち着いた動きになります。
4日目のまとめ 今日つかんでほしい感覚
今日の本質は、これです。
実務のCSVは「きれいじゃない」のが普通だと最初から思っておく。
数値変換は必ず try / except で囲み、変換できない行はスキップする。
必須項目が空欄の行は、if で弾いて「スキップした理由」をメッセージに残す。
正常行とエラー行を分けて、エラー行は別CSVに書き出すと“現場で使える”形になる。
ファイル自体のエラー(存在しない・権限なし)も try / except で受け止めて、アプリを落とさない。
1〜3日目で「きれいなCSVを自由に料理できる力」がつき、
4日目で「汚れたCSVでも落ちずに処理できる力」が乗りました。
この二つがそろったとき、
あなたのCSV処理は、もう立派な“実務レベル”です。

