在Node.js跨平台中下载和解压缩文件的最简单方法?


77

只是寻找一种简单的解决方案,以在任何操作系统上的Node.js中下载和解压缩.zip.tar.gz文件。

不知道这是内置的,还是我必须使用单独的库。有任何想法吗?只需查找几行代码,以便当我想在节点中下载下一个zip文件时,您就可以了。这样的感觉应该很容易和/或内置,但是我什么也找不到。谢谢!

Answers:


39

结帐adm-zip

ADM-ZIP是用于NodeJS的zip数据压缩的纯JavaScript实现。

该库使您可以:

  • 将zip文件直接解压缩到磁盘或内存缓冲区中
  • 压缩文件并将它们以.zip格式或压缩缓冲区存储到磁盘
  • 更新现有内容的内容/添加新文件/删除文件 .zip

1
我在任何zlib实现中都遇到了错误,该错误会引发“无效块类型”(错误代码为“ Z_DATA_ERROR”)错误,但Windows解压缩将在该文件上正常工作。adm-zip也似乎可以正常工作。
GotDibbs

5
cygwin压缩文件中的“无效或不受支持的zip格式。找不到END标头”。布洛
psp 2014年

我们unzip也可以使用此模块在服务器端归档吗?
Manwal'3

我使用adm-zip包收到此错误,这对我来说是行不通的-github.com/cthackers/adm-zip/issues/25
chrismarx '16

1
它无法解压缩大文件。例如,试图解压我有一个大小的文件2857964996RangeError [ERR_INVALID_OPT_VALUE]: The value "2857964996" is invalid for option "size"
亚历山大Annic

86

是2017年(准确地说是10月26日)。

对于诸如unzip之类的古老且普遍使用的技术,我希望那里存在一个相当流行,成熟的node.js解压缩库,该库“停滞”和“未维护”,因为它是“完整的”。

但是,大多数库似乎完全可怕,或者几个月前才提交。这非常令人担忧。因此,我浏览了几个解压缩库,阅读了他们的文档,并尝试了他们的示例来弄清楚WTF。例如,我尝试了以下方法:

2020年更新:尚未尝试过,但是还有存档器

热门推荐: yauzl

非常适合完全下载的文件。串流效果不佳。

有据可查。效果很好。说得通。

第二顺位: node-stream-zip

蚂蚁的node-stream-zip似乎是最好的

安装:

npm install --save node-stream-zip

用法:

'use strict';

var fs = require('fs');
var StreamZip = require('node-stream-zip');

var zip = new StreamZip({
  file: './example.zip'
, storeEntries: true
});

zip.on('error', function (err) { console.error('[ERROR]', err); });

zip.on('ready', function () {
  console.log('All entries read: ' + zip.entriesCount);
  //console.log(zip.entries());
});

zip.on('entry', function (entry) {
  var pathname = path.resolve('./temp', entry.name);
  if (/\.\./.test(path.relative('./temp', pathname))) {
      console.warn("[zip warn]: ignoring maliciously crafted paths in zip file:", entry.name);
      return;
  }

  if ('/' === entry.name[entry.name.length - 1]) {
    console.log('[DIR]', entry.name);
    return;
  }

  console.log('[FILE]', entry.name);
  zip.stream(entry.name, function (err, stream) {
    if (err) { console.error('Error:', err.toString()); return; }

    stream.on('error', function (err) { console.log('[ERROR]', err); return; });

    // example: print contents to screen
    //stream.pipe(process.stdout);

    // example: save contents to file
    fs.mkdir(
      path.dirname(pathname),
      { recursive: true },
      function (err) {
        stream.pipe(fs.createWriteStream(pathname));
      }
    );
  });
});

安全警告

不知道这是否检查entry.name会错误解析的恶意制作的路径(例如../../../foo/etc/passwd)。

您可以通过比较轻松地自己检查一下/\.\./.test(path.relative('./to/dir', path.resolve('./to/dir', entry.name)))

优点:(为什么我认为这是最好的?)

  • 可以解压缩普通文件(也许不是一些带有怪异扩展名的疯狂文件)
  • 可以流
  • 似乎不必加载整个zip即可读取条目
  • 在普通JavaScript中有示例(未编译)
  • 不包括厨房水槽(即,URL加载,S3或数据库层)
  • 使用流行库中的一些现有代码
  • 代码中没有太多毫无意义的时髦或忍者-foo

缺点

  • 像饿河马一样吞下错误
  • 抛出字符串而不是错误(无堆栈跟踪)
  • zip.extract()似乎不起作用(因此我zip.stream()在示例中使用了)

亚军:node-unzipper

安装:

npm install --save unzipper

用法:

'use strict';

var fs = require('fs');
var unzipper = require('unzipper');

fs.createReadStream('./example.zip')
  .pipe(unzipper.Parse())
  .on('entry', function (entry) {
    var fileName = entry.path;
    var type = entry.type; // 'Directory' or 'File'

    console.log();
    if (/\/$/.test(fileName)) {
      console.log('[DIR]', fileName, type);
      return;
    }

    console.log('[FILE]', fileName, type);

    // TODO: probably also needs the security check

    entry.pipe(process.stdout/*fs.createWriteStream('output/path')*/);
    // NOTE: To ignore use entry.autodrain() instead of entry.pipe()
  });

优点

  • 似乎以与相似的方式工作node-stream-zip,但控制较少
  • 更具功能性的 unzip
  • 似乎是串行而不是并行运行

缺点

  • 厨房水槽多少?仅包含大量与解压缩无关的内容
  • 读取整个文件(按块读取,这很好),而不仅仅是随机查找

51
没有人会惊讶于2019年没有内置函数可以将a提取.zip到给定位置吗?
费利佩

2
我尝试了adm-zip和yauzl / yazl。yauzl / yazl supoort较新版本的zip协议,并且没有太多的错误。
Powpow

3
您可能需要补充一点,从技术上讲,流式传输zip是对zip文件的无效使用。zip是在软盘时代制作的,因此可以选择将新文件刷新/添加到现有zip中。假设您的邮递区有3个档案A,B,C。无需编写整个zip pkzip,而只是在文件末尾添加新的A,然后在末尾放置一个新的中央目录。因此,如果您流式传输,将得到2个A文件,其中一个无效。
gman

mkdirp是未定义的!
里卡多·G·萨拉瓦

6
@Felipe Hooray!差不多2021年,在节点中处理zip文件仍然很痛苦!
Runsis

35

Node通过zlib模块内置了对gzip和deflate的支持:

var zlib = require('zlib');

zlib.gunzip(gzipBuffer, function(err, result) {
    if(err) return console.error(err);

    console.log(result);
});

编辑:您甚至可以pipe直接通过Gunzip(例如,使用request)数据:

var request = require('request'),
    zlib = require('zlib'),
    fs = require('fs'),
    out = fs.createWriteStream('out');

// Fetch http://example.com/foo.gz, gunzip it and store the results in 'out'
request('http://example.com/foo.gz').pipe(zlib.createGunzip()).pipe(out);

对于tar档案,有Isaacs的tar模块,npm使用。

编辑2:更新了答案,因为zlib不支持该zip格式。这仅适用于gzip


13
不。这些示例都不起作用。node.js的zlib模块仅用于表示单个资源的流和缓冲区。而不是zip或tar存档。
pyrotechnick 2012年

3
您似乎误解了这个问题。他没有尝试解压缩单个流或单个文件。他正试图从整个档案中提取文件。如您所述:Isaacs的tar模块确实可以与tar一起使用,但是您的zip代码不会从zip存档中提取文件。
pyrotechnick 2012年

14

yauzl是一个强大的解压缩库。设计原则:

  • 遵循规格。不要扫描本地文件头。阅读中央目录以获取文件元数据。
  • 不要阻塞JavaScript线程。使用并提供异步API。
  • 使内存使用情况受到控制。不要尝试一次在RAM中缓冲整个文件。
  • 切勿崩溃(如果使用正确)。不要让格式不正确的zip文件关闭试图捕获错误的客户端应用程序。
  • 捕获不安全的文件名条目。如果zip文件条目的文件名以“ /”或/ [A-Za-z]://开头,或者包含“ ..”路径段或“ \”(根据规范),则会引发错误。

目前有97%的测试覆盖率。


7
已经打包了一个更简单的界面github.com/maxogden/extract-zip
Offirmo

13

我尝试了一些nodejs解压缩库,包括adm-zip和unzip,然后选择了extract-zip,后者是yauzl的包装。似乎是最简单的实现。

https://www.npmjs.com/package/extract-zip

var extract = require('extract-zip')
extract(zipfile, { dir: outputPath }, function (err) {
   // handle err
})

4
+1 extract-zipyauzl似乎比其他人维护得更频繁。撰写本文时,三年之内没有承诺解压缩adm-zip
阿克塞利·帕伦(AkseliPalén)

旁注:您也可以将其用作承诺,而无需承诺。
亚历山大·桑托斯

5

我发现以下方法成功,可与.zip一起使用
(在此处进行简化:无错误检查,仅将所有文件解压缩到当前文件夹)

function DownloadAndUnzip(URL){
    var unzip = require('unzip');
    var http = require('http');
    var request = http.get(URL, function(response) {
        response.pipe(unzip.Extract({path:'./'}))
    });
}

3

我期待了很长时间,没有找到简单的工作示例,但是基于这些答案,我创建了downloadAndUnzip()函数。

用法很简单:

downloadAndUnzip('http://your-domain.com/archive.zip', 'yourfile.xml')
    .then(function (data) {
        console.log(data); // unzipped content of yourfile.xml in root of archive.zip
    })
    .catch(function (err) {
        console.error(err);
    });

这是声明:

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

var downloadAndUnzip = function (url, fileName) {

    /**
     * Download a file
     * 
     * @param url
     */
    var download = function (url) {
        return new Promise(function (resolve, reject) {
            request({
                url: url,
                method: 'GET',
                encoding: null
            }, function (err, response, body) {
                if (err) {
                    return reject(err);
                }
                resolve(body);
            });
        });
    };

    /**
     * Unzip a Buffer
     * 
     * @param buffer
     * @returns {Promise}
     */
    var unzip = function (buffer) {
        return new Promise(function (resolve, reject) {

            var resolved = false;

            var zip = new AdmZip(buffer);
            var zipEntries = zip.getEntries(); // an array of ZipEntry records

            zipEntries.forEach(function (zipEntry) {
                if (zipEntry.entryName == fileName) {
                    resolved = true;
                    resolve(zipEntry.getData().toString('utf8'));
                }
            });

            if (!resolved) {
                reject(new Error('No file found in archive: ' + fileName));
            }
        });
    };


    return download(url)
        .then(unzip);
};

2
这是不可扩展的,因为它使用内存
Vanuan

代码可能要简单得多:const zipEntries = new AdmZip(buffer).getEntries()const output = zipEntries.filter(zipEntry => zipEntry.entryName == fileName).map(zipEntry => zipEntry.getData()。toString(' utf8'))返回output.length> 0?resolve(output):拒绝(新错误('在存档中找不到文件:'+ fileName))
sgracki

0

另一个工作示例:

var zlib = require('zlib');
var tar = require('tar');
var ftp = require('ftp');

var files = [];

var conn = new ftp();
conn.on('connect', function(e) 
{
    conn.auth(function(e) 
    {
        if (e)
        {
            throw e;
        }
        conn.get('/tz/tzdata-latest.tar.gz', function(e, stream) 
        {
            stream.on('success', function() 
            {
                conn.end();

                console.log("Processing files ...");

                for (var name in files)
                {
                    var file = files[name];

                    console.log("filename: " + name);
                    console.log(file);
                }
                console.log("OK")
            });
            stream.on('error', function(e) 
            {
                console.log('ERROR during get(): ' + e);
                conn.end();
            });

            console.log("Reading ...");

            stream
            .pipe(zlib.createGunzip())
            .pipe(tar.Parse())
            .on("entry", function (e) 
            {    
                var filename = e.props["path"];
                console.log("filename:" + filename);
                if( files[filename] == null )
                {
                    files[filename] = "";
                }
                e.on("data", function (c) 
                {
                    files[filename] += c.toString();
                })    
            });
        });
    });
})
.connect(21, "ftp.iana.org");

2
gzip的是不一样的.ZIP
贝尼亚

请注意,tar现在具有tar.Extract([options]),其选项路径为“ / path”,用于保存脚本的下半部分,请参阅npmjs.org/package/tar
domenukk 2014年

0

结帐Gunzip文件

import gunzip from 'gunzip-file';

const unzipAll = async () => {
  try {
    const compFiles = fs.readdirSync('tmp')
    await Promise.all(compFiles.map( async file => {
      if(file.endsWith(".gz")){
        gunzip(`tmp/${file}`, `tmp/${file.slice(0, -3)}`)
      }
    }));
  }
  catch(err) {
    console.log(err)
  }
}

-1

下载并解压缩.tar.gz

const https = require("https");
const tar = require("tar");

https.get("https://url.to/your.tar.gz", function(response) {
  response.pipe(
    tar.x({
      strip: 1,
      C: "some-dir"
    })
  );
});

-3

您也可以使用“ unzip”简单地提取现有的zip文件。它适用于任何大小的文件,您需要将其添加为npm的依赖项。

fs.createReadStream(filePath).pipe(unzip.Extract({path:moveIntoFolder})).on('close', function(){
        //To do after unzip
				callback();
		});


2
请说明什么unzip是内置软件包,或者是否需要npm安装它。
Jerinaw

1
是解压缩包的链接。它不是内置程序包。
Sarath Kumar Rajendran,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.