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

IT技术 javascript node.js download fs
2021-01-22 14:30:10

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

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

6个回答

您可以创建一个 HTTPGET请求并将其response通过管道传输到一个可写的文件流中:

const http = require('http'); // or 'https' for https:// URLs
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这样的东西

我得到了以下控制台输出,当我跑这个脚本:node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ Error: connect ECONNREFUSED at errnoException (net.js:646:11) at Object.afterConnect [as oncomplete] (net.js:637:18)
2021-03-14 14:30:10
@EthanKeiley 为什么你说它没有正确关闭?默认情况下createWriteStream将设置autoClosetrue在可读结束时readable.pipe调用end()可写。
2021-03-14 14:30:10
如果您请求https必须使用,这取决于 req url 类型,https否则会抛出错误。
2021-03-18 14:30:10
当脚本结束时,这段代码是否正确关闭了文件,否则会丢失数据?
2021-03-22 14:30:10
@quantumpotato 看看你从你的请求中得到的回应
2021-04-04 14:30:10

不要忘记处理错误!以下代码基于 Augusto Roman 的回答。

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);
  });
};
@Abdul 如果您与班上的其他人分享您的想法,也许会更好?
2021-03-11 14:30:10
有没有办法查看下载速度?比如可以跟踪多少mb/s?谢谢!
2021-03-19 14:30:10
@Abdul 听起来你对 node.js/javascript 很陌生。看看这个教程:tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm并不复杂。
2021-03-24 14:30:10
@vince-yuandownload()本身就pipe可以吗?
2021-03-26 14:30:10
@VinceYuan 回调让我很困惑。如果我现在调用download(),我会怎么做?我会把什么作为cb论点?我有download('someURI', '/some/destination', cb)但不明白在 cb 中放什么
2021-04-06 14:30:10

正如 Michelle Tilley 所说,但使用适当的控制流程:

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

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

如果不等待finish事件,幼稚的脚本可能会以不完整的文件结束。

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

@Abdul 仅当成功获取文件后需要执行某些操作时,才使用函数指定回调。
2021-03-12 14:30:10
回调让我很困惑。如果我现在调用download(),我会怎么做?我会把什么作为cb论点?我有download('someURI', '/some/destination', cb)但不明白在 cb 中放什么
2021-03-29 14:30:10
在保存之前检查状态代码会很好: response.statusCode == 200
2021-03-30 14:30:10

说到处理错误,听请求错误甚至更好。我什至会通过检查响应代码来验证。这里仅对 200 响应代码认为成功,但其他代码可能很好。

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

尽管这段代码相对简单,我还是建议使用request module,因为它可以处理更多的协议(你好 HTTPS!),而http.

这样做会像这样:

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);
    });
};
毫无疑问,它更容易出错。无论如何,在使用请求module是一种选择的任何情况下,我都会建议它,因为它的级别更高,因此更容易和高效。
2021-03-17 14:30:10
@ventura 是的,顺便说一句,还有现在可以处理安全连接的本机httpsmodule。
2021-03-19 14:30:10
@Alex,不,这是一条错误消息,并且有返回。所以如果response.statusCode !== 200cb onfinish永远不会被调用。
2021-03-22 14:30:10
感谢您展示使用请求module的示例。
2021-03-28 14:30:10
request module直接适用于 HTTPs。凉爽的!
2021-03-30 14:30:10

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

一个完整的解决方案是:

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

在不等待完成事件的情况下,幼稚的脚本可能会以不完整的文件结束。如果不cb通过 close安排回调,您可能会在访问文件和文件实际准备好之间发生竞争。

@philk 你怎么知道如果var request =删除了一个全局变量
2021-03-10 14:30:10
@philk 谢谢。是的,我认为这就是polkovnikov.ph 的意思。
2021-03-18 14:30:10
你是对的,没有必要保存请求,反正它没有被使用。这就是你的意思?
2021-03-26 14:30:10
他将它“存储”到一个变量中,因此默认情况下它不会成为全局变量。
2021-04-03 14:30:10
您将请求存储到变量中有什么用?
2021-04-06 14:30:10