概要
PythonでCSVを読むコードを実装していたら以下のエラーになりました。
本記事では原因と解決方法を記載します。
_csv.Error: iterator should return strings, not bytes (did you open the file in text mode?)
原因
発生する原因は、csvモジュールのreaderオブジェクトに対して、バイト列を返してしまっているため。
文字列を返す必要があります。以下、どちらでも上述のエラーになります。
csvfile = request.FILES['file']
reader = csv.DictReader(csvfile)
for row in reader:
csvfile = request.FILES['file']
decoded_file = csvfile.read() # バイト列として読み込んでいる
reader = csv.reader(decoded_file)
for row in reader:
ちなみに、open()関数で'rb'というモードを指定している場合もファイルがバイナリモードで開かれているので、同じくエラーが発生します。csv.readerは文字列を期待しているからですね。
解決方法
エラーメッセージの通り、ファイルがテキストモードで開かれていないのが原因なので、csv.readerに渡す前にテキストモードで開けば解決します。以下のように変更するとエラーがなくなりました。
csvfile = request.FILES['file']
decoded_file = csvfile.read().decode('utf-8-sig').splitlines()
reader = csv.reader(decoded_file)
for row in reader:
上記では、.decode('utf-8-sig')を使用してバイト列をUTF-8エンコーディングでデコードし、テキストに変換します。
decode('utf-8-sig')については、BOM除去という点で一つ詰まった点があります。
BOM除去については以下の記事で記載していますので参考ください。
【Python】CSVを読み込むと文字列の中に\ufeffが入ってしまう場合の原因と解決方法
補足:request.FILES['file']とは
Djangoでは、HTTPリクエストに添付されたファイルを処理するために、request.FILESという属性を提供しています。
request.FILESは、ユーザーがフォームからファイルをアップロードした場合や、APIリクエストの一部としてファイルを送信した場合など、リクエストに含まれるファイルデータを取り扱うための特別なデータ構造です。
'file'部分は、アップロードされたファイルのフィールド名を指定します。
例えば、hogehoge.csvをアップロードした場合、request.FILES['file']をデバッグすればhogehoge.csvと出力されます。また、データの型は<class 'django.core.files.uploadedfile.InMemoryUploadedFile'>というInMemoryUploadedFileクラスになります。ファイルのデータをメモリ内に一時的に保存し、後続の処理で利用することができます。
詳細はDjangoドキュメントからどうぞ。
ファイルのアップロード — Django 4.0.6 ドキュメント