行動すれば次の現実

テック中心の個人ブログ

RubyのAWS SDKを使用してS3からファイルをダウンロードするときのエンコーディング問題

S3 bucketにあるファイルをダウンロードしてTempfileに書き込みしようとしたところ以下のエラーが発生しました。

 Encoding::UndefinedConversionError:
       "\xE5" from ASCII-8BIT to UTF-8

その時のコードは以下の通りです

s3_client = Aws::S3::Client.new
Tempfile.open(file_name) do |file|
  s3_client.get_object({ bucket: 'your-bucket', key: 'your-key' }, target: file)
  file
end

ファイルはCSV形式でエンコーディングはUTF-8なのですが、ASCII-8BITとして見なされているようです。 原因と対処方法を整理したので、同様のことで悩んでいる方はぜひ参考にしてみてください。

エラーの原因

ASCII-8BITとはバイトストリームを取り扱う際に使用される特殊なエンコーディングです。S3上のファイルはエンコーディングがUTF-8のCSVファイルなのですが、S3からget_objectする際はバイナリデータとしてダウンロードされます。

バイナリデータをTempfileに焼き付けようとしたためEncoding::UndefinedConversionErrorのエラーが発生したようです。

Tempfileは画像などのバイナリデータをIO.binmodeとしてバイナリモードにする必要があります。

対処方法

コードを下記に変更することで解決しました。

s3_client = Aws::S3::Client.new
Tempfile.open(file_name) do |file|
  s3_client.get_object({ bucket: 'your-bucket', key: 'your-key' }, target: File.open(file, 'wb'))
  file
end

バイナリ形式の書き込みモード(wb)でFile.openしてあげてからファイルを書き込むようにします。 こうすることでTempfileへのバイナリストリームでの書き込みが可能となります。

もう一つの方法として、以下のようにTempfileをbinmodeにしたものを使用して書き込む方法もあります。 今回はタイプ数の少ないFile.openの方を採用しましたが、こちらの対応のほうが一般的のようです。

temp = Tempfile.new('temp.csv')
temp.binmode
File.read(temp.path, encoding: "bom|utf-8")

参考

ruby - File encoding issue when downloading file from AWS S3 - Stack Overflow