从AWS S3下载文件时的文件编码问题

时间:2018-08-08 21:38:30

标签: ruby amazon-web-services amazon-s3 aws-sdk-ruby

我在AWS S3中有一个CSV文件,试图在本地临时文件中打开该文件。这是代码:

s3 = Aws::S3::Resource.new
bucket = s3.bucket({bucket name})
obj = bucket.object({object key})
temp = Tempfile.new('temp.csv')
obj.get(response_target: temp)

它将从AWS中提取文件并将其加载到名为“ temp.csv”的新临时文件中。对于某些文件,obj.get(..)行会引发以下错误:

WARN: Encoding::UndefinedConversionError: "\xEF" from ASCII-8BIT to UTF-8
WARN: /Users/.rbenv/versions/2.5.0/lib/ruby/2.5.0/delegate.rb:349:in `write'
/Users/.rbenv/versions/2.5.0/lib/ruby/2.5.0/delegate.rb:349:in `block in delegating_block'
/Users/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/aws-sdk-core-3.21.2/lib/seahorse/client/http/response.rb:62:in `signal_data'
/Users/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/aws-sdk-core-3.21.2/lib/seahorse/client/net_http/handler.rb:83:in `block (3 levels) in transmit'
...
/Users/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/aws-sdk-s3-1.13.0/lib/aws-sdk-s3/client.rb:2666:in `get_object'
/Users/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/aws-sdk-s3-1.13.0/lib/aws-sdk-s3/object.rb:657:in `get'

Stacktrace显示最初由.get从适用于Ruby的AWS开发工具包引发错误。

我尝试过的事情:

将文件(对象)上传到AWS S3时,您可以指定content_encoding,所以我尝试将其设置为UTF-8:

obj.upload_file({file path}, content_encoding: 'utf-8')

另外,当您致电.get时,可以设置response_content_encoding

obj.get(response_target: temp, response_content_encoding: 'utf-8')

这些都不起作用,它们会导致与上述相同的错误。我真的希望能做到这一点。在AWS S3仪表板中,我可以看到确实通过代码正确设置了内容编码,但是似乎没有什么不同。

在上面的第一个代码段中,当我执行以下操作时,它确实起作用:

temp = Tempfile.new('temp.csv', encoding: 'ascii-8bit')

但是我更喜欢使用正确的编码从AWS S3上传和/或下载文件。有人可以解释为什么在临时文件上指定编码有效吗?或者如何通过AWS S3上传/下载使其工作?

重要说明:错误消息中有问题的字符似乎只是我正在使用的此自动生成文件开头添加的随机符号。我不担心正确读取字符,无论如何我都会在解析文件时忽略它。

3 个答案:

答案 0 :(得分:1)

对于您的所有问题,我没有完整的答案,但是我认为我有一个通用的解决方案,那就是始终将临时文件置于二进制模式。这样,AWS gem只需将存储桶中的数据转储到文件中,而无需任何进一步的重新编码:

步骤1(将临时文件置于bin模式):

temp = Tempfile.new('temp.csv')
temp.binmode

但是,您会遇到一个问题,那就是您的UTF-8文件中现在有一个3字节的BOM表头。

我不知道该BOM的来源。上传文件时在那儿?如果是这样,最好在上传之前剥离3字节的BOM。

但是,如果您按如下所示设置系统,那就没关系了,因为Ruby支持带或不带BOM的UTF-8透明读取,并且无论BOM头是否在文件中或文件中,都将正确返回字符串。不是:

步骤2(使用bom | utf-8处理文件):

File.read(temp.path, encoding: "bom|utf-8")
# or...
CSV.read(temp.path,  encoding: "bom|utf-8")

这应该涵盖您认为的所有基础。无论您接收的是编码为BOM + UTF-8还是普通UTF-8的文件,都将以这种方式正确处理它们,而最终字符串中不会出现任何额外的标题字符,并且在使用AWS保存文件时不会出错。

另一个选项(来自@kziegler)

改为使用obj.get.body,这将绕过response_target和Tempfile的整个问题。

有用的参考文献
Is there a way to remove the BOM from a UTF-8 encoded file?
How to avoid tripping over UTF-8 BOM when reading files
What's different between UTF-8 and UTF-8 without BOM?
How to write BOM marker to a file in Ruby

答案 1 :(得分:0)

Ruby SDK文档提供了一个将S3项目下载到https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/s3-example-get-bucket-item.html中的文件系统中的示例。我只是运行它,并且效果很好。

答案 2 :(得分:0)

我通过另外使用File.open(tmp, 'wb')解决了此编码问题。外观如下:

s3_object = Aws::S3::Resource.new.bucket("bucket-name").object("resource-key")

Tempfile.new.tap do |file|
   s3_object.get(response_target: File.open(file, "wb"))
end