将 CSV 流从 Ruby 上传到 S3

Posted

技术标签:

【中文标题】将 CSV 流从 Ruby 上传到 S3【英文标题】:Upload CSV stream from Ruby to S3 【发布时间】:2016-05-22 19:53:01 【问题描述】:

我正在处理我想从我的 Rails 应用程序导出的潜在巨大 CSV 文件,并且由于它在 Heroku 上运行,我的想法是在生成这些 CSV 文件时将它们直接流式传输到 S3。

现在,我有一个问题,Aws::S3 需要一个文件才能执行上传,而在我的 Rails 应用程序中,我想做类似的事情:

S3.bucket('my-bucket').object('my-csv') << %w(this is one line)

我怎样才能做到这一点?

【问题讨论】:

您看过 S3 Multipart gem 吗? github.com/maxgillett/s3_multipart 我认为在没有生成之前直接将文件保存到 s3 是可能的。 我正在动态构建 CSV,在后台作业中,我不通过某种界面上传它。 @AndreiHorak 你找到解决方案了吗? @Tonja 不幸的是,我没有时间深入研究它。希望你会遇到更好的运气! @linkyndy 我也有同样的要求。您对此有任何解决方案吗? 【参考方案1】:

您可以使用 s3 分段上传,它允许通过将大对象拆分为多个块进行上传。 https://docs.aws.amazon.com/AmazonS3/latest/dev/mpuoverview.html

分段上传需要更复杂的编码,但 aws-sdk-ruby V3 支持upload_stream 方法,该方法似乎在内部执行分段上传,并且非常易于使用。也许这个用例的确切解决方案。 https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#upload_stream-instance_method

client = Aws::S3::Client.new(
  region: 'ap-northeast-1',
  credentials: your_credential
)

obj = Aws::S3::Object.new('your-bucket-here', 'path-to-output', client: client)

require "csv"
obj.upload_stream do |write_stream|
  [
    %w(this is first line),
    %w(this is second line),
    %w(this is third line),
  ].each do |line|
    write_stream << line.to_csv
  end
end
this,is,first,line
this,is,second,line
this,is,third,line

upload_stream 块的参数通常可以用作 IO 对象,它允许您像处理文件或其他 IO 对象一样链接和包装 CSV 生成:

obj.upload_stream do |write_stream|
  CSV(write_stream) do |csv|
    [
      %w(this is first line),
      %w(this is second line),
      %w(this is third line),
    ].each do |line|
      csv << line
    end
  end
end

或者,例如,您可以在生成和上传 CSV 时对其进行压缩,使用临时文件来减少内存占用:

obj.upload_stream(tempfile: true) do |write_stream|
  # When uploading compressed data, use binmode to avoid an encoding error.
  write_stream.binmode

  Zlib::GzipWriter.wrap(write_stream) do |gzw|
    CSV(gzw) do |csv|
      [
        %w(this is first line),
        %w(this is second line),
        %w(this is third line),
      ].each do |line|
        csv << line
      end
    end
  end
end

已编辑:在压缩的示例代码中,您必须添加binmode 以修复以下错误:

Aws::S3::MultipartUploadError: multipart upload failed: "\x8D" from ASCII-8BIT to UTF-8

【讨论】:

谢谢三俊!这正是我一直在寻找的。我还添加了基于块的 CSV 生成器和 IO 包装器的示例,它们也可用于在上传到 S3 之前通过 gzip 进行压缩 你知道我用upload_stream上传文件时如何设置内容类型吗? @Kazuki 你可以像这样传递参数。 s3obj.upload_stream(acl: 'private', content_type: 'text/csv; charset=UTF-8') do |stream| ...s3obj 应该是这样的 s3obj = Aws::S3::Object.new('your-bucket-name', path, client: client)【参考方案2】:
s3 = Aws::S3::Resource.new(region:'us-west-2')
obj = s3.bucket.object("#FOLDER_NAME/#file_name.csv")
file_csv = CSV.generate do |csv|
    csv << ActionLog.column_names
    ActionLog.all.each do |action_log|
      csv << action_log.attributes.values
    end
  end
  obj.put body: file_csv

file_csv = CSV.generate 是在 Ruby 中创建一串 CSV 数据。创建这个 CSV 字符串后,我们使用存储桶将路径放入 S3

#FOLDER_NAME/#file_name.csv

在我的代码中,我将所有数据导出到 ActionLog 模型。

【讨论】:

用解释包围你的答案会大大改善它。 谢谢!我已经更新了我的答案,但是我的英语不好,对不起:( 我非常努力地改进您的语法和拼写。但我承认我的方法可能不够。我无法推断出所有的含义。 这不能回答我的问题。我特别提到“流”。检查我对另一个答案的回复。 请注意 ActionLog.all.each 会将所有 ActionLogs 加载到 RAM 中。你应该这样做ActionLog.find_each【参考方案3】:

我会看看http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html#write-instance_method,因为这可能就是您要找的。​​p>

编辑 http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadObjSingleOpRuby.html 可能更相关,因为第一个链接指向 ruby​​ aws-sdk v1

require 'aws-sdk'

s3 = Aws::S3::Resource.new(region:'us-west-2')
obj = s3.bucket('bucket-name').object('key')

# string data
obj.put(body: 'Hello World!')

# IO object
File.open('source', 'rb') do |file|
  obj.put(body: file)
end

【讨论】:

这仍然是一个已经构建的文件。我想逐步上传文件的块。

以上是关于将 CSV 流从 Ruby 上传到 S3的主要内容,如果未能解决你的问题,请参考以下文章

将 CSV 文件从 JSON 数据上传到 S3 存储桶

如何将抓取的数据从 Scrapy 以 csv 或 json 格式上传到 Amazon S3?

使用 lambda 函数通过 s3 存储桶将巨大的 .csv 文件上传到 dynamodb 时出错

直接从服务器将 csv 文件上传到 aws s3 存储桶

从静态网页上传 csv 文件到 S3 存储桶

从 Ruby on Rails 多次上传到 Amazon S3 - 使用啥后台处理系统?