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.

greepow
fonte
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 .

Michelle Tilley
fonte
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);
  });
};
Vince Yuan
fonte
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.

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

Augusto Roman
fonte
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);
            }
        });
    });
}
Bjarke Pjedsted
fonte
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.

A-312
fonte
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));
mido
fonte
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);
        });
    });
}
Feel Physics
fonte
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'))
mixdev
fonte
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));
kayz1
fonte
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.

wenningzhang
fonte
4

Você pode usar https://github.com/douzi8/ajax-request#download

request.download('http://res.m.ctrip.com/html5/Content/images/57.png', 
  function(err, res, body) {}
);
douzi
fonte
2
Ele está retornando um caractere de lixo se o nome do arquivo for diferente de ascii, como se o nome do arquivo estivesse em japonês.
Deepak Goel
4
Você acha que ajax-requestnão é uma biblioteca de terceiros?
Murat Çorlu 23/08
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);
    }
});
wdanxna
fonte
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");
          }
        });  
    }
KARTHIKEYAN.A
fonte
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" .

Idan Dagan
fonte
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;
}
databilim
fonte
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');
Geng Jiawen
fonte
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
fonte
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');

Yin
fonte
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
fonte
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.
              });
            });
          }
Frankenmint
fonte
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')
});
williamsi
fonte
-3

Podemos usar o módulo do nó de download e é muito simples, consulte abaixo https://www.npmjs.com/package/download

Iyyappan Subramani
fonte
2
A questão é perguntar como fazê-lo "sem usar bibliotecas de terceiros".
ma11hew28
-4
var requestModule=require("request");

requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));
Chandrakant Thakkar
fonte
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