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


443

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

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


5
“使用node.js下载文件” -是指上传到服务器吗?或使用服务器从远程服务器检索文件?或将文件提供给客户端以从您的node.js服务器下载?
约瑟夫

66
“我只想从给定的URL下载文件,然后将其保存到给定的目录中,”这似乎很清楚。:)
Michelle Tilley 2012年

34
约瑟夫(Joseph)错误地断言所有节点进程都是服务器进程
lededje 2013年

1
@lededje是什么阻止服务器进程下载文件并将其保存到服务器上的目录中?这是完全可行的。
Gherman

Answers:


598

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

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之类的内容


3
运行此脚本时,获得了以下控制台输出: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)
Anderson Green

尝试http.get在行上使用其他网址;也许http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(并替换file.pngfile.jpg)。
米歇尔·提里(Michelle Tilley),

8
脚本结束时,此代码是否会正确关闭文件,否则会丢失数据?
2015年

2
@quantumpotato查看您从请求中得到的答复
Michelle Tilley

6
如果您要求https必须使用req url类型,则取决于它,https否则将引发错误。
Krishnadas PC

523

不要忘记处理错误!以下代码基于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);
  });
};

2
@ vince-yuan download()本身有pipe能力吗?
rasx

@theGrayFox因为此答案中的代码比接受的代码长得多。:)
pootow

2
@Abdul听起来您是node.js / javascript的新手。看一下本教程:tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm这并不复杂。
文斯·袁

1
@Abdul如果与班上其他人分享所发现的东西,也许会很好?
Curtwagner1984

5
有没有办法查看下载速度?像能追踪多少mb / s?谢谢!
蒂诺·卡尔

137

正如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,而不是显式调用。


3
回调使我感到困惑。如果我现在调用download(),我将如何做?我将把什么作为cb论点?我有,download('someURI', '/some/destination', cb)但不知道要放入cb中的内容
阿卜杜勒

1
@Abdul仅当成功获取文件后需要执行某些操作时,才使用函数指定回调。
CatalinBerta,2016年

65

说到处理错误,最好还是听听请求错误。我什至可以通过检查响应代码来验证。在这里,只有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模块,因为它可以处理更多协议(原生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);
    });
};

2
request模块仅可直接用于HTTP。凉!
Thiago C. S Ventura

@ventura是的,顺便说一句,还有本机https模块,现在可以处理安全连接。
Buzut '16

毫无疑问,它更容易出错。无论如何,在任何情况下都可以使用请求模块的情况下,我建议您这样做,因为它是更高级别的,因此更容易有效。
Buzut '16

2
@Alex,不,这是一条错误消息,并且有返回。因此,如果response.statusCode !== 200cb on finish永远不会被调用。
Buzut'2

1
感谢您展示使用请求模块的示例。
皮特·阿尔文,

48

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通过关闭计划回调,则可能会在访问文件和实际准备好文件之间产生竞争。


2
您将请求存储到变量中是什么?
polkovnikov.ph 2014年

他将其“存储”到变量中,因此默认情况下不会成为全局变量。
2015年

@philk您如何知道如果var request =将其删除则创建全局变量?
ma11hew28 '18 -10-29

没错,没有必要保存请求,无论如何它都没有使用。那是你的意思吗?
菲尔克

17

也许node.js已更改,但其他解决方案似乎存在一些问题(使用节点v8.1.2):

  1. 您无需file.close()finish活动中致电。默认情况下,fs.createWriteStream设置为自动关闭:https ://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()应该在错误时调用。删除文件(unlink())时可能不需要这样做,但通常是: https //nodejs.org/api/stream.html#stream_visible_pipe_destination_options
  3. 临时文件未在删除 statusCode !== 200
  4. fs.unlink() 不建议不使用回调(输出警告)
  5. 如果dest文件存在;它被覆盖

下面是处理这些问题的修改后的解决方案(使用ES6和Promise)。

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

1
关于此的两个评论:1)它可能应该拒​​绝错误对象,而不是字符串,2)fs.unlink会悄悄吞下可能不一定要执行的错误
Richard Nienaber

1
这很棒!如果您的网址使用HTTPS,刚刚替补const https = require("https");const http = require("http");
拉斯

15

解决超时问题,防止内存泄漏:

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

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
这只是一个文件,没有协议或服务器可从...下载http.get("http://example.com/yourfile.html",function(){})
mjz19910 '18

此答案是否有内存泄漏:stackoverflow.com/a/22793628/242933
ma11hew28 '18 -10-29

您可以像在一样添加超时http.get。仅当文件花费太长时间无法下载时,才会发生内存泄漏。
A-312 '18

13

对于那些寻求基于es6样式基于promise的方式的人,我想它应该是这样的:

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

2
responseSet出于某些我没有时间进行调查的原因,该标志导致我的文件下载不完整。没有错误弹出,但是我正在填充的.txt文件只有一半的行需要存在。删除标志的逻辑可修复该问题。只是想指出,如果有人对方法有疑问。不过,+ 1
米兰Velebit

6

文斯·袁(Vince Yuan)的代码很棒,但似乎有问题。

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

我们可以指定目标文件夹吗?

6

我更喜欢request(),因为您可以同时使用http和https。

request('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
  .pipe(fs.createWriteStream('cat.jpg'))

看起来Request已过时github.com/request/request/issues/3142 "As of Feb 11th 2020, request is fully deprecated. No new changes are expected to land. In fact, none have landed for some time."
Michael Kubler

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

5

嗨,我认为您可以使用child_process模块和curl命令。

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内核。



4

使用Promise下载,可解决可读流。放置额外的逻辑来处理重定向。

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

1
302还是URL重定向的HTTP状态代码,因此您应该在if语句中使用此[301,302] .indexOf(res.statusCode)!== -1
sidanmor

该问题专门针对不包括第三方模式的问题:)
David Gatti

3

如果使用Express,请使用res.download()方法。否则使用fs模块。

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

(要么)

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

3

✅所以如果你使用管道,它将关闭所有其他流,并确保没有内存泄漏。

工作示例:

const http = require('http');
const { pipeline } = require('stream');
const fs = require('fs');

const file = fs.createWriteStream('./file.jpg');

http.get('http://via.placeholder.com/150/92c952', response => {
  pipeline(
    response,
    file,
    err => {
      if (err)
        console.error('Pipeline failed.', err);
      else
        console.log('Pipeline succeeded.');
    }
  );
});

“ .pipe和.pipeline on stream有什么区别”的答案开始。


2

路径:img类型:jpg随机uniqid

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

0

如果没有库,可能会指出这一点。这里有一些:

这是我的建议:

  • 呼叫系统工具,例如wgetcurl
  • 使用诸如node-wget-promise之类的工具,该工具也非常易于使用。 var wget = require('node-wget-promise'); wget('http://nodejs.org/images/logo.svg');

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

0

您可以尝试使用res.redirecthttps文件下载网址,然后它将下载文件。

喜欢: res.redirect('https//static.file.com/file.txt');


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

0

这是在没有第三方依赖的情况下进行处理并搜索重定向的另一种方法:

        var download = function(url, dest, cb) {
            var file = fs.createWriteStream(dest);
            https.get(url, function(response) {
                if ([301,302].indexOf(response.statusCode) !== -1) {
                    body = [];
                    download(response.headers.location, dest, cb);
                  }
              response.pipe(file);
              file.on('finish', function() {
                file.close(cb);  // close() is async, call cb after close completes.
              });
            });
          }

0

download.js(即/project/utils/download.js)

const fs = require('fs');
const request = require('request');

const download = (uri, filename, callback) => {
    request.head(uri, (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);
    });
};

module.exports = { download };


app.js

... 
// part of imports
const { download } = require('./utils/download');

...
// add this function wherever
download('https://imageurl.com', 'imagename.jpg', () => {
  console.log('done')
});


-4
var requestModule=require("request");

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

5
代码转储通常没有用,可以降低投票权或删除。至少要解释该代码对将来的访问者的作用是值得进行编辑的。
错误
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.