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.

greepow
źródło
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 .

Michelle Tilley
źródło
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);
  });
};
Vince Yuan
źródło
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.

gfxmonk
źródło
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);
    });
};
Buzut
źródło
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.

Augusto Roman
źródło
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);
            }
        });
    });
}
Bjarke Pjedsted
źródło
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.

A-312
źródło
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));
mido
źródło
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);
        });
    });
}
Poczuj fizykę
źródło
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'))
mixdev
źródło
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));
kayz1
źródło
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.

wenningzhang
źródło
4

Możesz użyć https://github.com/douzi8/ajax-request#download

request.download('http://res.m.ctrip.com/html5/Content/images/57.png', 
  function(err, res, body) {}
);
douzi
źródło
2
Zwraca śmieci, jeśli nazwa pliku jest inna niż ascii, tak jak nazwa pliku w języku japońskim.
Deepak Goel
4
Czy uważasz, że ajax-requestnie jest to biblioteka strony trzeciej?
Murat Çorlu
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);
    }
});
wdanxna
źródło
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");
          }
        });  
    }
KARTHIKEYAN.A
źródło
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” .

Idan Dagan
źródło
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;
}
databilim
źródło
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');
Geng Jiawen
źródło
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
źródło
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');

Yin
źródło
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
źródło
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.
              });
            });
          }
Frankenmint
źródło
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')
});
williamsi
źródło
-3

Możemy użyć modułu węzła pobierania i jego bardzo prosty, patrz poniżej https://www.npmjs.com/package/download

Iyyappan Subramani
źródło
2
Pytanie brzmi: jak to zrobić „bez korzystania z bibliotek stron trzecich”.
ma11hew28,
-4
var requestModule=require("request");

requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));
Chandrakant Thakkar
źródło
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