Bagaimana cara mengunduh file dengan Node.js (tanpa menggunakan perpustakaan pihak ketiga)?

443

Bagaimana cara mengunduh file dengan Node.js tanpa menggunakan perpustakaan pihak ketiga ?

Saya tidak butuh sesuatu yang istimewa. Saya hanya ingin mengunduh file dari URL yang diberikan, dan kemudian menyimpannya ke direktori yang diberikan.

greepow
sumber
5
"unduh file dengan node.js" - maksud Anda mengunggah ke server? atau mengambil file dari server jauh menggunakan server Anda? atau melayani file ke klien untuk diunduh dari server node.js Anda?
Joseph
66
"Saya hanya ingin mengunduh file dari url yang diberikan, dan kemudian menyimpannya ke direktori yang diberikan," tampaknya cukup jelas. :)
Michelle Tilley
34
Joseph membuat pernyataan yang salah bahwa semua proses simpul adalah proses server
lededje
1
@lededje Apa yang mencegah proses server mengunduh file dan menyimpannya ke direktori di server? Itu bisa dilakukan secara prefect.
Gherman

Jawaban:

598

Anda dapat membuat GETpermintaan HTTP dan responsemengirimkannya ke aliran file yang dapat ditulisi:

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

Jika Anda ingin mendukung pengumpulan informasi di baris perintah - seperti menentukan file target atau direktori, atau URL - lihat sesuatu seperti Komandan .

Michelle Tilley
sumber
3
Aku punya konsol output berikut ketika aku berlari script ini: 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
Coba gunakan URL lain di http.gettelepon; mungkin http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(dan ganti file.pngdengan file.jpg).
Michelle Tilley
8
Apakah kode ini menutup file dengan benar ketika skrip berakhir atau apakah akan kehilangan data?
philk
2
@quantumpotato Lihatlah respons yang Anda dapatkan dari permintaan Anda
Michelle Tilley
6
Ini tergantung pada jenis url req jika Anda meminta httpsAnda harus menggunakan httpsselain itu akan menimbulkan kesalahan.
Krishnadas PC
523

Jangan lupa untuk menangani kesalahan! Kode berikut didasarkan pada jawaban 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);
  });
};
Vince Yuan
sumber
2
@ vince-yuan download()sendiri pipebisa?
rasx
@theGrayFox Karena kode dalam jawaban ini jauh lebih lama daripada yang diterima. :)
pootow
2
@Abdul Sepertinya Anda masih baru di node.js / javascript. Lihatlah tutorial ini: tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm Ini tidak rumit.
Vince Yuan
1
@Abdul mungkin akan lebih baik jika Anda berbagi dengan seluruh kelas apa yang sudah Anda ketahui?
Curtwagner1984
5
Apakah ada cara untuk melihat kecepatan unduhan? Suka bisa melacak berapa mb / s? Terima kasih!
Tino Caer
137

Seperti yang dikatakan Michelle Tilley, tetapi dengan aliran kontrol yang sesuai:

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

Tanpa menunggu finishacara, skrip naif dapat berakhir dengan file yang tidak lengkap.

Sunting: Terima kasih kepada @Augusto Roman karena menunjukkan yang cbharus diteruskan file.close, tidak dipanggil secara eksplisit.

gfxmonk
sumber
3
panggilan balik itu membingungkan saya. jika sekarang saya meminta download(), bagaimana saya melakukannya? Apa yang akan saya tempatkan sebagai cbargumen? Saya punya download('someURI', '/some/destination', cb)tetapi tidak mengerti apa yang harus dimasukkan ke dalam cb
Abdul
1
@ Abdul Anda menentukan panggilan balik dengan fungsi hanya jika Anda perlu melakukan sesuatu ketika file telah berhasil diambil.
CatalinBerta
65

Berbicara tentang penanganan kesalahan, lebih baik mendengarkan permintaan kesalahan juga. Saya bahkan akan memvalidasi dengan memeriksa kode respons. Di sini dianggap berhasil hanya untuk 200 kode respons, tetapi kode lain mungkin baik.

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

Terlepas dari kesederhanaan relatif dari kode ini, saya akan menyarankan untuk menggunakan modul permintaan karena menangani lebih banyak protokol (halo HTTPS!) Yang tidak didukung secara native oleh http.

Itu akan dilakukan seperti itu:

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);
    });
};
Buzut
sumber
2
Modul permintaan hanya berfungsi langsung untuk HTTPs. Keren!
Thiago C. S Ventura
@ventura ya, btw, ada juga modul https asli yang sekarang dapat menangani koneksi yang aman.
Buzut
Ini lebih rentan kesalahan tanpa keraguan. Bagaimanapun, dalam hal apa pun di mana menggunakan modul permintaan adalah sebuah pilihan, saya akan menyarankannya karena tingkatnya jauh lebih tinggi dan dengan demikian, lebih mudah dan efisien.
Buzut
2
@Alex, tidak, ini adalah pesan kesalahan dan ada balasannya. Jadi jika response.statusCode !== 200cb on finishtidak akan pernah dipanggil.
Buzut
1
Terima kasih telah menunjukkan contoh menggunakan modul permintaan.
Pete Alvin
48

Jawaban gfxmonk memiliki ras data yang sangat ketat antara panggilan balik dan file.close()penyelesaian. file.close()sebenarnya membutuhkan callback yang dipanggil saat penutupan telah selesai. Jika tidak, penggunaan langsung file mungkin gagal (sangat jarang!).

Solusi lengkap adalah:

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

Tanpa menunggu acara selesai, skrip naif mungkin berakhir dengan file yang tidak lengkap. Tanpa menjadwalkan cbpanggilan balik melalui penutupan, Anda mungkin mendapatkan perlombaan antara mengakses file dan file yang sebenarnya sudah siap.

Augusto Roman
sumber
2
Untuk apa Anda menyimpan permintaan ke variabel?
polkovnikov.ph
ia "menyimpan" ke dalam variabel sehingga tidak menjadi variabel global secara default.
philk
@ philk bagaimana Anda tahu variabel global dibuat jika var request =dihapus?
ma11hew28
Anda benar, tidak perlu menyimpan permintaan, itu tidak digunakan pula. Itu yang kamu maksud?
philk
17

Mungkin node.js telah berubah, tetapi tampaknya ada beberapa masalah dengan solusi lain (menggunakan simpul v8.1.2):

  1. Anda tidak perlu menelepon file.close()di finishacara tersebut. Per default fs.createWriteStreamdisetel ke autoClose: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()harus dipanggil kesalahan. Mungkin ini tidak diperlukan saat file dihapus ( unlink()), tetapi biasanya ini: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. File temp tidak dihapus pada statusCode !== 200
  4. fs.unlink() tanpa panggilan balik sudah usang (peringatan keluaran)
  5. Jika destada file; itu ditimpa

Di bawah ini adalah solusi yang dimodifikasi (menggunakan ES6 dan janji-janji) yang menangani masalah ini.

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);
            }
        });
    });
}
Bjarke Pjedsted
sumber
1
Dua komentar tentang ini: 1) itu mungkin harus menolak objek Kesalahan, bukan string, 2) fs.unlink diam-diam akan menelan kesalahan yang mungkin belum tentu menjadi apa yang ingin Anda lakukan
Richard Nienaber
1
Ini sangat bagus! Dan jika URL Anda menggunakan HTTPS, hanya pengganti const https = require("https");untukconst http = require("http");
Russ
15

Solusi dengan batas waktu, mencegah kebocoran memori:

Kode berikut didasarkan pada jawaban 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();
    });
});

Jangan membuat file saat Anda mendapatkan kesalahan, dan lebih suka menggunakan batas waktu untuk menutup permintaan Anda setelah X detik.

A-312
sumber
1
ini hanya sebuah file, tidak memiliki protokol atau server untuk diunduh dari ...http.get("http://example.com/yourfile.html",function(){})
mjz19910
Apakah ada kebocoran memori dalam jawaban ini: stackoverflow.com/a/22793628/242933 ?
ma11hew28
Anda dapat menambahkan batas waktu seperti yang saya lakukan di http.get. Kebocoran memori hanya jika file terlalu lama untuk diunduh.
A-312
13

bagi mereka yang datang mencari cara berbasis gaya es6 janji, saya kira itu akan menjadi seperti:

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));
mido
sumber
2
responseSetBendera disebabkan, untuk beberapa alasan yang saya tidak punya waktu untuk menyelidiki, file saya untuk diunduh tidak lengkap. Tidak ada kesalahan muncul tetapi file .txt yang saya isi memiliki setengah dari baris yang perlu ada di sana. Menghapus logika untuk bendera memperbaikinya. Hanya ingin menunjukkan itu jika seseorang memiliki masalah dengan pendekatan tersebut. Tetap saja, +1
Milan Velebit
6

Kode Vince Yuan bagus tetapi tampaknya ada sesuatu yang salah.

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);
        });
    });
}
Rasakan Fisika
sumber
dapatkah kita menentukan folder tujuan?
6

Saya lebih suka request () karena Anda bisa menggunakan http dan https.

request('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
  .pipe(fs.createWriteStream('cat.jpg'))
mixdev
sumber
Sepertinya Permintaan telah ditinggalkan 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));
kayz1
sumber
5

Hai, saya pikir Anda dapat menggunakan modul child_process dan perintah 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()

Selain itu, ketika Anda ingin mengunduh 、 banyak file besar, Anda dapat menggunakan modul cluster untuk menggunakan lebih banyak core cpu.

Wenningzhang
sumber
4

Anda dapat menggunakan https://github.com/douzi8/ajax-request#download

request.download('http://res.m.ctrip.com/html5/Content/images/57.png', 
  function(err, res, body) {}
);
douzi
sumber
2
Ini mengembalikan karakter sampah jika nama file selain ascii seperti jika nama file dalam bahasa Jepang.
Deepak Goel
4
Apakah Anda pikir ajax-requestitu bukan perpustakaan pihak ketiga?
Murat Çorlu
4

Unduh menggunakan janji, yang menyelesaikan aliran yang dapat dibaca. masukkan logika ekstra untuk menangani pengalihan.

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);
    }
});
wdanxna
sumber
1
302 juga merupakan kode status HTTP untuk pengalihan URL, jadi Anda harus menggunakan ini [301.302] .indexOf (res.statusCode)! == -1 dalam pernyataan if
sidanmor
Pertanyaan-pertanyaan khusus untuk tidak termasuk mode pihak ketiga :)
David Gatti
3

Jika Anda menggunakan metode express use res.download (). jika tidak, gunakan modul fs.

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

(atau)

   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");
          }
        });  
    }
KARTHIKEYAN.A
sumber
3

OJadi jika Anda menggunakan pipa , itu akan menutup semua aliran lainnya dan memastikan bahwa tidak ada kebocoran memori.

Contoh kerja:

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

Dari jawaban saya untuk "Apa perbedaan antara .pipe dan .pipeline pada stream" .

Idan Dagan
sumber
2

Path: tipe img: jpg random 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;
}
databilim
sumber
0

Tanpa perpustakaan itu bisa buggy hanya untuk menunjukkan. Berikut ini beberapa di antaranya:

  • Tidak dapat menangani pengalihan http, seperti url ini https://calibre-ebook.com/dist/portable yang merupakan biner.
  • Modul http tidak dapat https url, Anda akan mendapatkannya Protocol "https:" not supported.

Di sini saran saya:

  • Panggil alat sistem seperti wgetataucurl
  • gunakan beberapa alat seperti simpul-wget-janji yang juga sangat mudah digunakan. var wget = require('node-wget-promise'); wget('http://nodejs.org/images/logo.svg');
Geng Jiawen
sumber
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);
  });
};
Alex Pilugin
sumber
0

Anda dapat mencoba menggunakan res.redirecturl unduhan file https, dan kemudian akan mengunduh file tersebut.

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

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

Inilah cara lain untuk menanganinya tanpa ketergantungan pihak ketiga dan juga mencari pengalihan:

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

download.js (yaitu /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')
});
williamsi
sumber
-3

Kita dapat menggunakan modul simpul unduhan dan sangat sederhana, silakan lihat di bawah ini https://www.npmjs.com/package/download

Iyyappan Subramani
sumber
2
Pertanyaannya adalah bertanya bagaimana cara melakukannya "tanpa menggunakan perpustakaan pihak ketiga".
ma11hew28
-4
var requestModule=require("request");

requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));
Chandrakant Thakkar
sumber
5
Pembuangan kode pada umumnya tidak berguna dan mungkin diturunkan atau dihapus. Sebaiknya edit untuk setidaknya menjelaskan apa yang dilakukan kode untuk pengunjung masa depan.
Bug