使用ruby流式处理并解压缩大型csv文件

时间:2014-04-29 23:48:44

标签: ruby csv io net-http rubyzip

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

  • big_file.zip~700mb
  • big_file.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是否可以使用rubyzip的InputStream?它是否足以像File一样解析行?如果它想要读取但缓冲区是空的,它会不会出现问题?

我不知道这是否是正确的做法。也许一些EventMachine解决方案会更好(虽然我之前从未使用过EventMachine,但是如果它对这样的东西效果更好,我就是为了它)。

1 个答案:

答案 0 :(得分:10)

自从我发布此问题以来已经有一段时间了,万一其他人遇到它我认为可能值得分享我找到的内容。

  1. 我处理Ruby的标准库CSV的行数太慢了。我的csv文件非常简单,无论如何我都不需要所有的东西来处理引用的字符串或类型强制。只需使用IO#gets然后在逗号上拆分行即可。
  2. 我无法将整个内容从http传递到Zip::Inputstream到包含csv数据的某些IO。这是因为zip file structure在文件末尾有中央目录结束(EOCD)。这是为了提取文件所需要的,因此从http流式传输它似乎不会起作用。
  3. 我最终选择的解决方案是将文件下载到磁盘,然后使用Ruby的open3库和Linux unzip包来从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开关将解压缩的文件发送给stdout。 IO.popen然后使用管道在ruby中创建一个IO对象。工作得很好。如果您想要额外的处理,也可以将它与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