2014-04-29 18 views
6

Ho problemi dove devo scaricare, decomprimere e quindi elaborare riga per riga un file CSV molto grande. Penso che sia utile per dare un'idea quanto sia grande il file è:Stream e decomprimere file csv di grandi dimensioni con ruby ​​

  • big_file.zip ~ 700MB
  • big_file.csv ~ 23GB

Ecco alcune cose che vorrei per accadere:

  • non c'è bisogno di scaricare l'intero file prima di decomprimere
  • non dover decomprimere tutto il file prima di parsing linee csv
  • 0.123.
  • Non utilizzare su molto di memoria/disco mentre si fa tutto questo

Non so se è possibile o no. Ecco cosa stavo pensando:

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 

Ecco i problemi che so di:

  • open-uri legge tutta risposta e lo salva in un Tempfile, che non va bene con un file di queste dimensioni. Probabilmente avrei bisogno di usare Net::HTTP direttamente, ma non sono sicuro di come farlo e ottenere ancora un IO.
  • Non so quanto velocemente sarà il download o se il Zip::InputStream funziona come ho mostrato. È possibile decomprimere alcuni file quando non è ancora tutto?
  • Il CSV.foreach funziona con Rubyzip InputStream? Si comporta abbastanza come File che sarà in grado di analizzare le righe? Sarà fuori di testa se vuole leggere ma il buffer è vuoto?

Non so se questo è l'approccio giusto. Forse una soluzione EventMachine sarebbe meglio (anche se non ho mai usato EventMachine prima, ma se funziona meglio per qualcosa di simile, sono tutto a posto).

+0

Non penso che lo streaming dello zip funzionerà a causa della struttura dei file zip. Potrebbe forse fare qualcosa come 'funzip' se c'era solo un file nello zip (o quello che volevo era il primo) ma non è così. – ZombieDev

risposta

6

È passato un po 'di tempo da quando ho postato questa domanda e nel caso in cui qualcuno lo incontrasse ho pensato che valesse la pena condividere quello che ho trovato.

  1. Per il numero di righe mi stavo occupando di libreria standard di Ruby CSV era troppo lento. Il mio file CSV era abbastanza semplice da non aver bisogno di tutte quelle cose per trattare le stringhe tra virgolette o la coercizione di tipo comunque. Era molto più semplice usare solo IO#gets e quindi dividere la riga in virgole.
  2. Impossibile visualizzare l'intero contenuto da http a Zip::Inputstream in un numero IO contenente i dati csv. Questo perché zip file structure ha la fine della directory centrale (EOCD) alla fine del file. Ciò è necessario per estrarre il file in modo che lo streaming da http non sembri funzioni.

La soluzione che ho finito per andare con è stato quello di scaricare il file su disco e quindi utilizzare libreria Open3 di Ruby e il pacchetto Linux unzip per lo streaming di file CSV non compressa dalla zip.

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 

L'interruttore -p sul decomprimere invia il file estratto a stdout. IO.popen quindi utilizzare i tubi per trasformare un oggetto IO in rubino. Funziona molto bene. Potresti usarlo anche con lo CSV se volevi quell'elaborazione extra, era troppo lento per me.