Come scaricare un file con Node.js (senza utilizzare librerie di terze parti)?


443

Come faccio a scaricare un file con Node.js senza utilizzare librerie di terze parti ?

Non ho bisogno di niente di speciale. Voglio solo scaricare un file da un determinato URL e quindi salvarlo in una determinata directory.


5
"scarica un file con node.js" - intendi caricare sul server? o recuperare un file da un server remoto usando il tuo server? o servire un file su un client per il download dal tuo server node.js?
Joseph,

66
"Voglio solo scaricare un file da un determinato URL e quindi salvarlo in una determinata directory", sembra abbastanza chiaro. :)
Michelle Tilley,

34
Joseph sta affermando erroneamente che tutti i processi dei nodi sono processi server
lededje

1
@lededje Cosa impedisce a un processo server di scaricare un file e salvarlo in una directory su un server? È perfettamente fattibile.
Gherman,

Risposte:


598

È possibile creare una GETrichiesta HTTP e reindirizzarla responsein un flusso di file scrivibile:

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

Se desideri supportare la raccolta di informazioni sulla riga di comando, ad esempio specificando un file o una directory di destinazione o un URL, controlla qualcosa come Commander .


3
Ho ottenuto il seguente output della console quando mi sono imbattuto questo script: 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

Prova a utilizzare un URL diverso sulla http.getlinea; forse http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(e sostituirlo file.pngcon file.jpg).
Michelle Tilley,

8
Questo codice chiude correttamente il file al termine dello script o perderebbe i dati?
philk,

2
@quantumpotato Dai un'occhiata alla risposta che stai ricevendo dalla tua richiesta
Michelle Tilley

6
Questo dipende dal tipo di url req se stai richiedendo httpsche devi usare httpsaltrimenti genererà un errore.
Krishnadas PC

523

Non dimenticare di gestire gli errori! Il seguente codice si basa sulla risposta di 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 è esso download()stesso in pipegrado?
Rasx,

@theGrayFox Perché il codice in questa risposta è molto più lungo di quello accettato. :)
Pootow,

2
@Abdul Sembra che tu sia molto nuovo su node.js / javascript. Dai un'occhiata a questo tutorial: tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm Non è complesso.
Vince Yuan

1
@Abdul forse sarebbe bello se condividi con il resto della classe quello che hai capito?
Curtwagner1984,

5
C'è un modo per vedere la velocità del download? Come può tracciare quanti mb / s? Grazie!
Tino Caer,

138

Come diceva Michelle Tilley, ma con il flusso di controllo appropriato:

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

Senza attendere l' finishevento, gli script ingenui potrebbero finire con un file incompleto.

Modifica: Grazie a @Augusto Roman per aver sottolineato che cbdovrebbe essere passato file.close, non chiamato esplicitamente.


3
il callback mi sta confondendo. se ora invoco download(), come lo farei? Cosa collocherei come cbargomento? Ho il download('someURI', '/some/destination', cb)ma non capisco cosa mettere nel cb
Abdul

1
@Abdul Si specifica il callback con una funzione solo se è necessario fare qualcosa quando il file è stato recuperato correttamente.
CatalinBerta,

65

Parlando di errori di gestione, è ancora meglio ascoltare anche gli errori di richiesta. Verificherei anche controllando il codice di risposta. Qui è considerato un successo solo per 200 codici di risposta, ma altri codici potrebbero essere buoni.

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

Nonostante la relativa semplicità di questo codice, consiglierei di usare il modulo di richiesta in quanto gestisce molti più protocolli (ciao HTTPS!) Che non sono supportati nativamente da http.

Sarebbe fatto così:

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
Il modulo di richiesta funziona semplicemente per HTTPs. Freddo!
Thiago C. S Ventura,

@ventura sì, a proposito, c'è anche il modulo https nativo che ora può gestire connessioni sicure.
Buzut,

È più soggetto a errori senza dubbio. Ad ogni modo, in ogni caso in cui l'utilizzo del modulo di richiesta è un'opzione, lo consiglierei perché è di livello molto più alto e quindi, più semplice ed efficiente.
Buzut,

2
@Alex, no, questo è un messaggio di errore e c'è un ritorno. Quindi se response.statusCode !== 200il cb on finishnon verrà mai chiamato.
Buzut,

1
Grazie per aver mostrato un esempio usando il modulo di richiesta.
Pete Alvin,

48

La risposta di gfxmonk ha una corsa di dati molto stretta tra il callback e il file.close()completamento. file.close()accetta effettivamente un callback che viene chiamato al termine della chiusura. Altrimenti, gli usi immediati del file potrebbero non riuscire (molto raramente!).

Una soluzione completa è:

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

Senza attendere l'evento di fine, gli script ingenui potrebbero finire con un file incompleto. Senza pianificare la cbrichiamata alla chiusura, è possibile ottenere una corsa tra l'accesso al file e il file effettivamente pronto.


2
Per cosa stai memorizzando la richiesta in una variabile?
polkovnikov.ph,

lo "archivia" in una variabile in modo che non diventi una variabile globale per impostazione predefinita.
philk

@philk come fai a sapere se var request =viene rimossa una variabile globale ?
ma11hew28,

Hai ragione, non è necessario salvare la richiesta, non viene comunque utilizzata. Questo è quello che vuoi dire?
philk

17

Forse node.js è cambiato, ma sembra che ci siano alcuni problemi con le altre soluzioni (usando il nodo v8.1.2):

  1. Non è necessario chiamare file.close()in finishevento. Per impostazione predefinita, fs.createWriteStreamè impostato su AutoClose: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()dovrebbe essere chiamato in caso di errore. Forse questo non è necessario quando il file viene eliminato ( unlink()), ma normalmente lo è: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. Il file temporaneo non è stato cancellato statusCode !== 200
  4. fs.unlink() senza callback è deprecato (avviso output)
  5. Se il destfile esiste; è ignorato

Di seguito è una soluzione modificata (utilizzando ES6 e promesse) che gestisce questi problemi.

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
Due commenti su questo: 1) probabilmente dovrebbe rifiutare oggetti Error, non stringhe, 2) fs.unlink inghiottirà tranquillamente errori che potrebbero non essere necessariamente quello che vuoi fare
Richard Nienaber

1
Funziona benissimo! E se i tuoi URL utilizzano HTTPS, basta sostituire const https = require("https");perconst http = require("http");
Russ

15

Soluzione con timeout, prevenire la perdita di memoria:

Il seguente codice si basa sulla risposta di 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();
    });
});

Non creare file quando viene visualizzato un errore e preferisci utilizzare il timeout per chiudere la richiesta dopo X secondi.


1
questo è solo un file, non ha protocollo o server da cui scaricare ...http.get("http://example.com/yourfile.html",function(){})
mjz19910

C'è una perdita di memoria in questa risposta: stackoverflow.com/a/22793628/242933 ?
ma11hew28,

Puoi aggiungere il timeout come ho fatto io http.get. La perdita di memoria è solo se il download del file richiede troppo tempo.
A-312,

13

per coloro che sono venuti alla ricerca di un modo basato sulla promessa in stile es6, immagino che sarebbe qualcosa del tipo:

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
responseSetflag ha causato, per qualche motivo che non avevo avuto il tempo di investigare, che il mio file fosse scaricato in modo incompleto. Non sono stati rilevati errori, ma il file .txt che stavo popolando aveva la metà delle righe che dovevano essere lì. Rimozione della logica per il flag risolto. Volevo solo far notare che qualcuno aveva i problemi con l'approccio. Ancora, +1
Milan Velebit

6

Il codice di Vince Yuan è fantastico ma sembra essere qualcosa di sbagliato.

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

possiamo specificare la cartella di destinazione?

6

Preferisco request () perché puoi usare sia http che https con esso.

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

Sembra che la richiesta sia stata deprecata 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

Ciao , Penso che puoi usare il modulo child_process e il comando 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()

Inoltre , quando vuoi scaricare grandi 、 più file , puoi usare il modulo cluster per usare più core cpu.



4

Scarica usando promise, che risolve un flusso leggibile. mettere una logica extra per gestire il reindirizzamento.

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 è anche un codice di stato HTTP per il reindirizzamento degli URL, quindi dovresti usare questo [301.302] .indexOf (res.statusCode)! == -1
nell'istruzione

Le domande erano specifiche per non includere le modalità di terze parti :)
David Gatti il

3

Se si utilizza express, utilizzare il metodo res.download (). altrimenti uso del modulo fs.

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

(o)

   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

O Quindi, se si utilizza la pipeline , chiuderebbe tutti gli altri flussi e si assicurerebbe che non vi siano perdite di memoria.

Esempio funzionante:

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

Dalla mia risposta a "Qual è la differenza tra .pipe e .pipeline su stream" .


2

Percorso: tipo 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;
}

0

Senza biblioteca potrebbe essere difettoso solo per sottolineare. Eccone alcuni:

  • Impossibile gestire il reindirizzamento http, come questo URL https://calibre-ebook.com/dist/portable che è binario.
  • Il modulo http non può https url, otterrai Protocol "https:" not supported.

Ecco il mio suggerimento:

  • Chiama lo strumento di sistema come wgetocurl
  • usa alcuni strumenti come node-wget-promise che è anche molto semplice da usare. 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

Puoi provare a utilizzare res.redirectl'URL di download del file https, quindi scaricherà il file.

Piace: 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

Ecco ancora un altro modo per gestirlo senza dipendenze di terze parti e anche cercare reindirizzamenti:

        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 (es. /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
I dump del codice non sono generalmente utili e possono essere ridimensionati o eliminati. Vale la pena modificare almeno per spiegare cosa sta facendo il codice per i futuri visitatori.
Bug
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.