Làm cách nào để tải xuống tệp bằng Node.js (không sử dụng thư viện của bên thứ ba)?


443

Làm cách nào để tôi tải xuống một tệp với Node.js mà không cần sử dụng thư viện của bên thứ ba ?

Tôi không cần bất cứ điều gì đặc biệt. Tôi chỉ muốn tải xuống một tệp từ một URL nhất định, sau đó lưu nó vào một thư mục nhất định.


5
"tải xuống một tệp với node.js" - bạn có nghĩa là tải lên máy chủ? hoặc lấy một tập tin từ một máy chủ từ xa bằng máy chủ của bạn? hoặc phục vụ một tệp cho khách hàng để tải xuống từ máy chủ node.js của bạn?
Giuse

66
"Tôi chỉ muốn tải xuống một tệp từ một url nhất định, sau đó lưu nó vào một thư mục nhất định", có vẻ như khá rõ ràng. :)
Michelle Tilley

34
Joseph đang đưa ra một xác nhận không chính xác rằng tất cả các quy trình nút là quy trình máy chủ
lededje

1
@lededje Điều gì ngăn quá trình máy chủ tải xuống tệp và lưu tệp vào thư mục trên máy chủ? Đó là hoàn toàn có thể làm được.
Gherman

Câu trả lời:


598

Bạn có thể tạo một GETyêu cầu HTTP và chuyển nó responsethành một luồng tệp có thể ghi:

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

Nếu bạn muốn hỗ trợ thu thập thông tin trên dòng lệnh - như chỉ định tệp mục tiêu hoặc thư mục hoặc URL - hãy kiểm tra một cái gì đó như Commander .


3
Tôi đã nhận được đầu ra giao diện điều khiển sau đây khi tôi chạy tập lệnh này : 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

Hãy thử sử dụng một URL khác trên http.getdòng; có thể http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(và thay thế file.pngbằng file.jpg).
Michelle Tilley

8
Liệu mã này đóng tập tin đúng cách khi tập lệnh kết thúc hoặc nó sẽ mất dữ liệu?
philk

2
@quantumpotato Hãy xem phản hồi bạn nhận được từ yêu cầu của bạn
Michelle Tilley

6
Điều này phụ thuộc vào loại url req nếu bạn yêu cầu httpsbạn phải sử dụng httpsnếu không nó sẽ gây ra lỗi.
PC của Krishnadas

523

Đừng quên xử lý lỗi! Các mã sau đây được dựa trên câu trả lời của 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-nhân dân tệ là download()chính nó pipecó thể?
rasx

@theGrayFox Bởi vì mã trong câu trả lời này dài hơn mã được chấp nhận. :)
pootow

2
@Abdul Có vẻ như bạn rất mới với node.js / javascript. Hãy xem hướng dẫn này: guidespoint.com/nodejs/nodejs_callbacks_concept.htmlm Nó không phức tạp.
Vince Yuan

1
@Abdul có lẽ sẽ tốt nếu bạn chia sẻ với những người còn lại trong lớp những gì bạn đã tìm ra?
Curtwagner1984

5
Có cách nào để xem tốc độ tải xuống không? Giống như có thể theo dõi bao nhiêu mb / s? Cảm ơn!
Tino Caer

137

Như Michelle Tilley đã nói, nhưng với dòng điều khiển thích hợp:

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

Không chờ đợi finishsự kiện, các kịch bản ngây thơ có thể kết thúc với một tệp không đầy đủ.

Chỉnh sửa: Cảm ơn @Augusto Roman đã chỉ ra rằng cbnên được chuyển đến file.close, không được gọi một cách rõ ràng.


3
cuộc gọi lại làm tôi bối rối Nếu bây giờ tôi gọi download(), tôi sẽ làm thế nào? Tôi sẽ đặt điều gì làm cbđối số? Tôi có download('someURI', '/some/destination', cb)nhưng không hiểu nên đặt gì vào cb
Abdul

1
@Abdul Bạn chỉ định gọi lại bằng một hàm chỉ khi bạn cần làm gì đó khi tệp đã được tìm nạp thành công.
CatalinBerta

65

Nói về xử lý lỗi, nó thậm chí còn tốt hơn để nghe lỗi yêu cầu. Tôi thậm chí sẽ xác nhận bằng cách kiểm tra mã phản hồi. Ở đây, nó được coi là thành công chỉ với 200 mã phản hồi, nhưng các mã khác có thể tốt.

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

Mặc dù sự đơn giản tương đối của mã này, tôi khuyên bạn nên sử dụng mô-đun yêu cầu vì nó xử lý nhiều giao thức hơn (xin chào HTTPS!) Mà không được hỗ trợ bởi http.

Điều đó sẽ được thực hiện như vậy:

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
Mô-đun yêu cầu chỉ hoạt động trực tiếp cho HTTP. Mát mẻ!
Thiago C. S Ventura

@ventura yep, btw, cũng có mô-đun https gốc có thể xử lý các kết nối an toàn.
Buzut

Không còn nghi ngờ gì nữa. Dù sao, trong mọi trường hợp sử dụng mô-đun yêu cầu là một tùy chọn, tôi khuyên bạn nên sử dụng cấp độ cao hơn và do đó, dễ dàng và hiệu quả hơn.
Buzut

2
@Alex, không, đây là một thông báo lỗi và có sự trở lại. Vì vậy, nếu response.statusCode !== 200cb trên finishsẽ không bao giờ được gọi.
Buzut

1
Cảm ơn bạn đã hiển thị ví dụ bằng cách sử dụng mô-đun yêu cầu.
Pete Alvin

48

Câu trả lời của gfxmonk có một cuộc đua dữ liệu rất chặt chẽ giữa cuộc gọi lại và việc file.close()hoàn thành. file.close()thực sự có một cuộc gọi lại được gọi khi đóng xong. Nếu không, việc sử dụng tập tin ngay lập tức có thể thất bại (rất hiếm khi!).

Một giải pháp hoàn chỉnh là:

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

Không chờ đợi sự kiện kết thúc, các kịch bản ngây thơ có thể kết thúc với một tệp không đầy đủ. Nếu không lập lịch cbgọi lại thông qua đóng, bạn có thể có một cuộc đua giữa việc truy cập tệp và tệp thực sự đã sẵn sàng.


2
Điều gì cho bạn đang lưu trữ yêu cầu vào một biến?
polkovnikov.ph

anh ta "lưu trữ" nó thành một biến để nó không trở thành biến toàn cục theo mặc định.
philk

@philk làm thế nào để bạn biết một biến toàn cục được tạo nếu var request =bị xóa?
ma11hew28

Bạn nói đúng, không cần phải lưu yêu cầu, dù sao nó cũng không được sử dụng. Đó là những gì bạn nghĩ?
philk

17

Có thể node.js đã thay đổi, nhưng có vẻ như có một số vấn đề với các giải pháp khác (sử dụng nút v8.1.2):

  1. Bạn không cần phải gọi file.close()trong finishsự kiện. Mỗi mặc định, fs.createWriteStreamnó được đặt thành tự động Đóng: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()nên được gọi là lỗi. Có thể điều này không cần thiết khi tệp bị xóa ( unlink()), nhưng thông thường nó là: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. Tập tin tạm thời không bị xóa trên statusCode !== 200
  4. fs.unlink() không có cuộc gọi lại bị phản đối (cảnh báo đầu ra)
  5. Nếu desttập tin tồn tại; nó bị ghi đè

Dưới đây là một giải pháp sửa đổi (sử dụng ES6 và các lời hứa) xử lý các vấn đề này.

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
Hai nhận xét về điều này: 1) có lẽ nên từ chối các đối tượng Lỗi, không phải chuỗi, 2) fs.unlink sẽ lặng lẽ nuốt các lỗi có thể không nhất thiết là điều bạn muốn làm
Richard Nienaber

1
Điều này làm việc tuyệt vời! Và nếu URL của bạn sử dụng HTTPS, chỉ cần thay thế const https = require("https");choconst http = require("http");
Russ

15

Giải pháp hết thời gian chờ, ngăn rò rỉ bộ nhớ:

Đoạn mã sau dựa trên câu trả lời của 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();
    });
});

Không tạo tệp khi bạn gặp lỗi và trước khi sử dụng thời gian chờ để đóng yêu cầu của bạn sau X giây.


1
đây chỉ là một tệp, không có giao thức hoặc máy chủ để tải xuống từ ...http.get("http://example.com/yourfile.html",function(){})
mjz19910

Có một rò rỉ bộ nhớ trong câu trả lời này: stackoverflow.com/a/22793628/242933 ?
ma11hew28

Bạn có thể thêm thời gian chờ như tôi đã làm http.get. Việc rò rỉ bộ nhớ chỉ khi tập tin mất quá nhiều thời gian để tải xuống.
A-312

13

Đối với những người tìm kiếm theo cách hứa hẹn theo kiểu es6, tôi đoán nó sẽ giống như:

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
responseSetcờ gây ra, vì một số lý do mà tôi đã không có thời gian để điều tra, tệp của tôi sẽ được tải xuống không đầy đủ. Không có lỗi nào xuất hiện nhưng tệp .txt tôi đang điền có một nửa số hàng cần có ở đó. Loại bỏ logic cho cờ đã sửa nó. Chỉ muốn chỉ ra rằng nếu ai đó có vấn đề với cách tiếp cận. Tuy nhiên, +1
Milan Velebit

6

Mã của Vince Yuan rất tuyệt nhưng dường như có gì đó không đúng.

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

chúng ta có thể chỉ định thư mục đích?

6

Tôi thích request () vì bạn có thể sử dụng cả http và https với nó.

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

Có vẻ như Yêu cầu đã bị phản đối 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

Xin chào Tôi nghĩ rằng bạn có thể sử dụng mô đun child_ process và lệnh 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()

Ngoài ra, khi bạn muốn tải xuống nhiều tệp, bạn có thể sử dụng mô-đun cụm để sử dụng nhiều lõi cpu hơn.


4

Bạn có thể sử dụng https://github.com/douzi8/ajax-request#doad

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

2
Nó đang trả về ký tự rác nếu tên tệp không phải là ascii như nếu tên tệp là tiếng Nhật.
Deepak Goel

4
Bạn có nghĩ rằng ajax-requestkhông phải là một thư viện bên thứ ba?
Murat Çorlu

4

Tải xuống bằng lời hứa, giải quyết một luồng có thể đọc được. đặt thêm logic để xử lý chuyển hướng.

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 cũng là mã trạng thái HTTP để chuyển hướng URL, vì vậy bạn nên sử dụng [301,302] .indexOf (res.statusCode)! == -1 trong câu lệnh if
sidanmor

Các câu hỏi cụ thể không bao gồm các chế độ của bên thứ ba :)
David Gatti

3

Nếu bạn đang sử dụng phương thức express, hãy sử dụng phương thức res.doad (). mặt khác sử dụng mô đun fs.

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

(hoặc là)

   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

OVậy nếu bạn sử dụng đường ống , nó sẽ đóng tất cả các luồng khác và đảm bảo rằng không có rò rỉ bộ nhớ.

Ví dụ làm việc:

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

Từ câu trả lời của tôi cho "Sự khác biệt giữa .pipe và .pipeline trên luồng" .


2

Đường dẫn: img loại: jpg ngẫu nhiên 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

Không có thư viện, nó có thể bị lỗi chỉ để chỉ ra. Ở đây có một ít:

  • Không thể xử lý chuyển hướng http, như url này https://calibre-ebook.com/dist/portable là nhị phân.
  • Mô-đun http không thể https url, bạn sẽ nhận được Protocol "https:" not supported.

Đây là gợi ý của tôi:

  • Gọi công cụ hệ thống như wgethoặccurl
  • sử dụng một số công cụ như nút-wget-lời hứa cũng rất đơn giản để sử dụng. 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

Bạn có thể thử sử dụng res.redirecturl tải xuống tệp https và sau đó nó sẽ tải xuống tệp.

Giống: 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

Đây là một cách khác để xử lý nó mà không cần sự phụ thuộc của bên thứ 3 và cũng đang tìm kiếm các chuyển hướng:

        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 (tức là /project/utils/doad.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
Các bãi mã thường không hữu ích và có thể bị hạ cấp hoặc bị xóa. Nó sẽ có giá trị chỉnh sửa để ít nhất giải thích những gì mã đang làm cho khách truy cập trong tương lai.
Lỗi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.