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.