Comment télécharger un fichier avec Node.js (sans utiliser de bibliothèques tierces)?

443

Comment télécharger un fichier avec Node.js sans utiliser de bibliothèques tierces ?

Je n'ai besoin de rien de spécial. Je souhaite uniquement télécharger un fichier à partir d'une URL donnée, puis l'enregistrer dans un répertoire donné.

Greepow
la source
5
"télécharger un fichier avec node.js" - voulez-vous dire télécharger sur le serveur? ou récupérer un fichier à partir d'un serveur distant en utilisant votre serveur? ou servir un fichier à un client pour le télécharger à partir de votre serveur node.js?
Joseph
67
"Je veux seulement télécharger un fichier à partir d'une URL donnée, puis l'enregistrer dans un répertoire donné", cela semble assez clair. :)
Michelle Tilley
34
Joseph fait une affirmation incorrecte que tous les processus de noeud sont des processus serveur
lededje
1
@lededje Qu'est-ce qui empêche un processus serveur de télécharger un fichier et de l'enregistrer dans un répertoire sur un serveur? C'est parfaitement faisable.
Gherman

Réponses:

598

Vous pouvez créer une GETdemande HTTP et la diriger responsevers un flux de fichiers accessible en écriture:

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

Si vous souhaitez prendre en charge la collecte d'informations sur la ligne de commande - comme spécifier un fichier ou un répertoire cible, ou une URL - consultez quelque chose comme Commander .

Michelle Tilley
la source
3
Je suis la sortie de la console suivante quand je courais ce 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
Essayez d'utiliser une URL différente sur la http.getligne; peut-être http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(et remplacer file.pngpar file.jpg).
Michelle Tilley
8
Ce code ferme-t-il correctement le fichier à la fin du script ou perdrait-il des données?
philk
2
@quantumpotato Jetez un œil à la réponse que vous obtenez de votre demande
Michelle Tilley
6
Cela dépend du type d'URL de demande si vous demandez, httpsvous devez utiliser httpssinon il générera une erreur.
Krishnadas PC
523

N'oubliez pas de gérer les erreurs! Le code suivant est basé sur la réponse d'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
la source
2
@ vince-yuan est download()lui - même pipecapable?
rasx
@theGrayFox Parce que le code dans cette réponse est bien plus long que celui accepté. :)
pootow
2
@Abdul On dirait que vous êtes très nouveau sur node.js / javascript. Jetez un oeil à ce tutoriel: tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm Ce n'est pas complexe.
Vince Yuan
1
@Abdul, ce serait peut-être bien si vous partagiez avec le reste de la classe ce que vous avez compris?
Curtwagner1984
5
Existe-t-il un moyen de voir la vitesse du téléchargement? Comme peut suivre combien de mb / s? Merci!
Tino Caer
138

Comme l'a dit Michelle Tilley, mais avec le flux de contrôle approprié:

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

Sans attendre l' finishévénement, les scripts naïfs peuvent se retrouver avec un fichier incomplet.

Edit: Merci à @Augusto Roman d'avoir souligné qui cbdevrait être transmis à file.close, pas appelé explicitement.

gfxmonk
la source
3
le rappel me déroute. si j'invoque maintenant download(), comment ferais-je? Que placerais-je comme cbargument? J'ai le download('someURI', '/some/destination', cb)mais je ne comprends pas quoi mettre dans le cb
Abdul
1
@Abdul Vous spécifiez le rappel avec une fonction uniquement si vous devez faire quelque chose lorsque le fichier a été récupéré avec succès.
CatalinBerta
65

En parlant de gestion des erreurs, il vaut mieux écouter aussi les erreurs de demande. Je validerais même en vérifiant le code de réponse. Ici, il est considéré comme un succès uniquement pour 200 codes de réponse, mais d'autres codes peuvent être bons.

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

Malgré la relative simplicité de ce code, je conseillerais d'utiliser le module de requête car il gère beaucoup plus de protocoles (bonjour HTTPS!) Qui ne sont pas supportés nativement par http.

Cela se ferait ainsi:

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
la source
2
Le module de demande fonctionne directement pour les HTTP. Cool!
Thiago C. S Ventura
@ventura yep, btw, il y a aussi le module https natif qui peut désormais gérer les connexions sécurisées.
Buzut
C'est plus sujet aux erreurs sans aucun doute. Quoi qu'il en soit, dans tous les cas où l'utilisation du module de demande est une option, je le conseillerais car c'est un niveau beaucoup plus élevé et donc, plus facile et efficace.
Buzut
2
@Alex, non, c'est un message d'erreur et il y a un retour. Donc, si response.statusCode !== 200le cb on finishne sera jamais appelé.
Buzut
1
Merci de montrer l'exemple en utilisant le module de demande.
Pete Alvin
48

La réponse de gfxmonk a une course de données très serrée entre le rappel et la file.close()fin. file.close()prend en fait un rappel qui est appelé lorsque la fermeture est terminée. Sinon, les utilisations immédiates du fichier peuvent échouer (très rarement!).

Une solution complète est:

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

Sans attendre l'événement de fin, les scripts naïfs peuvent se retrouver avec un fichier incomplet. Sans planifier le cbrappel via la fermeture, vous pouvez obtenir une course entre l'accès au fichier et le fichier réellement prêt.

Augusto Roman
la source
2
Pourquoi stockez-vous la demande dans une variable?
polkovnikov.ph
il le "stocke" dans une variable pour qu'il ne devienne pas une variable globale par défaut.
philk
@philk comment savez-vous qu'une variable globale est créée si elle var request =est supprimée?
ma11hew28
Vous avez raison, il n'est pas nécessaire d'enregistrer la demande, elle n'est pas utilisée de toute façon. C'est ça que tu veux dire?
philk
17

Peut-être que node.js a changé, mais il semble qu'il y ait des problèmes avec les autres solutions (en utilisant le nœud v8.1.2):

  1. Vous n'avez pas besoin d'appeler file.close()lors de l' finishévénement. Par défaut, le fs.createWriteStreamest défini sur autoClose: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()doit être appelé en cas d'erreur. Peut-être que ce n'est pas nécessaire lorsque le fichier est supprimé ( unlink()), mais normalement c'est: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. Le fichier temporaire n'est pas supprimé sur statusCode !== 200
  4. fs.unlink() sans rappel est obsolète (affiche un avertissement)
  5. Si le destfichier existe; il est annulé

Vous trouverez ci-dessous une solution modifiée (utilisant ES6 et promesses) qui gère ces problèmes.

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
la source
1
Deux commentaires à ce sujet: 1) il devrait probablement rejeter les objets d'erreur, pas les chaînes, 2) fs.unlink avalera tranquillement des erreurs qui ne sont pas nécessairement ce que vous voulez faire
Richard Nienaber
1
Cela fonctionne très bien! Et si vos URL utilisent HTTPS, remplacez simplement const https = require("https");parconst http = require("http");
Russ
15

Solution avec timeout, évite les fuites de mémoire:

Le code suivant est basé sur la réponse de 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();
    });
});

Ne créez pas de fichier lorsque vous obtenez une erreur et préférez utiliser le délai d'expiration pour fermer votre demande après X secondes.

A-312
la source
1
ce n'est qu'un fichier, n'a pas de protocole ou de serveur à télécharger depuis ...http.get("http://example.com/yourfile.html",function(){})
mjz19910
Y a-t-il une fuite de mémoire dans cette réponse: stackoverflow.com/a/22793628/242933 ?
ma11hew28
Vous pouvez ajouter un délai d'expiration comme je l'ai fait dans http.get. La fuite de mémoire ne se produit que si le fichier prend trop de temps pour être téléchargé.
A-312
13

pour ceux qui sont venus à la recherche de promesses basées sur le style es6, je suppose que ce serait quelque chose comme:

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
la source
2
responseSetflag a causé, pour une raison que je n'avais pas eu le temps d'examiner, mon fichier à télécharger de manière incomplète. Aucune erreur n'est apparue mais le fichier .txt que je remplissais avait la moitié des lignes qui devaient être là. La suppression de la logique du drapeau l'a corrigé. Je voulais juste signaler si quelqu'un avait des problèmes avec l'approche. Toujours, +1
Milan Velebit
6

Le code de Vince Yuan est génial mais il semble que quelque chose ne va pas.

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);
        });
    });
}
Ressentez la physique
la source
pouvons-nous spécifier le dossier de destination?
6

Je préfère request () car vous pouvez utiliser à la fois http et https.

request('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
  .pipe(fs.createWriteStream('cat.jpg'))
mixdev
la source
On dirait que Request a été déconseillé 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
la source
5

Salut , Je pense que vous pouvez utiliser le module child_process et la commande 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()

De plus, lorsque vous souhaitez télécharger de gros fichiers multiples, vous pouvez utiliser le module de cluster pour utiliser plus de cœurs de processeur.

wenningzhang
la source
4

Vous pouvez utiliser https://github.com/douzi8/ajax-request#download

request.download('http://res.m.ctrip.com/html5/Content/images/57.png', 
  function(err, res, body) {}
);
douzi
la source
2
Il renvoie un caractère poubelle si le nom de fichier est autre que ascii comme si le nom de fichier est en japonais.
Deepak Goel
4
Pensez-vous que ce ajax-requestn'est pas une bibliothèque tierce?
Murat Çorlu
4

Téléchargez à l'aide de promise, qui résout un flux lisible. mettre une logique supplémentaire pour gérer la redirection.

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
la source
1
302 est également le code d'état HTTP pour la redirection d'URL, vous devez donc utiliser ce [301,302] .indexOf (res.statusCode)! == -1 dans l'instruction if
sidanmor
Les questions étaient spécifiques pour ne pas inclure les modes tiers :)
David Gatti
3

Si vous utilisez la méthode express use res.download (). sinon utilisation du module fs.

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

(ou)

   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
la source
3

OPar conséquent, si vous utilisez un pipeline , il fermera tous les autres flux et s'assurera qu'il n'y a pas de fuite de mémoire.

Exemple de travail:

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

De ma réponse à "Quelle est la différence entre .pipe et .pipeline sur les flux" .

Idan Dagan
la source
2

Chemin: type img: jpg uniqid aléatoire

    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
la source
0

Sans bibliothèque, il pourrait être bogué pour le signaler. Voici quelques-uns:

  • Impossible de gérer la redirection http, comme cette URL https://calibre-ebook.com/dist/portable qui est binaire.
  • Le module http ne peut pas https url, vous obtiendrez Protocol "https:" not supported.

Voici ma suggestion:

  • Outil système d'appel comme wgetoucurl
  • utilisez un outil comme node-wget-promise qui est également très simple à utiliser. var wget = require('node-wget-promise'); wget('http://nodejs.org/images/logo.svg');
Geng Jiawen
la source
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
la source
0

Vous pouvez essayer d'utiliser res.redirectl'URL de téléchargement du fichier https, puis il téléchargera le fichier.

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

Yin
la source
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
la source
0

Voici encore une autre façon de le gérer sans dépendance tierce et de rechercher également des redirections:

        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
la source
0

download.js (ie /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
la source
-3

Nous pouvons utiliser le module de nœud de téléchargement et son très simple, veuillez vous référer ci-dessous https://www.npmjs.com/package/download

Iyyappan Subramani
la source
2
La question est de savoir comment le faire "sans utiliser de bibliothèques tierces".
ma11hew28
-4
var requestModule=require("request");

requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));
Chandrakant Thakkar
la source
5
Les vidages de code ne sont généralement pas utiles et peuvent être rétrogradés ou supprimés. Il vaudrait la peine d'éditer pour au moins expliquer ce que le code fait pour les futurs visiteurs.
Bugs du