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

File encoding issue when downloading file from AWS S3

我在 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 从 AWS SDK for Ruby.

抛出的

我尝试过的事情:

当上传文件(对象)到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 上传 and/or 下载文件。有人可以解释为什么在临时文件上指定编码有效吗?或者如何让它通过 AWS S3 工作 upload/download?

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

我没有对你所有问题的完整答案,但我想我有一个通用的解决方案,那就是始终将临时文件置于二进制模式。这样 AWS gem 将简单地将存储桶中的数据转储到文件中,而无需进一步 re/encoding:

第一步(将临时文件放入binmode):

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

然而你会遇到一个问题,那就是现在你的 UTF-8 文件中有一个 3 字节的 BOM header。

我不知道这个BOM是从哪里来的。上传文件时是否存在?如果是这样,在上传之前剥离 3 字节 BOM 可能是个好主意。

但是,如果你的系统设置如下,则没有关系,因为Ruby支持透明读取UTF-8有无BOM,并且return无论字符串如何BOM header 是否在文件中:

步骤 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 的文件,您都将以这种方式正确处理它们,最终字符串中不会出现任何额外的 header 字符,并且在使用 AWS 保存它们时不会出错。

另一个选项(来自OP)

改用 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 the difference between UTF-8 and UTF-8 without BOM?
How to write BOM marker to a file in Ruby

Ruby SDK 文档有一个将 S3 项目下载到 https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/s3-example-get-bucket-item.html 文件系统的示例。我只是 运行 它并且工作正常。

我另外使用 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