Jak pobrać plik za pomocą Node.js (bez korzystania z bibliotek stron trzecich)?


443

Jak mogę pobrać plik za pomocą Node.js bez korzystania z bibliotek stron trzecich ?

Nie potrzebuję niczego specjalnego. Chcę tylko pobrać plik z danego adresu URL, a następnie zapisać go w danym katalogu.


5
„pobierz plik za pomocą node.js” - masz na myśli przesłanie na serwer? lub pobrać plik ze zdalnego serwera za pomocą swojego serwera? lub podać plik klientowi do pobrania z serwera node.js?
Joseph

66
„Chcę tylko pobrać plik z danego adresu URL, a następnie zapisać go w danym katalogu”, wydaje się to dość jasne. :)
Michelle Tilley,

34
Joseph niepoprawnie twierdzi, że wszystkie procesy węzłowe są procesami serwerowymi
lededje,

1
@lededje Co uniemożliwia procesowi serwera pobranie pliku i zapisanie go w katalogu na serwerze? Jest to doskonale wykonalne.
Gherman

Odpowiedzi:


598

Możesz utworzyć GETżądanie HTTP i potokować responseje do zapisywalnego strumienia plików:

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

Jeśli chcesz wesprzeć zbieranie informacji w wierszu poleceń - na przykład określenie pliku docelowego lub katalogu lub adresu URL - sprawdź coś takiego jak Commander .


3
Mam następujące wyjścia konsoli kiedy wpadłem ten skrypt: 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

Spróbuj użyć innego adresu URL w http.getwierszu; może http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(i wymienić file.pngz file.jpg).
Michelle Tilley,

8
Czy ten kod poprawnie zamyka plik po zakończeniu skryptu, czy też utraci dane?
philk

2
@quantumpotato Spójrz na odpowiedź, którą otrzymujesz od swojej prośby
Michelle Tilley

6
Zależy to od typu adresu URL żądania https, jeśli chcesz , musisz użyć, w httpsprzeciwnym razie spowoduje to błąd.
Krishnadas PC

523

Nie zapomnij obsługiwać błędów! Poniższy kod oparty jest na odpowiedzi Augusto Romana.

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 download()sam jest w pipestanie?
rasx

@theGrayFox Ponieważ kod w tej odpowiedzi jest znacznie dłuższy niż zaakceptowany. :)
pootow

2
@Abdul Brzmi, jakbyś był bardzo nowy w node.js / javascript. Spójrz na ten samouczek: tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm To nie jest skomplikowane.
Vince Yuan,

1
@Abdul, może byłoby dobrze, gdybyś podzielił się z resztą klasy tym, co odkryłeś?
Curtwagner1984,

5
Czy istnieje sposób, aby sprawdzić szybkość pobierania? Jak można śledzić, ile Mb / s? Dzięki!
Tino Caer,

137

Jak powiedziała Michelle Tilley, ale z odpowiednim przepływem kontrolnym:

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

Bez oczekiwania na finishzdarzenie, naiwne skrypty mogą zakończyć się niepełnym plikiem.

Edycja: Podziękowania dla @Augusto Roman za wskazanie, które cbnależy przekazać file.close, a nie wywołać jawnie.


3
oddzwanianie mnie dezorientuje. jeśli teraz przywołam download(), jak mam to zrobić? Co chciałbym umieścić jako cbargument? Mam, download('someURI', '/some/destination', cb)ale nie rozumiem, co włożyć do cb
Abdul

1
@Abdul Oddzwanianie określasz za pomocą funkcji tylko wtedy, gdy musisz coś zrobić po pomyślnym pobraniu pliku.
CatalinBerta

65

Mówiąc o obsłudze błędów, jeszcze lepiej jest słuchać żądań błędów. Sprawdziłbym nawet, sprawdzając kod odpowiedzi. Tutaj uważa się za sukces tylko dla 200 kodów odpowiedzi, ale inne kody mogą być dobre.

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

Pomimo względnej prostoty tego kodu, radziłbym użyć modułu żądania, ponieważ obsługuje on o wiele więcej protokołów (cześć HTTPS!), Które nie są natywnie obsługiwane http.

Można to zrobić tak:

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
Moduł żądania działa po prostu dla HTTPs. Fajne!
Thiago C. S Ventura

@ventura yep, btw, istnieje również natywny moduł https , który może teraz obsługiwać bezpieczne połączenia.
Buzut

Bez wątpienia jest bardziej podatny na błędy. W każdym razie, w każdym przypadku, w którym użycie modułu żądania jest opcją, radzę, ponieważ jest to o wiele wyższy poziom, a zatem łatwiejsze i wydajniejsze.
Buzut

2
@Alex, nie, to komunikat o błędzie i zwrot. Więc jeśli response.statusCode !== 200cb on finishnigdy nie zostanie wywołany.
Buzut

1
Dziękujemy za pokazanie przykładu za pomocą modułu żądania.
Pete Alvin

48

Odpowiedź gfxmonk ma bardzo ścisły wyścig danych między wywołaniem zwrotnym a file.close()zakończeniem. file.close()faktycznie odbiera oddzwonienie, które jest wywoływane po zakończeniu zamykania. W przeciwnym razie natychmiastowe użycie pliku może się nie powieść (bardzo rzadko!).

Kompletne rozwiązanie to:

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

Bez oczekiwania na zakończenie, naiwne skrypty mogą kończyć się niekompletnym plikiem. Bez planowania cboddzwaniania przez zamknięcie możesz uzyskać wyścig między dostępem do pliku a plikiem, który jest w rzeczywistości gotowy.


2
Po co przechowujesz zapytanie w zmiennej?
polkovnikov.ph

„przechowuje” go w zmiennej, aby domyślnie nie stała się zmienną globalną.
philk

@philk, skąd wiesz, że zmienna globalna jest tworzona, jeśli var request =zostanie usunięta?
ma11hew28,

Masz rację, nie ma potrzeby zapisywania żądania, i tak nie jest ono używane. To masz na myśli?
philk

17

Być może node.js się zmieniło, ale wydaje się, że istnieją pewne problemy z innymi rozwiązaniami (przy użyciu węzła v8.1.2):

  1. Nie musisz dzwonić file.close()na finishwydarzenie. Domyślnie fs.createWriteStreamjest ustawione na autoClose: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()powinien zostać wywołany w przypadku błędu. Może nie jest to potrzebne, gdy plik jest usuwany ( unlink()), ale zwykle jest to: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. Plik tymczasowy nie jest usuwany w dniu statusCode !== 200
  4. fs.unlink() bez oddzwaniania jest przestarzałe (ostrzeżenie o wyjściach)
  5. Jeśli destplik istnieje; jest nadpisane

Poniżej znajduje się zmodyfikowane rozwiązanie (wykorzystujące ES6 i obietnice), które rozwiązuje te problemy.

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
Dwa komentarze na ten temat: 1) prawdopodobnie powinien odrzucić obiekty Error, a nie ciągi, 2) fs.unlink po cichu przełknie błędy, które niekoniecznie muszą być tym, co chcesz zrobić
Richard Nienaber

1
To działa świetnie! A jeśli adresy URL użyciu protokołu HTTPS, wystarczy podstawić const https = require("https");zaconst http = require("http");
Russ

15

Rozwiązanie z przekroczeniem limitu czasu, zapobieganie wyciekom pamięci:

Poniższy kod oparty jest na odpowiedzi Brandona Tilleya:

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

Nie twórz pliku, gdy pojawi się błąd, i korzystaj z limitu czasu, aby zamknąć żądanie po X sekundach.


1
to tylko plik, nie ma protokołu ani serwera do pobrania z ...http.get("http://example.com/yourfile.html",function(){})
mjz19910

Czy w tej odpowiedzi jest wyciek pamięci: stackoverflow.com/a/22793628/242933 ?
ma11hew28,

Możesz dodać limit czasu jak ja http.get. Przeciek pamięci występuje tylko wtedy, gdy pobieranie pliku trwa zbyt długo.
A-312

13

dla tych, którzy szukają sposobu opartego na obietnicy w stylu es6, myślę, że byłoby to coś w stylu:

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
responseSetFlaga spowodowała, z jakiegoś powodu, dla którego nie miałem czasu na zbadanie, mój plik został pobrany niekompletnie. Nie wyskoczyły żadne błędy, ale plik .txt, który wypełniałem, zawierał połowę wierszy, które musiały tam być. Usunięto logikę flagi. Chciałem tylko podkreślić, że jeśli ktoś miałby problemy z tym podejściem. Mimo to +1
Milan Velebit

6

Kod Vince'a Yuan jest świetny, ale wydaje się, że coś jest nie tak.

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

czy możemy określić folder docelowy?

6

Wolę request (), ponieważ możesz używać zarówno http, jak i https.

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

Wygląda na to, że wniosek został wycofany 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

Cześć, myślę, że możesz użyć modułu child_process i polecenia 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()

Ponadto, gdy chcesz pobrać duże 、 wiele plików, możesz użyć modułu klastra , aby użyć większej liczby rdzeni procesora.



4

Pobierz za pomocą obietnicy, która rozwiązuje czytelny strumień. umieść dodatkową logikę do obsługi przekierowania.

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 jest także kodem stanu HTTP dla przekierowania URL, więc powinieneś użyć tego [301,302] .indexOf (res.statusCode)! == -1 w instrukcji if
sidanmor

Pytania były specyficzne, aby nie uwzględniać trybów stron trzecich :)
David Gatti

3

Jeśli używasz ekspresowego, użyj metody res.download (). w przeciwnym razie użyj modułu fs.

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

(lub)

   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

✅ Więc jeśli użyjesz potoku , zamknie on wszystkie inne strumienie i upewni się, że nie ma wycieków pamięci.

Przykład roboczy:

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

Z mojej odpowiedzi do „Jaka jest różnica między .pipe a .pipeline w strumieniach” .


2

Ścieżka: img type: 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

Bez biblioteki wskazanie tego byłoby błędem. Tu jest kilka:

  • Nie można obsłużyć przekierowania HTTP, takiego jak ten adres URL https://calibre-ebook.com/dist/portable, który jest binarny.
  • Moduł http nie może https url, dostaniesz Protocol "https:" not supported.

Oto moja sugestia:

  • Wywołaj narzędzie systemowe, takie jak wgetlubcurl
  • użyj jakiegoś narzędzia, takiego jak węzeł-wget-obietnica, który również jest bardzo prosty w użyciu. 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

Możesz spróbować użyć res.redirectadresu URL pobierania pliku https, a wtedy zostanie pobrany plik.

Lubić: 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

Oto jeszcze jeden sposób, aby sobie z tym poradzić bez zależności od strony trzeciej, a także poszukać przekierowań:

        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 (tj. /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
Zrzuty kodu są generalnie nieprzydatne i mogą zostać odrzucone lub usunięte. Warto byłoby edytować, aby przynajmniej wyjaśnić, co robi kod dla przyszłych użytkowników.
Błędy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.