pandasのto_csv関数のパラメータにはファイルパスだけでなくbufferを指定することができます。
それを使用して、読み込みしたCSVファイルの文字コードを変換してS3に出力する処理を実装したのですが、何故か文字コードが想定通り変換されなかったので、備忘がてら解決した内容を書いておきます。
目次
実施作業
準備
本事象が発生したのはLambda関数でしたが、本記事ではPC上にPythonの仮想環境を準備して、PC内に保管したCSVファイルを変換してS3へのアップロードを行います。
pandasやboto3、ローカルのPythonからS3へのアクセス設定については省略します。
検証したバージョンは以下になります。
- Python:3.9.16
- pandas:1.5.3
$ file -i test_utf8.csv test_utf8.csv: text/csv; charset=utf-8
事象再現
まずはpandasでUTF-8のファイルをDataFrameとして読み込みします。
import pandas as pd import os df=pd.read_csv('test_utf8.csv', encoding='utf_8')
ストリングバッファを定義して、to_csv関数でDataFrameの内容をShift JISで書き出します。
バッファの内容はbody変数に格納します。
buffer=io.StringIO() df.to_csv(buffer, index=False, encoding='shift_jis', sep=',', quoting=1, lineterminator='\r\n') body=buffer.getvalue()
boto3クライアントを定義してput_objectを使用してS3バケットにアップロードします。
client=boto3.client('s3') client.put_object(Bucket='test-tmp-20230224', Key='test/test_sjis.csv', Body=body)
アップロードされていたことを確認したので、これをダウンロードしてファイルの文字コードを確認してみます。
to_csvをしたときにエンコーディングでShift JISを指定しているはずですが、何故かエンコーディングが効いておらずUTF-8で出力されていました。
$ file -i test_sjis.csv test_sjis.csv: text/csv; charset=utf-8
解消方法
エンコーディングされなかった原因ですが、理由は簡単で、元の実装ではバッファを定義した際に以下のように実装していたのですが、
buffer=io.StringIO()
以下のように修正して実行したら正常にエンコーディングされるようになりました。
buffer=io.BytesIO()
該当箇所だけ変更して再度実行してみると
import pandas as pd import os df=pd.read_csv('/mnt/c/Users/daiki.handa/Desktop/test_utf8.csv', encoding='utf_8') buffer=io.BytesIO() ★変更箇所 df.to_csv(buffer, index=False, encoding='shift_jis', sep=',', quoting=1, lineterminator='\r\n') body=buffer.getvalue() client.put_object(Bucket='test-tmp-20230224', Key='test/test_sjis2.csv', Body=body)
無事Shift JISで変換されていました。
$ file -i test_sjis2.csv test_sjis2.csv: text/csv; charset=unknown-8bit $ nkf -g test_sjis2.csv Shift_JIS
何故わかったか
自分がちゃんと確認していなかったのが良くなかったんですが、pandasのto_csv関数のドキュメントを確認したら以下のように記載があり、encodingオプションはバイナリ形式のファイルオブジェクトしかサポートされていないようです。
encodingstr, オプション
出力ファイルに使用するエンコーディングを表す文字列。デフォルトは 'utf-8' です。
path_or_bufがバイナリ以外のファイルオブジェクトの場合、エンコーディングはサポートされません。
感想及び所感
原因に気付くのに数時間使ってたので、解決方法を見つけたときは膝から崩れ落ちました。。
ちゃんと公式ドキュメントを確認するのって大事ですね。勉強になりました。