Como baixar um arquivo com Node.js (sem usar bibliotecas de terceiros)?


443

Como faço para baixar um arquivo com Node.js sem usar bibliotecas de terceiros ?

Eu não preciso de nada de especial. Eu só quero fazer o download de um arquivo de um determinado URL e salvá-lo em um determinado diretório.


5
"baixar um arquivo com node.js" - você quer dizer fazer upload para o servidor? ou recuperar um arquivo de um servidor remoto usando seu servidor? ou veicular um arquivo para um cliente para download no seu servidor node.js.
Joseph

66
"Eu só quero baixar um arquivo de um determinado URL e salvá-lo em um determinado diretório", parece bem claro. :)
Michelle Tilley

34
Joseph está fazendo uma afirmação incorreta de que todos os processos de nó são processos do servidor
lededje

1
@lededje O que impede um processo de servidor de baixar um arquivo e salvá-lo em um diretório em um servidor? É perfeitamente viável.
Gherman

Respostas:


598

Você pode criar uma GETsolicitação HTTP e canalizá-la responsepara um fluxo de arquivos gravável:

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 você deseja oferecer suporte à coleta de informações na linha de comando - como especificar um arquivo ou diretório de destino ou URL - consulte algo como o Commander .


3
Eu tenho a seguinte saída do console quando eu corri esse 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

Tente usar um URL diferente na http.getlinha; talvez http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(e substitua file.pngpor file.jpg).
precisa saber é o seguinte

8
Esse código fecha o arquivo corretamente quando o script termina ou ele perderia dados?
20915 philk

2
@quantumpotato Dê uma olhada na resposta que você está recebendo de volta do seu pedido
Michelle Tilley 8/18

6
Isso depende do tipo de URL de solicitação, se você estiver solicitando, httpsdeverá usar, httpscaso contrário, ocorrerá erro.
Krishnadas PC

523

Não se esqueça de lidar com erros! O código a seguir é baseado na resposta de 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 é download()em si pipecapaz?
rasx

@theGrayFox Porque o código nesta resposta é muito mais longo que o aceito. :)
pootow

2
@Abdul Parece que você é muito novo no node.js / javascript. Dê uma olhada neste tutorial: tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm Não é complexo.
Vince Yuan

1
@ Abdul talvez seja bom se você compartilhar com o resto da classe o que descobriu?
Curtwagner1984

5
Existe uma maneira de ver a velocidade do download? Como pode acompanhar quantos mb / s? Obrigado!
Tino Caer

137

Como Michelle Tilley disse, mas com o fluxo de controle apropriado:

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

Sem aguardar o finishevento, scripts ingênuos podem acabar com um arquivo incompleto.

Edit: Obrigado a @Augusto Roman por apontar que cbdeve ser passado para file.close, e não chamado explicitamente.


3
o retorno de chamada está me confundindo. se eu invocar agora download(), como faria? O que eu colocaria como cbargumento? Tenho a download('someURI', '/some/destination', cb), mas não entendo o que colocar no cb
Abdul

1
@Abdul Você especifica o retorno de chamada com uma função apenas se precisar fazer alguma coisa quando o arquivo tiver sido buscado com êxito.
CatalinBerta 17/05

65

Falando em lidar com erros, é ainda melhor ouvir solicitações de erros também. Eu até validaria verificando o código de resposta. Aqui é considerado sucesso apenas para 200 códigos de resposta, mas outros códigos podem ser 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);
    });
};

Apesar da relativa simplicidade desse código, aconselho o uso do módulo request, pois ele lida com muitos outros protocolos (hello HTTPS!) Que não são suportados nativamente por http.

Isso seria feito assim:

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
O módulo de solicitação funciona diretamente para HTTPs. Legal!
Thiago C. S Ventura

@ ventura sim, aliás, também há o módulo https nativo que agora pode lidar com conexões seguras.
Buzut

É mais propenso a erros, sem dúvida. De qualquer forma, em qualquer caso em que o uso do módulo de solicitação seja uma opção, eu o aconselharia, pois é muito mais alto e, portanto, mais fácil e eficiente.
Buzut

2
@ Alex, não, esta é uma mensagem de erro e há um retorno. Portanto, se response.statusCode !== 200o cb on finishnunca for chamado.
Buzut

1
Obrigado por mostrar o exemplo usando o módulo de solicitação.
Pete Alvin

48

A resposta de gfxmonk tem uma corrida de dados muito estreita entre o retorno de chamada e a file.close()conclusão. file.close()na verdade, recebe um retorno de chamada chamado quando o fechamento é concluído. Caso contrário, o uso imediato do arquivo poderá falhar (muito raramente!).

Uma solução 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.
    });
  });
}

Sem aguardar o evento de conclusão, os scripts ingênuos podem acabar com um arquivo incompleto. Sem agendar o cbretorno de chamada por meio do fechamento, você pode ter uma corrida entre acessar o arquivo e o arquivo realmente estar pronto.


2
Para que você está armazenando uma solicitação em uma variável?
polkovnikov.ph

ele "armazena" em uma variável para que não se torne uma variável global por padrão.
28415 philk

@philk, como você sabe que uma variável global é criada se var request =for removida?
ma11hew28

Você está certo, não há necessidade de salvar a solicitação, ela não é usada de qualquer maneira. É isso que você quer dizer?
philk

17

Talvez o node.js tenha mudado, mas parece que existem alguns problemas com as outras soluções (usando o nó v8.1.2):

  1. Você não precisa ligar file.close()no finishevento. Por padrão, fs.createWriteStreamé definido como autoClose: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()deve ser chamado por erro. Talvez isso não seja necessário quando o arquivo for excluído ( unlink()), mas normalmente é: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. O arquivo temporário não é excluído em statusCode !== 200
  4. fs.unlink() sem retorno de chamada é preterido (gera aviso)
  5. Se o destarquivo existir; é substituído

Abaixo está uma solução modificada (usando ES6 e promessas) que lida com esses problemas.

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
Dois comentários sobre isso: 1) ele provavelmente deveria rejeitar objetos Erro, não cordas, 2) fs.unlink vai erros tranquilamente andorinha que pode não ser necessariamente o que você quer fazer
Richard Nienaber

1
Isso funciona muito bem! E se os seus URLs usar HTTPS, basta substituir const https = require("https");porconst http = require("http");
Russ

15

Solução com tempo limite, evite vazamento de memória:

O código a seguir é baseado na resposta 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();
    });
});

Não crie arquivo quando ocorrer um erro e prefira usar o tempo limite para fechar sua solicitação após X segundos.


1
este é apenas um arquivo, não tem nenhum protocolo ou servidor para transferência a partir ...http.get("http://example.com/yourfile.html",function(){})
mjz19910

Há um vazamento de memória nesta resposta: stackoverflow.com/a/22793628/242933 ?
ma11hew28

Você pode adicionar tempo limite como eu fiz http.get. O vazamento de memória é apenas se o arquivo demorar muito para ser baixado.
A-312

13

para aqueles que vieram em busca da maneira baseada em promessas no estilo es6, acho que seria algo como:

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
responseSetA flag causou, por algum motivo que eu não tive tempo de investigar, meu arquivo para ser baixado incompletamente. Nenhum erro foi exibido, mas o arquivo .txt que eu estava preenchendo tinha metade das linhas que precisavam estar lá. A remoção da lógica do sinalizador a corrigiu. Só queria salientar se alguém tinha problemas com a abordagem. Ainda assim, +1
Milan Velebit 05/10

6

O código de Vince Yuan é ótimo, mas parece haver algo errado.

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

podemos especificar a pasta de destino?

6

Prefiro request () porque você pode usar http e https com ele.

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

Parece que Request foi descontinuado 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

Oi. Eu acho que você pode usar o módulo child_process e o 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()

Além disso, quando você deseja baixar grandes arquivos múltiplos, você pode usar o módulo de cluster para usar mais núcleos de CPU.



4

Faça o download usando a promessa, que resolve um fluxo legível. coloque lógica extra para lidar com o redirecionamento.

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 também é código de status HTTP para a URL de redirecionamento, então você deve usar esse [301,302] .indexOf (res.statusCode) == -1 no if!
sidanmor

As perguntas era específica para não incluir modos de terceiros :)
David Gatti

3

Se você estiver usando express use o método res.download (). caso contrário, use o módulo 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");
          }
        });  
    }

3

OEntão, se você usar o pipeline , ele fechará todos os outros fluxos e garantirá que não haja vazamentos de memória.

Exemplo de trabalho:

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

Da minha resposta para "Qual é a diferença entre .pipe e .pipeline em fluxos" .


2

Caminho: img tipo: jpg uniqid aleatório

    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

Sem biblioteca, poderia ser um buggy apenas para apontar. Aqui estão alguns:

  • Não é possível lidar com o redirecionamento http, como este URL https://calibre-ebook.com/dist/portable que é binário.
  • módulo http não pode https url, você receberá Protocol "https:" not supported.

Aqui está minha sugestão:

  • Chame a ferramenta do sistema como wget oucurl
  • use alguma ferramenta como node-wget-promessa que também é muito simples de usar. 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

Você pode tentar usar res.redirecto URL de download do arquivo https e, em seguida, ele fará o download do arquivo.

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

Aqui está outra maneira de lidar com isso sem dependência de terceiros e também procurando por redirecionamentos:

        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 (por exemplo, /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
Geralmente, os despejos de código não são úteis e podem ser eliminados ou eliminados. Vale a pena editar para pelo menos explicar o que o código está fazendo para futuros visitantes.
Bugs
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.