CSVを読むだけで3時間ハマりました。
信じたくないですが、本当です。原因は文字コード、BOM、列名の空白、そして私の「まあ読めるだろう」という油断でした。夜中にコーヒーを飲みながら UnicodeDecodeError を見つめると、かなり静かな気持ちになります。
CSVは簡単そうに見えて、現場ではだいたい何かあります。
まずは標準ライブラリで読む
小さなCSVなら、最初は標準ライブラリで十分です。
import csv
from pathlib import Path
def read_csv(path: str) -> list[dict[str, str]]:
with Path(path).open(encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
return [dict(row) for row in reader]
if __name__ == "__main__":
rows = read_csv("sample.csv")
# ハマりポイント: いきなり全件表示せず、件数と先頭1件だけ見る
print(f"rows={len(rows)}")
print(rows[0] if rows else "empty")
utf-8-sig にしているのは、BOM付きUTF-8のCSVを読むことがあるからです。これを知らずに、先頭の列名に見えない文字が混ざって30分迷いました。
pandasで読む場合
集計や列操作をするならpandasが便利です。
import pandas as pd
def load_table(path: str) -> pd.DataFrame:
df = pd.read_csv(path, encoding="utf-8-sig")
# ハマりポイント: 列名の前後空白を先に消す
df.columns = [str(col).strip() for col in df.columns]
return df
if __name__ == "__main__":
df = load_table("sample.csv")
print(df.shape)
print(df.head(3))
ここでも最初に見るのは、全データではなく形です。df.shape と df.head(3) だけで、行数、列数、文字化けの有無がかなり分かります。
3時間の内訳
最初の40分は、文字コードでした。UTF-8だと思ったら違う。違うと思って別の文字コードを試す。こうなると、だんだん勘で作業し始めます。
次の30分は、BOMでした。列名が "name" に見えるのに、実際は先頭に見えない文字が付いていました。これはかなり意地悪です。
次の50分は、列名の空白でした。"price " と "price" は別物です。人間の目にはほぼ同じでも、Pythonは許してくれません。
残りの60分は、私の確認不足です。最初に件数と列名を出していれば、もっと早く終わっていました。
最初に必ず出す診断コード
今は、CSVを受け取ったらまずこれを実行します。
import pandas as pd
def inspect_csv(path: str, encoding: str = "utf-8-sig") -> None:
df = pd.read_csv(path, encoding=encoding)
df.columns = [str(col).strip() for col in df.columns]
print(f"rows={len(df)}")
print(f"columns={list(df.columns)}")
print(df.head(3).to_string(index=False))
print(df.dtypes)
if __name__ == "__main__":
inspect_csv("sample.csv")
この30行弱で、かなりの事故を初期発見できます。特に列名と先頭3行は必ず見ます。
文字コードを当てに行く前に
文字化けした時、すぐに文字コードを総当たりしたくなります。
でも、その前に入手元を確認したほうが速いことがあります。Excelから出したのか、Webシステムから出したのか、別ツールが生成したのか。出どころで候補はかなり絞れます。
それでも分からない場合だけ、候補を試します。
from pathlib import Path
def try_encodings(path: str) -> None:
data = Path(path).read_bytes()
for encoding in ["utf-8-sig", "utf-8", "cp932"]:
try:
text = data.decode(encoding)
print(f"OK: {encoding}")
print(text.splitlines()[0])
except UnicodeDecodeError:
print(f"NG: {encoding}")
ハマりポイントは、成功したから正解とは限らないことです。読めても文字が崩れている場合があります。最後は列名と先頭行を目で確認します。
CSVで決めた3つのルール
1つ目、最初に全件処理しない。件数、列名、先頭3行を見る。
2つ目、列名は必ずstripする。見えない空白で条件分岐を壊さないためです。
3つ目、文字コードは推測だけで進めない。入手元と実データの見え方で確認します。
この3つだけで、CSV作業の失敗はかなり減りました。
保存する時も油断しない
CSVは読む時だけでなく、書き出す時にも事故ります。
私は一度、読み込みは成功したのに、保存後のファイルをExcelで開いたら文字が崩れました。原因は保存時の文字コードでした。読むことに集中しすぎて、出口を見ていなかったのです。
Excelで開く可能性があるなら、BOM付きUTF-8で保存することがあります。
import pandas as pd
def save_for_excel(df: pd.DataFrame, path: str) -> None:
# ハマりポイント: Excelで開く前提ならutf-8-sigが無難な場面がある
df.to_csv(path, index=False, encoding="utf-8-sig")
逆に、システム連携でBOMが邪魔になる場合もあります。つまり、正解は1つではありません。誰が、どのツールで、何のために開くのかで変わります。
私はCSVを扱う時、入力の文字コード、列名、行数、出力の文字コードの4つをメモするようにしました。たった4行ですが、翌月の自分をかなり助けます。
まとめ
PythonでCSVを読む作業は、簡単なようで地味に罠が多いです。
文字コード、BOM、列名、空白、行数。最初の5分で診断すれば、3時間の迷子はかなり防げます。
CSV処理で一番大事なのは、読み込むコードではなく、読み込めたと思い込まない確認です。

コメント