Wie lade ich eine Datei mit Node.js herunter (ohne Bibliotheken von Drittanbietern zu verwenden)?

443

Wie lade ich eine Datei mit Node.js herunter, ohne Bibliotheken von Drittanbietern zu verwenden ?

Ich brauche nichts Besonderes. Ich möchte nur eine Datei von einer bestimmten URL herunterladen und sie dann in einem bestimmten Verzeichnis speichern.

greepow
quelle
5
"Datei mit node.js herunterladen" - meinst du Upload auf den Server? oder eine Datei von einem Remote-Server mit Ihrem Server abrufen? oder einem Client eine Datei zum Herunterladen von Ihrem node.js-Server bereitstellen?
Joseph
66
"Ich möchte nur eine Datei von einer bestimmten URL herunterladen und dann in einem bestimmten Verzeichnis speichern", scheint es ziemlich klar zu sein. :)
Michelle Tilley
34
Joseph macht eine falsche Behauptung, dass alle
Knotenprozesse
1
@lededje Was verhindert, dass ein Serverprozess eine Datei herunterlädt und in einem Verzeichnis auf einem Server speichert? Es ist perfekt machbar.
Gherman

Antworten:

598

Sie können eine HTTP- GETAnforderung erstellen und responsein einen beschreibbaren Dateistream weiterleiten:

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

Wenn Sie das Sammeln von Informationen in der Befehlszeile unterstützen möchten, z. B. das Angeben einer Zieldatei, eines Zieldverzeichnisses oder einer URL, lesen Sie etwas wie Commander .

Michelle Tilley
quelle
3
Ich habe die folgende Konsolenausgabe erhalten, als ich dieses Skript ausgeführt habe : 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
Versuchen Sie, eine andere URL in der http.getZeile zu verwenden. vielleicht http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(und ersetzen file.pngdurch file.jpg).
Michelle Tilley
8
Schließt dieser Code die Datei ordnungsgemäß, wenn das Skript endet, oder gehen Daten verloren?
Philk
2
@ Quantumpotato Werfen Sie einen Blick auf die Antwort, die Sie von Ihrer Anfrage erhalten
Michelle Tilley
6
Dies hängt vom angeforderten URL-Typ ab, wenn Sie ihn anfordern httpsmüssen, httpsandernfalls wird ein Fehler ausgegeben.
Krishnadas PC
523

Vergessen Sie nicht, mit Fehlern umzugehen! Der folgende Code basiert auf der Antwort von 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
quelle
2
@ Vince-Yuan ist download()selbst in der pipeLage?
Rasx
@theGrayFox Weil der Code in dieser Antwort viel länger ist als der akzeptierte. :)
pootow
2
@Abdul Klingt so, als wären Sie sehr neu in node.js / javascript. Schauen Sie sich dieses Tutorial an: tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm Es ist nicht komplex.
Vince Yuan
1
@Abdul Vielleicht wäre es gut, wenn Sie mit dem Rest der Klasse teilen, was Sie herausgefunden haben?
Curtwagner1984
5
Gibt es eine Möglichkeit, die Geschwindigkeit des Downloads zu sehen? Wie kann man verfolgen, wie viele MB / s? Vielen Dank!
Tino Caer
137

Wie Michelle Tilley sagte, aber mit dem entsprechenden Kontrollfluss:

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

Ohne auf das finishEreignis zu warten , können naive Skripte eine unvollständige Datei enthalten.

Bearbeiten: Vielen Dank an @Augusto Roman für den Hinweis, dass an übergeben werden cbsollte file.close, nicht explizit aufgerufen.

gfxmonk
quelle
3
Der Rückruf verwirrt mich. Wenn ich jetzt aufrufe download(), wie würde ich das tun? Was würde ich als cbArgument angeben? Ich habe die download('someURI', '/some/destination', cb)aber nicht verstanden, was ich in die cb setzen soll
Abdul
1
@Abdul Sie geben den Rückruf mit einer Funktion nur an, wenn Sie etwas tun müssen, wenn die Datei erfolgreich abgerufen wurde.
CatalinBerta
65

Apropos Fehlerbehandlung: Es ist sogar noch besser, Anforderungsfehler zu hören. Ich würde sogar validieren, indem ich den Antwortcode überprüfe. Hier wird es nur für 200 Antwortcodes als Erfolg angesehen, aber andere Codes könnten gut sein.

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

Trotz der relativen Einfachheit dieses Codes würde ich empfehlen, das Anforderungsmodul zu verwenden, da es viel mehr Protokolle (Hallo HTTPS!) Verarbeitet, die von Haus aus nicht unterstützt werden http.

Das würde so gemacht werden:

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
quelle
2
Das Anforderungsmodul funktioniert nur direkt für HTTPs. Cool!
Thiago C. S Ventura
@ventura yep, übrigens, es gibt auch das native https- Modul, das jetzt sichere Verbindungen verarbeiten kann.
Buzut
Es ist ohne Zweifel fehleranfälliger. In jedem Fall, in dem die Verwendung des Anforderungsmoduls eine Option ist, würde ich es empfehlen, da es viel höher und damit einfacher und effizienter ist.
Buzut
2
@Alex, nein, das ist eine Fehlermeldung und es gibt eine Rückkehr. Also, wenn response.statusCode !== 200das cb on finishniemals aufgerufen wird.
Buzut
1
Vielen Dank, dass Sie ein Beispiel mit dem Anforderungsmodul gezeigt haben.
Pete Alvin
48

Die Antwort von gfxmonk hat ein sehr enges Datenrennen zwischen dem Rückruf und dem file.close()Abschluss. file.close()Nimmt tatsächlich einen Rückruf entgegen, der aufgerufen wird, wenn der Abschluss abgeschlossen ist. Andernfalls kann die sofortige Verwendung der Datei fehlschlagen (sehr selten!).

Eine vollständige Lösung ist:

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

Ohne auf das Endereignis zu warten, können naive Skripte eine unvollständige Datei enthalten. Ohne den cbRückruf über das Schließen zu planen , kann es zu einem Wettlauf zwischen dem Zugriff auf die Datei und der tatsächlichen Bereitschaft der Datei kommen.

Augusto Roman
quelle
2
Wofür speichern Sie die Anfrage in einer Variablen?
polkovnikov.ph
er "speichert" es in einer Variablen, so dass es standardmäßig nicht zu einer globalen Variablen wird.
Philk
@philk Woher weißt du, dass eine globale Variable erstellt wird, wenn sie var request =entfernt wird?
ma11hew28
Sie haben Recht, die Anfrage muss nicht gespeichert werden, sie wird sowieso nicht verwendet. Das ist, was du meinst?
Philk
17

Möglicherweise hat sich node.js geändert, aber es scheint, dass es einige Probleme mit den anderen Lösungen gibt (unter Verwendung von Node v8.1.2):

  1. Sie müssen file.close()die finishVeranstaltung nicht anrufen . Standardmäßig fs.createWriteStreamist der Wert auf autoClose eingestellt: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()sollte bei Fehler aufgerufen werden. Möglicherweise wird dies nicht benötigt, wenn die Datei gelöscht wird ( unlink()), aber normalerweise ist dies: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. Temp-Datei wird am nicht gelöscht statusCode !== 200
  4. fs.unlink() ohne Rückruf ist veraltet (Ausgabe Warnung)
  5. Wenn eine destDatei vorhanden ist; es wird überschrieben

Im Folgenden finden Sie eine modifizierte Lösung (mit ES6 und Versprechungen), mit der diese Probleme behoben werden können.

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
quelle
1
Zwei Kommentare dazu: 1) Es sollte wahrscheinlich Fehlerobjekte ablehnen, keine Zeichenfolgen. 2) fs.unlink verschluckt leise Fehler, die möglicherweise nicht unbedingt das sind, was Sie tun möchten
Richard Nienaber
1
Das funktioniert super! Und wenn Ihre URLs HTTPS verwenden, ersetzen nur const https = require("https");fürconst http = require("http");
Russ
15

Lösung mit Zeitüberschreitung, Speicherverlust verhindern:

Der folgende Code basiert auf der Antwort von 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();
    });
});

Erstellen Sie keine Datei, wenn Sie eine Fehlermeldung erhalten, und verwenden Sie lieber das Zeitlimit, um Ihre Anforderung nach X Sekunden zu schließen.

A-312
quelle
1
Dies ist nur eine Datei, hat kein Protokoll oder Server zum Herunterladen von ...http.get("http://example.com/yourfile.html",function(){})
mjz19910
Gibt es einen Speicherverlust in dieser Antwort: stackoverflow.com/a/22793628/242933 ?
ma11hew28
Sie können eine Zeitüberschreitung hinzufügen, wie ich es in getan habe http.get. Der Speicherverlust tritt nur auf, wenn das Herunterladen der Datei zu lange dauert.
A-312
13

Für diejenigen, die auf der Suche nach einem Versprechen im Es6-Stil waren, wäre es wahrscheinlich so etwas wie:

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
quelle
2
responseSetflag verursachte aus irgendeinem Grund, für den ich keine Zeit gehabt hatte, meine Datei unvollständig herunterzuladen. Es sind keine Fehler aufgetreten, aber die von mir aufgefüllte TXT-Datei enthielt die Hälfte der Zeilen, die vorhanden sein mussten. Durch Entfernen der Logik für das Flag wurde das Problem behoben. Ich wollte nur darauf hinweisen, wenn jemand Probleme mit dem Ansatz hatte. Noch +1
Milan Velebit
6

Der Code von Vince Yuan ist großartig, aber es scheint etwas falsch zu sein.

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);
        });
    });
}
Fühle Physik
quelle
Können wir den Zielordner angeben?
6

Ich bevorzuge request (), da Sie damit sowohl http als auch https verwenden können.

request('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
  .pipe(fs.createWriteStream('cat.jpg'))
mixdev
quelle
Es sieht so aus, als wäre die Anfrage veraltet. 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
quelle
5

Hallo , Ich denke, Sie können das Modul child_process und den Befehl curl verwenden.

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

Wenn Sie große Dateien herunterladen möchten, können Sie außerdem das Cluster- Modul verwenden, um mehr CPU-Kerne zu verwenden.

wenningzhang
quelle
4

Sie können https://github.com/douzi8/ajax-request#download verwenden

request.download('http://res.m.ctrip.com/html5/Content/images/57.png', 
  function(err, res, body) {}
);
Douzi
quelle
2
Es gibt ein Müllzeichen zurück, wenn der Dateiname nicht ASCII ist, wie wenn der Dateiname auf Japanisch ist.
Deepak Goel
4
Denken Sie, dass ajax-requestes sich nicht um eine Drittanbieter-Bibliothek handelt?
Murat Çorlu
4

Download mit Versprechen, die einen lesbaren Stream auflösen. Setzen Sie zusätzliche Logik ein, um die Umleitung zu handhaben.

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
quelle
1
302 ist auch HTTP-Statuscode für die URL-Umleitung, daher sollten Sie diese [301,302] .indexOf (res.statusCode)! == -1 in der if-Anweisung verwenden
Sidanmor
Die Fragen waren spezifisch, um keine Modi von Drittanbietern einzuschließen :)
David Gatti
3

Wenn Sie Express verwenden, verwenden Sie die Methode res.download (). sonst fs Modul verwenden.

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

(oder)

   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
quelle
3

✅Wenn Sie eine Pipeline verwenden , werden alle anderen Streams geschlossen und es wird sichergestellt, dass keine Speicherlecks auftreten.

Arbeitsbeispiel:

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

Von meiner Antwort auf "Was ist der Unterschied zwischen .pipe und .pipeline in Streams" .

Idan Dagan
quelle
2

Pfad: img Typ: jpg zufällige 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
quelle
0

Ohne Bibliothek könnte es fehlerhaft sein, nur um darauf hinzuweisen. Hier sind ein paar:

  • Die http-Umleitung wie diese URL https://calibre-ebook.com/dist/portable kann nicht verarbeitet werden die binär ist.
  • http-Modul kann nicht https URL, erhalten Sie Protocol "https:" not supported.

Hier mein Vorschlag:

  • Rufen Sie das System-Tool wie auf wget oder aufcurl
  • Verwenden Sie ein Tool wie Node-Wget-Versprechen, das auch sehr einfach zu bedienen ist. var wget = require('node-wget-promise'); wget('http://nodejs.org/images/logo.svg');
Geng Jiawen
quelle
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
quelle
0

Sie können versuchen res.redirect, die Download-URL für die https-Datei zu verwenden. Anschließend wird die Datei heruntergeladen.

Mögen: res.redirect('https//static.file.com/file.txt');

Yin
quelle
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
quelle
0

Hier ist noch eine andere Möglichkeit, ohne Abhängigkeit von Drittanbietern damit umzugehen und auch nach Weiterleitungen zu suchen:

        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
quelle
0

download.js (dh /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
quelle
-3

Wir können das Download-Knoten-Modul verwenden und es ist sehr einfach. Weitere Informationen finden Sie unter https://www.npmjs.com/package/download

Iyyappan Subramani
quelle
2
Die Frage ist, wie dies "ohne Verwendung von Bibliotheken von Drittanbietern" zu tun ist.
ma11hew28
-4
var requestModule=require("request");

requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));
Chandrakant Thakkar
quelle
5
Code-Dumps sind im Allgemeinen nicht nützlich und können herabgestuft oder gelöscht werden. Es lohnt sich zu bearbeiten, um zumindest zu erklären, was der Code für zukünftige Besucher tut.
Bugs