¿Cómo descargar y descomprimir un archivo zip en la memoria en NodeJS?


Quiero descargar un archivo zip de Internet y descomprimirlo en memoria sin guardarlo en un archivo temporal. ¿Cómo puedo hacer esto?

Esto es lo que intenté:

var url = 'http://bdn-ak.bloomberg.com/precanned/Comdty_Calendar_Spread_Option_20120428.txt.zip';

var request = require('request'), fs = require('fs'), zlib = require('zlib');

  request.get(url, function(err, res, file) {
     if(err) throw err;
     zlib.unzip(file, function(err, txt) {
        if(err) throw err;
        console.log(txt.toString()); //outputs nothing
     });
  });
[2] [EDITAR]] Como sugerí, intenté usar la biblioteca adm-zip y todavía no puedo hacer que esto funcione:
var ZipEntry = require('adm-zip/zipEntry');
request.get(url, function(err, res, zipFile) {
        if(err) throw err;
        var zip = new ZipEntry();
        zip.setCompressedData(new Buffer(zipFile.toString('utf-8')));
        var text = zip.getData();
        console.log(text.toString()); // fails
    });
Author: pathikrit, 2012-04-28

5 answers

  • Necesita una biblioteca que pueda manejar búferes. La última versión de adm-zip hará:

    npm install git://github.com/cthackers/adm-zip.git

  • Mi solución utiliza el método http.get, ya que devuelve trozos de búfer.

Código:

var file_url = 'http://bdn-ak.bloomberg.com/precanned/Comdty_Calendar_Spread_Option_20120428.txt.zip';

var request = require('request');
var fs = require('fs');
var AdmZip = require('adm-zip');
var http = require('http');
var url = require('url');

var options = {
    host: url.parse(file_url).host,
    port: 80,
    path: url.parse(file_url).pathname
};

http.get(options, function(res) {
    var data = [], dataLen = 0; 

    res.on('data', function(chunk) {

            data.push(chunk);
            dataLen += chunk.length;

        }).on('end', function() {
            var buf = new Buffer(dataLen);

            for (var i=0, len = data.length, pos = 0; i < len; i++) { 
                data[i].copy(buf, pos); 
                pos += data[i].length; 
            } 

            var zip = new AdmZip(buf);
            var zipEntries = zip.getEntries();
            console.log(zipEntries.length)

            for (var i = 0; i < zipEntries.length; i++)
                console.log(zip.readAsText(zipEntries[i])); 
        });
});

La idea es crear una matriz de búferes y concatenarlos en uno nuevo al final. Esto se debe al hecho de que los búferes no se pueden cambiar de tamaño.

 56
Author: mihai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-02 19:08:08

Lamentablemente no puede canalizar el flujo de respuesta en el trabajo de descompresión como node zlib lib le permite hacer, tiene que almacenar en caché y esperar el final de la respuesta. Le sugiero que canalice la respuesta a un flujo fs en caso de archivos grandes, de lo contrario, llenará su memoria en un abrir y cerrar de ojos!

No entiendo completamente lo que estás tratando de hacer, pero en mi humilde opinión este es el mejor enfoque. Debe mantener sus datos en memoria solo el tiempo que realmente los necesita , y luego transmitir al analizador csv .

Si desea mantener todos sus datos en memoria, puede reemplazar el método de analizador csv fromPath con from que toma un búfer en su lugar y en getData devuelve directamente unzipped

Puede usar el AMDZip (como dijo @mihai) en lugar de node-zip, solo preste atención porque AMDZip aún no está publicado en npm, por lo que necesita:

$ npm install git://github.com/cthackers/adm-zip.git

N.B. Supuesto: el archivo zip contiene solo un archivo

var request = require('request'),
    fs = require('fs'),
    csv = require('csv')
    NodeZip = require('node-zip')

function getData(tmpFolder, url, callback) {
  var tempZipFilePath = tmpFolder + new Date().getTime() + Math.random()
  var tempZipFileStream = fs.createWriteStream(tempZipFilePath)
  request.get({
    url: url,
    encoding: null
  }).on('end', function() {
    fs.readFile(tempZipFilePath, 'base64', function (err, zipContent) {
      var zip = new NodeZip(zipContent, { base64: true })
      Object.keys(zip.files).forEach(function (filename) {
        var tempFilePath = tmpFolder + new Date().getTime() + Math.random()
        var unzipped = zip.files[filename].data
        fs.writeFile(tempFilePath, unzipped, function (err) {
          callback(err, tempFilePath)
        })
      })
    })
  }).pipe(tempZipFileStream)
}

getData('/tmp/', 'http://bdn-ak.bloomberg.com/precanned/Comdty_Calendar_Spread_Option_20120428.txt.zip', function (err, path) {
  if (err) {
    return console.error('error: %s' + err.message)
  }
  var metadata = []
  csv().fromPath(path, {
    delimiter: '|',
    columns: true
  }).transform(function (data){
    // do things with your data
    if (data.NAME[0] === '#') {
      metadata.push(data.NAME)
    } else {
      return data
    }
  }).on('data', function (data, index) {
    console.log('#%d %s', index, JSON.stringify(data, null, '  '))
  }).on('end',function (count) {
    console.log('Metadata: %s', JSON.stringify(metadata, null, '  '))
    console.log('Number of lines: %d', count)
  }).on('error', function (error) {
    console.error('csv parsing error: %s', error.message)
  })
})
 6
Author: kilianc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-06 18:32:19

Si estás bajo macOS o Linux, puedes usar el comando unzip para descomprimir desde stdin.

En este ejemplo estoy leyendo el archivo zip del sistema de archivos en un objeto Buffer pero funciona con un archivo descargado también:

// Get a Buffer with the zip content
var fs = require("fs")
  , zip = fs.readFileSync(__dirname + "/test.zip");


// Now the actual unzipping:
var spawn = require('child_process').spawn
  , fileToExtract = "test.js"
    // -p tells unzip to extract to stdout
  , unzip = spawn("unzip", ["-p", "/dev/stdin", fileToExtract ])
  ;

// Write the Buffer to stdin
unzip.stdin.write(zip);

// Handle errors
unzip.stderr.on('data', function (data) {
  console.log("There has been an error: ", data.toString("utf-8"));
});

// Handle the unzipped stdout
unzip.stdout.on('data', function (data) {
  console.log("Unzipped file: ", data.toString("utf-8"));
});

unzip.stdin.end();

Que en realidad es solo la versión de nodo de:

cat test.zip | unzip -p /dev/stdin test.js

EDITAR : Vale la pena señalar que esto no funcionará si el zip de entrada es demasiado grande para ser leído en un trozo de stdin. Si necesita leer archivos más grandes, y su archivo zip contiene solo un archivo, puede usar funzip en lugar de unzip:

var unzip = spawn("funzip");

Si su archivo zip contiene varios archivos (y el archivo que desea no es el primero) me temo que no tiene suerte. Unzip necesita buscar en el archivo .zip ya que los archivos zip son solo un contenedor, y unzip puede simplemente descomprimir el último archivo en él. En ese caso, debe guardar el archivo temporalmente ( node-temp es útil).

 4
Author: enyo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-01 10:29:27

Hace dos días se lanzó el módulo node-zip, que es un envoltorio para la versión de JavaScript de Zip: JSZip.

var NodeZip = require('node-zip')
  , zip = new NodeZip(zipBuffer.toString("base64"), { base64: true })
  , unzipped = zip.files["your-text-file.txt"].data;
 1
Author: enyo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-01 10:29:13

Var fs = require ('fs); var unzip = require ('unzip');

/ / descomprimir a.zip al diccionario actual

Fs.createReadStream ('./path/a.zip").tubo (descomprimir.Extract ({ path:'./ path/'}));

Usé unzip module, y funcionó .

 -4
Author: lxd875697126,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-01 10:01:14