11

我有问题需要下载、解压缩然后逐行处理一个非常大的 CSV 文件。我认为让您了解文件的大小很有用:

  • 大文件.zip ~700mb
  • 大文件.csv ~23gb

这是我希望发生的一些事情:

  • 解压前不必下载整个文件
  • 在解析 csv 行之前不必解压缩整个文件
  • 在执行所有这些操作时不要占用太多内存/磁盘

我不知道这是否可能。这就是我的想法:

require 'open-uri'
require 'rubyzip'
require 'csv'

open('http://foo.bar/big_file.zip') do |zipped|
  Zip::InputStream.open(zipped) do |unzipped|
    sleep 10 until entry = unzipped.get_next_entry && entry.name == 'big_file.csv'
    CSV.foreach(unzipped) do |row|
      # process the row, maybe write out to STDOUT or some file
    end
  end
end

以下是我所知道的问题:

  • open-uri读取整个响应并将其保存到一个Tempfile不适合这种大小的文件的文件中。我可能需要Net::HTTP直接使用,但我不知道该怎么做并且仍然得到一个IO.
  • 我不知道下载速度会有多快,也不知道它的Zip::InputStream工作方式是否符合我展示的工作方式。当它还没有全部存在时,它可以解压缩一些文件吗?
  • CSV.foreach与 ruby​​zip 的工作InputStream?它的行为是否足够像File它能够解析出行?如果它想读取但缓冲区是空的,它会发疯吗?

我不知道这是否是正确的方法。也许一些 EventMachine 解决方案会更好(虽然我以前从未使用过 EventMachine,但如果它更适合这样的事情,我完全赞成)。

4

1 回答 1

12

自从我发布这个问题以来已经有一段时间了,如果其他人遇到它,我认为可能值得分享我的发现。

  1. 对于我处理 Ruby 标准库的行数CSV来说太慢了。我的 csv 文件很简单,我不需要所有这些东西来处理带引号的字符串或键入强制。它更容易使用IO#gets,然后用逗号分隔行。
  2. 我无法将整个内容从 http 流式传输到包含 csv 数据的Zip::Inputstream某些内容。IO这是因为zip 文件结构在文件末尾具有中央目录结尾 (EOCD)。这是提取文件所必需的,因此从 http 流式传输它似乎不起作用。

我最终采用的解决方案是将文件下载到磁盘,然后使用 Ruby 的 open3 库和 Linuxunzip包从 zip 流式传输未压缩的 csv 文件。

require 'open3'

IO.popen('unzip -p /path/to/big_file.zip big_file.csv', 'rb') do |io|
  line = io.gets
  # do stuff to process the CSV line
end

-p压缩开关将提取的文件发送到标准输出。IO.popen然后使用管道使其成为IOruby​​ 中的对象。效果很好。如果你想要额外的处理,你也可以使用它CSV,它对我来说太慢了。

require 'open3'
require 'csv'

IO.popen('unzip -p /path/to/big_file.zip big_file.csv', 'rb') do |io|
  CSV.foreach(io) do |row|
    # process the row
  end
end
于 2015-08-06T13:57:27.677 回答