Python WebAcademy Blog

Pythonのcsvモジュールとは?表データの読み書きと文字化けの防ぎ方を初心者向けに解説

|

Pythonで表データを扱うときに使う標準ライブラリのcsvモジュールを、IT初心者向けにやさしく解説します。readerとDictReaderの読み分け、書き出しの手順、初心者がほぼ必ずぶつかるnewlineの空行問題とExcelでの文字化け、タブ区切りへの対応、そしてpandasとの使い分けまで、動かせるコードを並べながら順番に紹介します。

Pythonの基本文法に慣れてきた頃、手元の表データをコードで扱ってみたくなりませんか。

売上の一覧、アンケートの結果、会員名簿。仕事で扱うデータの多くは、行と列でできた表の形をしています。

その表をプログラムから読み書きするとき、いちばんよく登場するのがcsvという形式です。Pythonには、これを扱うためのcsvモジュールが最初から入っています。

この記事では、csvモジュールの基本を順番に追いかけます。あわせて、初心者がほぼ必ずぶつかる文字化けと空行の問題も片づけていきましょう。

csvファイルは、実はただのテキストファイル

csvはComma-Separated Valuesの略で、値をカンマで区切って並べただけの形式です。Excelのアイコンで表示されることが多いので誤解されがちですが、中身はメモ帳でも開ける普通のテキストにすぎません。

たとえば、次のような3行のテキストがcsvファイルの正体です。

name,age,city
佐藤,28,東京
鈴木,34,大阪

1行目が見出しで、2行目からがデータ。たったこれだけの決まりごとで、表を表現しています。

構造がシンプルなぶん、どんなツールでも読み書きできます。だからこそ、システム同士がデータを受け渡すときの共通語のように使われ続けているわけです。

この形式にはRFC 4180という仕様書もあります。カンマや改行を含む値はダブルクォートで囲む、といった取り決めがそこに書かれています。

なぜsplitではなくcsvモジュールを使うのか

カンマで区切るだけなら、文字列をsplitすれば済みそうに見えます。私も駆け出しの頃は本気でそう考えていました。

ところが、値そのものにカンマが入っていると一気に破綻します。住所や商品名では、まったく珍しくないケースです。

name,memo
佐藤,"東京都,港区"

この2行目をsplitで割ると要素が3つに分かれ、列がずれます。ダブルクォートで囲まれた中身は区切らない、という判断をしてくれるのがcsvモジュールの価値です

私は10年ほどエンジニアとして開発に関わってきましたが、この列ずれが原因の障害は何度も見てきました。自前のsplitで書かれた読み込み処理は、たいてい数か月後に事故を起こします。

まずは1行ずつ読んでみる

読み込みの基本はcsv.readerです。ファイルを開いて渡すと、1行ずつリストとして取り出せます。

先ほどの3行のファイルをmembers.csvという名前で保存して、次のコードを動かしてみましょう。

import csv

with open("members.csv", encoding="utf-8", newline="") as f:
    reader = csv.reader(f)
    header = next(reader)
    print(header)
    for row in reader:
        print(row)

実行すると、次のように表示されます。

['name', 'age', 'city']
['佐藤', '28', '東京']
['鈴木', '34', '大阪']

next関数で見出しの1行だけを先に取り出し、残りをforで回しているのがポイントです。こうしておくと、見出しがデータに紛れ込みません。

openにwith文を使っているのは、処理が終わったあとにファイルを確実に閉じるためです。この書き方の仕組みが気になる方は、こちらもあわせてどうぞ。【関連記事】pythonのコンテキストマネージャーって何?詳しく解説します!

見出し付きのデータはDictReaderが読みやすい

readerで読むと、各行はリストになります。つまり、名前を取り出すにはrow[0]と書くことになります。

列が5つ、10つと増えてくると、この番号がだんだん苦痛になってきます。そこで登場するのがcsv.DictReaderです。

import csv

with open("members.csv", encoding="utf-8", newline="") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["city"])

DictReaderは1行目を見出しとして自動で読み取り、各行を辞書にして返してくれます。row[0]ではなくrow["name"]と書けるので、あとから読み返したときの意味がはっきりします。

列の順番が変わっても壊れないのも利点です。実務でもらうcsvは、途中で列が1つ増えることが本当によくあります。

数字は文字列のまま返ってくる

ここで初心者がつまずきやすい点をひとつ。csvモジュールは、読み取った値を勝手に数値へ変換しません。

公式ドキュメントにも、行はリストとして返され、自動的な型変換は行われないと明記されています。年齢の28は、数値の28ではなく文字列の28として届くわけです。

そのため、合計を出したいときは自分でintへ変換します。

import csv

total = 0
with open("members.csv", encoding="utf-8", newline="") as f:
    for row in csv.DictReader(f):
        total += int(row["age"])

print(total)

これを忘れると、足し算のつもりが文字列の連結になったり、そもそもエラーで止まったりします。csvから読んだ値はすべて文字列、と覚えておくと安全です。

csvファイルへ書き出す

読めるようになったら、次は書き出しです。writerowsを使えば、複数行をまとめて出力できます。

辞書のリストをそのまま書きたい場合は、DictWriterが便利です。

import csv

rows = [
    {"name": "田中", "age": 41, "city": "名古屋"},
    {"name": "高橋", "age": 25, "city": "福岡"},
]

with open("output.csv", "w", encoding="utf-8-sig", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "age", "city"])
    writer.writeheader()
    writer.writerows(rows)

fieldnamesで列の順番を決め、writeheaderで見出し行を出します。あとはwriterowsに渡すだけです。

出力先のパスを組み立てるときは、文字列の連結よりpathlibのほうが安全に書けます。【関連記事】Pythonのpathlibとは?ファイルパス操作を初心者向けに解説

newline="" を忘れるとどうなるか

上のコードで、openにnewline=""を渡しているのに気づいたでしょうか。これは、おまじないではなく明確な理由があります。

公式ドキュメントは、ファイルオブジェクトを渡すときはnewline=""を指定して開くべきだと述べています。指定しないと、クォートで囲まれた中の改行が正しく解釈されず、さらにWindowsのように改行が\r\nの環境では余分な\rが足されてしまうためです。

その結果どうなるか。書き出したファイルを開くと、1行おきに空行が入った見た目になります。

読むときも書くときも、csvを扱うopenにはnewline=""を付ける。これを習慣にしておくだけで、原因のわかりにくい不具合をひとつ確実に避けられます。

Excelで開くと文字化けするときの直し方

もうひとつの定番のつまずきが文字化けです。Pythonで書き出したcsvをExcelで開いたら、日本語が意味不明な記号の羅列になっていた。よくある光景です。

原因は、ファイル側の文字コードと、Excelが想定する文字コードの食い違いにあります。ExcelはBOMという3バイトの目印がないと、そのファイルをUTF-8だと判断してくれないことがあります。

Pythonでは、encodingにutf-8-sigを指定するとこのBOMを自動で付けてくれます。先ほどの書き出しコードでutf-8-sigを使っていたのは、そのためです。

逆に、古い業務システムからもらったファイルはcp932で保存されていることがあります。用途ごとの目安を表にまとめました。

encodingの指定 どんなときに使うか
utf-8 Pythonや他のシステムとやり取りする、いちばん標準的な指定
utf-8-sig 書き出したファイルを誰かにExcelで開いてもらうとき
cp932 Windowsの古いシステムからもらったファイルを読むとき

読み込みでUnicodeDecodeErrorが出たときは、まずcp932を試してみてください。私の経験では、日本の業務データに限れば、これで8割方は解決します。

タブ区切りや別の区切り文字にも対応できる

カンマではなくタブで区切られたtsvというファイルを渡されることもあります。この場合も、csvモジュールがそのまま使えます。

delimiterという引数に区切り文字を指定するだけです。

import csv

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

セミコロン区切りのファイルも、同じ要領で読めます。区切り文字が違うだけで別のライブラリを探す必要はありません。

なお、データの受け渡しにはjson形式もよく使われます。階層のあるデータならこちらのほうが向いています。【関連記事】PythonでJSONデータの扱いをマスター!API連携に必須のjsonモジュールの使い方

pandasとどう使い分けるか

表データと聞くと、pandasを思い浮かべた方もいるかもしれません。どちらを使えばいいのか、迷うところです。

大まかな違いを表にしました。

観点 csvモジュール pandas
インストール 不要(標準ライブラリ) 必要
得意なこと 1行ずつの読み書き、加工しながらの出力 集計、並べ替え、欠損値の処理
メモリの使い方 1行分ずつ処理できる 表全体を読み込む
学習コスト 低い やや高い

集計や分析をしたいならpandasが圧倒的に速く書けます。一方、数百万行のログを1行ずつ処理したい場面では、メモリに全部載せないcsvモジュールが有利です。

私自身、日々のちょっとした変換作業はいまだにcsvモジュールで済ませています。追加のインストールが要らないので、他の人の環境でもそのまま動くからです。

pandasのほうが気になる方は、こちらの入門記事から始めてみてください。【関連記事】pandas入門 データ処理をやってみよう

つまずきポイントをまとめて確認する

最後に、この記事で扱った落とし穴を一覧にしておきます。困ったときの確認表として使ってください。

症状 原因 対処
1行おきに空行が入る newlineを指定していない openにnewline=""を渡す
Excelで日本語が崩れる BOMがない 書き出しでutf-8-sigを使う
UnicodeDecodeErrorが出る 文字コードが違う 読み込みでcp932を試す
数値の計算がおかしい 値が文字列のまま intやfloatへ変換する
列がずれる splitで自作している csv.readerに任せる

どれも一度ぶつかれば忘れないものばかりです。逆に言えば、知らないと必ず一度は時間を溶かします。

csvが読み書きできるようになると、日々の集計や転記をPythonに任せられるようになります。事務作業の自動化がどれだけ効くのかは、こちらでも紹介しています。【関連記事】非エンジニアがPythonを学ぶべき「コスパ最強」の理由。事務作業を秒で終わらせるには?

まずは手元にある小さなcsvを1つ、readerで読んで表示するところから始めてみましょう。動いた瞬間から、できることが一気に広がります。

ここまでお読みいただきありがとうございました。

参考情報

次のアクション

記事で学んだ内容を実際に動かしてみよう

Python WebAcademyでは、ブラウザ上でコードを書きながら基礎から実践まで体系的に学べます。

Python WebAcademyの学習画面

あわせて読む

関連記事

ブログ一覧へ

Python学習ロードマップ

まずはこの3講座から

記事で気になったテーマを、順番に手を動かしながら学べます。

ロードマップを見る