web-dev-qa-db-fra.com

Comment télécharger et décompresser un fichier Zip en mémoire dans NodeJs?

Je souhaite télécharger un fichier Zip sur Internet et le décompresser en mémoire sans l'enregistrer dans un fichier temporaire. Comment puis-je faire ceci?

Voici ce que j'ai essayé:

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
     });
  });

[EDIT] Comme suggéré, j'ai essayé d'utiliser la bibliothèque adm-Zip et je ne peux toujours pas faire fonctionner cela:

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
    });
42
pathikrit

Vous avez besoin d'une bibliothèque capable de gérer les tampons. La dernière version de adm-Zip ça ira:

npm install adm-Zip

Ma solution utilise le http.get, car elle renvoie des morceaux de tampon.

Code:

var file_url = 'http://notepad-plus-plus.org/repository/7.x/7.6/npp.7.6.bin.x64.Zip';

var AdmZip = require('adm-Zip');
var http = require('http');

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

  res.on('data', function(chunk) {
    data.Push(chunk);
    dataLen += chunk.length;

  }).on('end', function() {
    var buf = Buffer.alloc(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++) {
      if (zipEntries[i].entryName.match(/readme/))
        console.log(Zip.readAsText(zipEntries[i]));
    }
  });
});

L'idée est de créer un tableau de tampons et de les concaténer en un nouveau à la fin. Cela est dû au fait que les tampons ne peuvent pas être redimensionnés.

Mise à jour

Il s'agit d'une solution plus simple qui utilise le module request pour obtenir la réponse dans un tampon, en définissant encoding: null dans les options. Il suit également les redirections et résout automatiquement http/https.

var file_url = 'https://github.com/mihaifm/linq/releases/download/3.1.1/linq.js-3.1.1.Zip';

var AdmZip = require('adm-Zip');
var request = require('request');

request.get({url: file_url, encoding: null}, (err, res, body) => {
  var Zip = new AdmZip(body);
  var zipEntries = Zip.getEntries();
  console.log(zipEntries.length);

  zipEntries.forEach((entry) => {
    if (entry.entryName.match(/readme/i))
      console.log(Zip.readAsText(entry));
  });
});

Le body de la réponse est un tampon qui peut être passé directement à AdmZip, simplifiant tout le processus.

67
mihai

Malheureusement, vous ne pouvez pas canaliser le flux de réponse dans le travail de décompression comme le permet le nœud zlib lib, vous devez mettre en cache et attendez la fin de la réponse. Je vous suggère de diriger la réponse vers un flux fs en cas de gros fichiers, sinon vous remplirez votre mémoire en un clin d'œil!

Je ne comprends pas complètement ce que vous essayez de faire, mais à mon humble avis c'est la meilleure approche . Vous devez conserver vos données en mémoire uniquement le temps dont vous en avez vraiment besoin , puis diffuser vers le analyseur csv.

Si vous souhaitez conserver toutes vos données en mémoire, vous pouvez remplacer la méthode de l'analyseur csv fromPath par from qui prend un tampon à la place et dans getData retourner directement unzipped

Vous pouvez utiliser le AMDZip (comme l'a dit @mihai) au lieu de node-Zip, faites juste attention car AMDZip n'est pas encore publié dans npm donc vous avez besoin de:

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

N.B. Hypothèse: le fichier Zip ne contient qu'un seul fichier

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
kilianc

Si vous êtes sous MacOS ou Linux, vous pouvez utiliser la commande unzip pour décompresser de stdin.

Dans cet exemple, je lis le fichier Zip du système de fichiers dans un objet Buffer mais cela fonctionne aussi avec un fichier téléchargé:

// 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();

Ce qui est en fait juste la version du nœud de:

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

[~ # ~] edit [~ # ~] : Il convient de noter que cela ne fonctionnera pas si le Zip d'entrée est trop grand pour être lu en un seul morceau de stdin. Si vous devez lire des fichiers plus gros et que votre fichier Zip ne contient qu'un seul fichier, vous pouvez utiliser funzip au lieu de unzip:

var unzip = spawn("funzip");

Si votre fichier Zip contient plusieurs fichiers (et que le fichier que vous voulez n'est pas le premier), j'ai peur de dire que vous n'avez pas de chance. Décompressez doit rechercher dans le .Zip fichier car les fichiers Zip ne sont qu'un conteneur, et décompresser peut simplement décompresser le dernier fichier qu'il contient. Dans ce cas, vous devez enregistrer le fichier temporairement ( node-temp est très pratique).

4
enyo

Il y a deux jours, le module node-Zip a été publié, qui est un wrapper pour la version JavaScript uniquement 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
enyo