关于javascript:如何使用Node.js下载文件(不使用第三方库)?

How to download a file with Node.js (without using third-party libraries)?

如何在不使用第三方库的情况下下载node.js文件?

我不需要什么特别的东西。我只想从给定的URL下载一个文件,然后将其保存到给定的目录中。


别忘了处理错误!以下代码基于奥古斯托·罗曼的答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  }).on('error', function(err) { // Handle errors
    fs.unlink(dest); // Delete the file async. (But we don't check the result)
    if (cb) cb(err.message);
  });
};


您可以创建一个HTTP GET请求,并将其response传输到一个可写文件流中:

1
2
3
4
5
6
7
const http = require('http');
const fs = require('fs');

const file = fs.createWriteStream("file.jpg");
const request = http.get("http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg", function(response) {
  response.pipe(file);
});

如果您想支持在命令行上收集信息(比如指定目标文件、目录或URL),请查看类似commander的内容。


正如布兰登·蒂利所说,但有适当的控制流程:

1
2
3
4
5
6
7
8
9
10
11
12
var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);
    });
  });
}

如果不等待finish事件,幼稚的脚本最终可能会得到一个不完整的文件。

编辑:感谢@augusto roman指出,cb应该传递给file.close,而不是显式调用。


说到处理错误,最好还是听听请求错误。我甚至会通过检查响应代码来验证。在这里,只有200个响应代码被认为是成功的,但是其他代码可能是好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const fs = require('fs');
const http = require('http');

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);

    const request = http.get(url, (response) => {
        // check if response is success
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        response.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request error too
    request.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result)
        return cb(err.message);
    });
};

尽管这段代码相对简单,但我还是建议使用请求模块,因为它可以处理更多的协议(hello-https!)它不是由http本地支持的。

应该这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const fs = require('fs');
const request = require('request');

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);
    const sendReq = request.get(url);

    // verify response code
    sendReq.on('response', (response) => {
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        sendReq.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request errors
    sendReq.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result)
        return cb(err.message);
    });
};


gfxmonk的答案在回调和file.close()完成之间有一个非常紧密的数据竞争。file.close()实际上接受一个回调,当关闭完成时调用该回调。否则,立即使用该文件可能会失败(很少!).

完整的解决方案是:

1
2
3
4
5
6
7
8
9
10
11
12
var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  });
}

如果不等待finish事件,幼稚的脚本最终可能会得到一个不完整的文件。如果不通过close调度cb回调,您可能会在访问文件和实际准备好的文件之间出现竞争。


可能node.js已经改变了,但其他解决方案(使用node v8.1.2)似乎有一些问题:

  • finish事件中,您不需要呼叫file.close()。默认情况下,fs.createWriteStream被设置为autoclose:https://nodejs.org/api/fs.html fs createwritestream_path_选项
  • 出错时应调用file.close()。删除文件时,可能不需要这样做(unlink()),但通常是:https://nodejs.org/api/stream.html stream readable _pipe_destination_选项
  • statusCode !== 200上没有删除临时文件
  • 不推荐不带回调的fs.unlink()(输出警告)
  • 如果存在dest文件,它将被重写。
  • 下面是一个改进的解决方案(使用ES6和Promises),用于处理这些问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    const http = require("http");
    const fs = require("fs");

    function download(url, dest) {
        return new Promise((resolve, reject) => {
            const file = fs.createWriteStream(dest, { flags:"wx" });

            const request = http.get(url, response => {
                if (response.statusCode === 200) {
                    response.pipe(file);
                } else {
                    file.close();
                    fs.unlink(dest, () => {}); // Delete temp file
                    reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`);
                }
            });

            request.on("error", err => {
                file.close();
                fs.unlink(dest, () => {}); // Delete temp file
                reject(err.message);
            });

            file.on("finish", () => {
                resolve();
            });

            file.on("error", err => {
                file.close();

                if (err.code ==="EEXIST") {
                    reject("File already exists");
                } else {
                    fs.unlink(dest, () => {}); // Delete temp file
                    reject(err.message);
                }
            });
        });
    }


    对于那些寻求ES6风格的基于承诺的方式的人来说,我想应该是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    var http = require('http');
    var fs = require('fs');

    function pDownload(url, dest){
      var file = fs.createWriteStream(dest);
      return new Promise((resolve, reject) => {
        var responseSent = false; // flag to make sure that response is sent only once.
        http.get(url, response => {
          response.pipe(file);
          file.on('finish', () =>{
            file.close(() => {
              if(responseSent)  return;
              responseSent = true;
              resolve();
            });
          });
        }).on('error', err => {
            if(responseSent)  return;
            responseSent = true;
            reject(err);
        });
      });
    }

    //example
    pDownload(url, fileLocation)
      .then( ()=> console.log('downloaded file no issues...'))
      .catch( e => console.error('error while downloading', e));


    解决方案超时,防止内存泄漏:

    以下代码基于Brandon Tilley的答案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var http = require('http'),
        fs = require('fs');

    var request = http.get("http://example12345.com/yourfile.html", function(response) {
        if (response.statusCode === 200) {
            var file = fs.createWriteStream("copy.html");
            response.pipe(file);
        }
        // Add timeout.
        request.setTimeout(12000, function () {
            request.abort();
        });
    });

    收到错误时不要创建文件,最好在x秒后使用超时关闭请求。


    袁文思的密码很好,但似乎有问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function download(url, dest, callback) {
        var file = fs.createWriteStream(dest);
        var request = http.get(url, function (response) {
            response.pipe(file);
            file.on('finish', function () {
                file.close(callback); // close() is async, call callback after close completes.
            });
            file.on('error', function (err) {
                fs.unlink(dest); // Delete the file async. (But we don't check the result)
                if (callback)
                    callback(err.message);
            });
        });
    }


    您可以使用https://github.com/douzi8/ajax请求下载

    1
    2
    3
    request.download('http://res.m.ctrip.com/html5/Content/images/57.png',
      function(err, res, body) {}
    );


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const download = (url, path) => new Promise((resolve, reject) => {
    http.get(url, response => {
        const statusCode = response.statusCode;

        if (statusCode !== 200) {
            return reject('Download error!');
        }

        const writeStream = fs.createWriteStream(path);
        response.pipe(writeStream);

        writeStream.on('error', () => reject('Error writing to file!'));
        writeStream.on('finish', () => writeStream.close(resolve));
    });}).catch(err => console.error(err));


    如果您使用的是express,请使用res.download()方法。否则fs模块使用。

    1
    2
    3
    4
    app.get('/read-android', function(req, res) {
       var file ="/home/sony/Documents/docs/Android.apk";
        res.download(file)
    });

    (或)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
       function readApp(req,res) {
          var file = req.fileName,
              filePath ="/home/sony/Documents/docs/";
          fs.exists(filePath, function(exists){
              if (exists) {    
                res.writeHead(200, {
                 "Content-Type":"application/octet-stream",
                 "Content-Disposition" :"attachment; filename=" + file});
                fs.createReadStream(filePath + file).pipe(res);
              } else {
                res.writeHead(400, {"Content-Type":"text/plain"});
                res.end("ERROR File does NOT Exists.ipa");
              }
            });  
        }


    使用Promise下载,它可以解析可读的流。添加额外的逻辑来处理重定向。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    var http = require('http');
    var promise = require('bluebird');
    var url = require('url');
    var fs = require('fs');
    var assert = require('assert');

    function download(option) {
        assert(option);
        if (typeof option == 'string') {
            option = url.parse(option);
        }

        return new promise(function(resolve, reject) {
            var req = http.request(option, function(res) {
                if (res.statusCode == 200) {
                    resolve(res);
                } else {
                    if (res.statusCode === 301 && res.headers.location) {
                        resolve(download(res.headers.location));
                    } else {
                        reject(res.statusCode);
                    }
                }
            })
            .on('error', function(e) {
                reject(e);
            })
            .end();
        });
    }

    download('http://localhost:8080/redirect')
    .then(function(stream) {
        try {

            var writeStream = fs.createWriteStream('holyhigh.jpg');
            stream.pipe(writeStream);

        } catch(e) {
            console.error(e);
        }
    });


    路径:img类型:JPG随机唯一性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        function resim(url) {

        var http = require("http");
        var fs = require("fs");
        var sayi = Math.floor(Math.random()*10000000000);
        var uzanti =".jpg";
        var file = fs.createWriteStream("img/"+sayi+uzanti);
        var request = http.get(url, function(response) {
      response.pipe(file);
    });

            return sayi+uzanti;
    }


    嗨,我想您可以使用child_process module和curl命令。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const cp = require('child_process');

    let download = async function(uri, filename){
        let command = `curl -o ${filename}  '${uri}'`;
        let result = cp.execSync(command);
    };


    async function test() {
        await download('http://zhangwenning.top/20181221001417.png', './20181221001417.png')
    }

    test()

    另外,当您想要下载大的、多个文件时,可以使用集群模块来使用更多的CPU内核。


    如果没有图书馆,只需指出一点就可以了。以下是一些:

    • 无法处理HTTP重定向,如此URL https://calibre-ebook.com/dist/portable,它是二进制的。
    • HTTP模块不能https url,你会得到Protocol"https:" not supported.

    我的建议是:

    • 调用系统工具,如wgetcurl
    • 使用一些像node wget promise这样的工具也非常简单。埃多克斯1〔20〕

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    function download(url, dest, cb) {

      var request = http.get(url, function (response) {

        const settings = {
          flags: 'w',
          encoding: 'utf8',
          fd: null,
          mode: 0o666,
          autoClose: true
        };

        // response.pipe(fs.createWriteStream(dest, settings));
        var file = fs.createWriteStream(dest, settings);
        response.pipe(file);

        file.on('finish', function () {
          let okMsg = {
            text: `File downloaded successfully`
          }
          cb(okMsg);
          file.end();
        });
      }).on('error', function (err) { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result)
        let errorMsg = {
          text: `Error in file downloadin: ${err.message}`
        }
        if (cb) cb(errorMsg);
      });
    };


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var fs = require('fs'),
        request = require('request');

    var download = function(uri, filename, callback){
        request.head(uri, function(err, res, body){
        console.log('content-type:', res.headers['content-type']);
        console.log('content-length:', res.headers['content-length']);
        request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);

        });
    };  

    download('https://www.cryptocompare.com/media/19684/doge.png', 'icons/taskks12.png', function(){
        console.log('done');
    });


    您可以尝试使用res.redirect到https文件下载URL,然后它将下载该文件。

    比如:res.redirect('https//static.file.com/file.txt');


    1
    2
    3
    var requestModule=require("request");

    requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));


    我们可以使用下载节点模块,它非常简单,请参阅下面的网址:https://www.npmjs.com/package/download