如何在NodeJs的内存中下载和解压缩zip文件?

IT技术 javascript node.js zip zlib unzip
2021-02-23 11:43:21

我想从 Internet 下载一个 zip 文件并将其解压缩到内存中而不保存到临时文件中。我怎样才能做到这一点?

这是我尝试过的:

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

[编辑] 根据建议,我尝试使用 adm-zip 库,但仍然无法完成这项工作:

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
    });
4个回答

您需要一个可以处理缓冲区的库。最新版本的adm-zip将做:

npm install adm-zip

我的解决方案使用该http.get方法,因为它返回 Buffer 块。

代码:

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

这个想法是创建一个缓冲区数组,并在最后将它们连接成一个新的缓冲区。这是因为缓冲区不能调整大小。

更新

这是一个更简单的解决方案,它使用requestmodule通过encoding: null在选项中设置来获取缓冲区中的响应它还遵循重定向并自动解析 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));
  });
});

body响应的是,可以直接传递到缓冲器AdmZip,简化了整个过程。

不适用于来自 github 标签的 zip - 错误无效或不受支持的 zip 格式。未找到 END 标头
2021-04-16 11:43:21
是的,谢谢……像往常一样,有很多东西可以使用。我考虑过,但认为只有 node.js 代码的示例可能会更好。
2021-04-19 11:43:21
我以前axios制作的请求,这必须下载整个事情作为一个ArrayBuffer如果设置选项的responseType'arraybuffer'然后你可以response.data直接传递给 AdmZip
2021-05-06 11:43:21
npm 上的最新版本支持缓冲区。
2021-05-08 11:43:21
我只想强调,简单地安装 adm-zip 是npm install adm-zip行不通的,因为只有 github 上的最新版本才支持缓冲区。
2021-05-10 11:43:21

遗憾的是,您无法将响应流通过管道传输到解压缩作业中,因为节点zlib库允许您这样做,您必须缓存并等待响应结束。我建议你fs在大文件的情况下将响应通过管道传输流,否则你会在眨眼间填满你的记忆!

我不完全理解你想要做什么,但恕我直言,这是最好的方法您应该只在真正需要时才将数据保存在内存中,然后传输到csv 解析器

如果您想将所有数据保存在内存中,您可以将 csv 解析器方法替换为采用缓冲区的方法fromPathfrom并在 getData 中直接返回unzipped

您可以使用AMDZip(如@mihai 所说)代替node-zip,只需注意,因为AMDZip尚未在 npm 中发布,因此您需要:

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

NB 假设:zip 文件只包含一个文件

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

如果您使用的是 MacOS 或 Linux,则可以使用该unzip命令从stdin.

在这个例子中,我将文件系统中的 zip 文件读入一个Buffer对象,但它也适用于下载的文件:

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

这实际上只是以下的节点版本:

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

编辑:值得注意的是,如果输入 zip 太大而无法从标准输入中读取一个块,这将不起作用。如果您需要读取更大的文件,而您的 zip 文件只包含一个文件,您可以使用funzip代替unzip

var unzip = spawn("funzip");

如果您的 zip 文件包含多个文件(并且您想要的文件不是第一个),我恐怕会说您不走运。.zipunzip需要在文件中查找,因为 zip 文件只是一个容器,而 unzip 可能只是解压缩其中的最后一个文件。在这种情况下,您必须临时保存文件(node-temp派上用场)。

谢谢你。我一直在用很多坏的/未记录的 zip 库来解决问题,只是试图解压缩一个存档。这是黄金。
2021-04-16 11:43:21
由于 .zip 文件格式的设计,不可能在不牺牲正确性的情况下从头到尾解释 .zip 文件。中央目录是 .zip 文件内容的权威,位于 .zip 文件的末尾,而不是开头。像这样的流/管道需要缓冲整个 .zip 文件才能在解释任何内容之前到达中央目录(违背本示例的目的)。使用非平凡的 zip 文件执行此操作将导致“找不到中央目录结尾签名”。
2021-04-18 11:43:21
在mac或linux下使用'unzip'的好处是解压后会保留原文件的所有权限,但是node createWriteStream会默认写'0666'到文件中(adm-zip使用)。
2021-04-23 11:43:21
我从来没有在没有评论的情况下得到过反对......我认为这是因为这只适用于一个文件或者 zip 相当小。
2021-04-25 11:43:21
我有兴趣在不发表评论的情况下为某人投票反对。说真的,这不起作用的原因是什么?我是初学者。
2021-04-26 11:43:21

两天前,该modulenode-zip已经发布,它是 Zip 的JavaScript 版本的包装器:JSZip

var NodeZip = require('node-zip')
  , zip = new NodeZip(zipBuffer.toString("base64"), { base64: true })
  , unzipped = zip.files["your-text-file.txt"].data;
node-zip 不支持缓冲区,因此您被迫转换为字符串,这是一件坏事
2021-05-08 11:43:21