¿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
});
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.
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)
})
})
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).
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;
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ó .
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