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

kay*_*zie 4 ruby amazon-s3 amazon-web-services 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)
Run Code Online (Sandbox Code Playgroud)

它从 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'
Run Code Online (Sandbox Code Playgroud)

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

我尝试过的事情:

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

obj.upload_file({file path}, content_encoding: 'utf-8')
Run Code Online (Sandbox Code Playgroud)

此外,当您打电话时,.get您可以设置response_content_encoding

obj.get(response_target: temp, response_content_encoding: 'utf-8')
Run Code Online (Sandbox Code Playgroud)

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

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

temp = Tempfile.new('temp.csv', encoding: 'ascii-8bit')
Run Code Online (Sandbox Code Playgroud)

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

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

Cas*_*per 6

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

第 1 步(将 Tempfile 放入 binmode):

temp = Tempfile.new('temp.csv')
temp.binmode
Run Code Online (Sandbox Code Playgroud)

然而,您会遇到一个问题,那就是现在您的 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")
Run Code Online (Sandbox Code Playgroud)

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

另一种选择(来自OP)

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

有用的参考:
有没有办法从 UTF-8 编码的文件中删除 BOM?
如何避免在读取文件时被 UTF-8 BOM 绊倒
UTF-8 和没有 BOM 的 UTF-8 有什么区别?
如何在 Ruby 中将 BOM 标记写入文件

  • obj.get.body 的编码是 UTF-8。感谢您的建议,我会考虑可能会打开 AWS SDK gem 的问题。我会将其标记为已接受的答案,因为我认为它可以帮助人们解决我遇到的特定问题。 (2认同)