124
123

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PythonでExcelのCSVファイルを扱う

Last updated at Posted at 2016-02-08

Pythonのcsvパッケージは大変便利です。面倒なエスケープ処理をちゃんと行ってくれます。とりわけ、Excelファイルで送られてきたファイルを処理するのに重宝します。なんといっても、dialect='excel'でExcel CSVをちゃんと読めます。

ところが、日本語が絡むと問題は途端に厄介になります。問題の本質は何か。まず、ExcelのTSVの扱いに関して、以下のことが知られています。

  • Excelのtsvエクスポートはutf-16(BOM付き)である
  • Excelが直接読めるのはBOM付きutf-16のtsvである(カンマ区切りはウィザードが必要)
  • csv(カンマ区切り)出力や読み込みも可能だがおすすめできない(後述)

このことからわかるのは、BOM付きutf-16ファイルを扱うのが最善ということです。これを扱う一番の方法は、ioパッケージを使うことです。

with io.open(path, encoding='utf-16') as f:
  ...

さらっと書きましたが、ioパッケージを使うのが実は肝です。utf-16はそこそこに扱いにくいフォーマットです。まず、BOMがいます。さらにこの状態でcr+lfされると、バイト単位では間に\0が挟まるということです。ですので、普通にopen()を使うと謎の挙動をします。

さてここで問題が起こります。python2系を使っていると、csvパッケージはunicodeをサポートしていません。従って、上の方法で開くと読めません。ここで、日和ってopenを使うと、上記の理由で読むのに苦労します。以前は私はshift-jisのcsvでエクスポートしていたんですが、Excelは内部的にUnicodeのようで、濁点が別文字になった正規化されてない状態のことがあり、このときshift-jis出力したら濁点が全部消失しました。なのでこの方法もおすすめしません。

実はUnicodeをcsvで読む方法は、マニュアルの最後に書いてあります。正解は、unicodeのストリームを一旦utf-8にエンコードして、この状態でcsvパッケージで読ませて、スプリットしたら再度utf-8をデコードすればよいのです。書き込む時も同じで、いったんunicodeをutf-8にしてStringIOに書き込んで、ファイルに吐き出す直前で再度unicodeに変換すればいいのです。圧倒的に2度手間ですが、長年Excelと格闘して最終的に一番安定した出力が得られませいた。ちなみに、Python3ではcsvパッケージがstr(2で言うところのunicode)をサポートしたので、もはや普通にioで開いて処理すればいいです。

流石にこれらの処理を毎回書いていると日が暮れるので、ライブラリにしました。

上記の問題を全て回避しています、たぶん。

やってはいけないこと

以上が全てですが、おさらい的にやってはいけないことと理由を羅列してみましょう。

要素ごとにutf-16でencode

.encode('utf-16')はBOM付きutf-16文字列を生成します。ここが、その他のエンコードとの決定的な違いです。

>>> u'a'.encode('utf-16')
'\xff\xfea\x00'

[x.encode('utf-16') for x in row]の様なコードを書くと、アチラコチラにBOMが入った謎の文字列が生成されしまいます。このアプローチはダメです。ファイルの先頭にだけBOMが入るようにしないといけません。
これを回避する一番よい方法は、原則的にunicodeで扱って、ファイル出力の部分で制御することです。これをやってくれるのがioパッケージであり、通常のopenを使って自前でエンコードするべきではありません。
別の回避策として、一旦StringIOにutf-8で出力しておいて、ファイルに書き込む前に全体をunicodeに変換してから、再度utf-16に変換し直すという方法もあります。が、ioを使ったほうがすっきりします。

openで開いてパースしてからデコード

utf-16ということは、パースしたい改行文字やタブ文字も2バイトで書かれているということです。1バイト単位で処理するPython2のcsvパッケージに処理させると、不要な\0文字が残ってしまいます。このアプローチが使えるのは、Ascii文字がそのまま残っているutf-8やshift-jisなどだけです。

shift-jisのcsvでエクスポートしてから読み込み

エクセルは内部的にはUnicodeでデータを持っているようです。最悪なのは、濁点が別文字として保存されていることがあり、この状態でshift-jisでエクスポートすると、濁点が全部消えます。正規化してくれればいいのに。そのため、excelからのエクスポートはutf-16テキスト一択になります。扱いづらいですがしかたありません。

124
123
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
124
123

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?