¿Cómo descargar un archivo con Node.js (sin usar bibliotecas de terceros)?

443

¿Cómo descargo un archivo con Node.js sin usar bibliotecas de terceros ?

No necesito nada especial Solo quiero descargar un archivo de una URL determinada y luego guardarlo en un directorio determinado.

greepow
fuente
55
"descargar un archivo con node.js" : ¿quiere decir subir al servidor? o recuperar un archivo de un servidor remoto usando su servidor? o servir un archivo a un cliente para descargarlo de su servidor node.js?
Joseph
66
"Solo quiero descargar un archivo de una URL dada y luego guardarlo en un directorio dado", parece bastante claro. :)
Michelle Tilley
34
Joseph está haciendo una afirmación incorrecta de que todos los procesos de nodo son procesos de servidor
lededje
1
@lededje ¿Qué impide que un proceso del servidor descargue un archivo y lo guarde en un directorio de un servidor? Es perfectamente factible.
Gherman

Respuestas:

598

Puede crear una GETsolicitud HTTP y canalizarla responseen una secuencia de archivo grabable:

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

Si desea admitir la recopilación de información en la línea de comandos, como especificar un archivo o directorio de destino o URL, consulte algo como Commander .

Michelle Tilley
fuente
3
Tengo la siguiente salida de la consola cuando me encontré con este 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
Intente usar una URL diferente en la http.getlínea; tal vez http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(y reemplazar file.pngcon file.jpg).
Michelle Tilley
8
¿Este código cierra el archivo correctamente cuando finaliza el script o perdería datos?
philk
2
@quantumpotato Eche un vistazo a la respuesta que está recibiendo de su solicitud
Michelle Tilley
66
Esto depende del tipo de URL de solicitud si está solicitando httpsque deba usar, de lo httpscontrario arrojará un error.
Krishnadas PC
523

¡No olvides manejar los errores! El siguiente código se basa en la respuesta 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
fuente
2
@ Vince-Yuan es download()propio pipepoder?
rasx
@theGrayFox Porque el código en esta respuesta es mucho más largo que el aceptado. :)
pootow
2
@Abdul Parece que eres muy nuevo en node.js / javascript. Eche un vistazo a este tutorial: tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm No es complejo.
Vince Yuan
1
@Abdul, tal vez sería bueno si compartes con el resto de la clase lo que has descubierto.
Curtwagner1984
55
¿Hay alguna manera de ver la velocidad de la descarga? Como puede rastrear cuántos mb / s? ¡Gracias!
Tino Caer el
137

Como dijo Michelle Tilley, pero con el flujo de control apropiado:

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

Sin esperar el finishevento, los guiones ingenuos pueden terminar con un archivo incompleto.

Editar: Gracias a @Augusto Roman por señalar que se cbdebe pasar a file.close, no se llama explícitamente.

gfxmonk
fuente
3
la devolución de llamada me está confundiendo. si ahora invoco download(), ¿cómo lo haría? ¿Qué pondría como cbargumento? Tengo el download('someURI', '/some/destination', cb)pero no entiendo qué poner en el CB
Abdul
1
@Abdul Usted especifica la devolución de llamada con una función solo si necesita hacer algo cuando el archivo se ha recuperado correctamente.
CatalinBerta
65

Hablando de errores de manejo, es aún mejor escuchar los errores de solicitud también. Incluso validaría comprobando el código de respuesta. Aquí se considera exitoso solo para el código de respuesta 200, pero otros códigos pueden ser buenos.

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

A pesar de la relativa simplicidad de este código, recomendaría usar el módulo de solicitud, ya que maneja muchos más protocolos (¡Hola HTTPS!) Que no son compatibles de forma nativa http.

Eso se haría así:

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
fuente
2
El módulo de solicitud simplemente funciona directamente para HTTP. ¡Frio!
Thiago C. S Ventura
@ventura sí, por cierto, también está el módulo https nativo que ahora puede manejar conexiones seguras.
Buzut
Es más propenso a errores sin lugar a dudas. De todos modos, en cualquier caso donde usar el módulo de solicitud es una opción, lo recomendaría ya que es un nivel mucho más alto y, por lo tanto, más fácil y eficiente.
Buzut
2
@ Alex, no, este es un mensaje de error y hay un retorno. Entonces, si nunca se llamará a response.statusCode !== 200cb on finish.
Buzut
1
Gracias por mostrar un ejemplo utilizando el módulo de solicitud.
Pete Alvin el
48

La respuesta de gfxmonk tiene una carrera de datos muy ajustada entre la devolución de llamada y la file.close()finalización. file.close()en realidad toma una devolución de llamada que se llama cuando se completa el cierre. De lo contrario, los usos inmediatos del archivo pueden fallar (¡muy raramente!).

Una solución completa es:

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

Sin esperar el evento final, los guiones ingenuos pueden terminar con un archivo incompleto. Sin programar la cbdevolución de llamada a través del cierre, puede obtener una carrera entre acceder al archivo y el archivo que realmente está listo.

Augusto Roman
fuente
2
¿Para qué está almacenando la solicitud en una variable?
polkovnikov.ph
él "lo almacena" en una variable para que no se convierta en una variable global por defecto.
philk
@philk, ¿cómo sabes que se crea una variable global si var request =se elimina?
ma11hew28
Tienes razón, no hay necesidad de guardar la solicitud, de todos modos no se usa. ¿Eso es lo que quieres decir?
philk
17

Quizás node.js ha cambiado, pero parece que hay algunos problemas con las otras soluciones (usando el nodo v8.1.2):

  1. No necesita llamar file.close()en el finishevento. Por defecto, el fs.createWriteStreamestá configurado en autoClose: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()debe ser llamado por error. Quizás esto no sea necesario cuando se elimina el archivo ( unlink()), pero normalmente es: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. El archivo temporal no se elimina en statusCode !== 200
  4. fs.unlink() sin una devolución de llamada está en desuso (advertencia de salida)
  5. Si el destarchivo existe; está anulado

A continuación se muestra una solución modificada (usando ES6 y promesas) que maneja estos 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
fuente
1
Dos comentarios sobre esto: 1) probablemente debería rechazar objetos de Error, no cadenas, 2) fs.unlink tragará silenciosamente errores que podrían no ser necesariamente lo que quieres hacer
Richard Nienaber
1
¡Esto funciona muy bien! Y si sus URL utilizan HTTPS, simplemente sustituir const https = require("https");porconst http = require("http");
Russ
15

Solución con tiempo de espera, evitar pérdida de memoria:

El siguiente código se basa en la respuesta 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();
    });
});

No haga un archivo cuando reciba un error, y prefiera usar el tiempo de espera para cerrar su solicitud después de X segundos.

A-312
fuente
1
esto es solo un archivo, no tiene protocolo o servidor para descargar desde ...http.get("http://example.com/yourfile.html",function(){})
mjz19910
¿Hay una pérdida de memoria en esta respuesta: stackoverflow.com/a/22793628/242933 ?
ma11hew28
Puede agregar tiempo de espera como lo hice en http.get. La pérdida de memoria es solo si el archivo tarda demasiado en descargarse.
A-312
13

para aquellos que vinieron en busca de una forma basada en promesas de estilo es6, supongo que sería 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
fuente
2
responseSetLa bandera causó, por alguna razón que no había tenido tiempo de investigar, que mi archivo se descargara de forma incompleta. No aparecieron errores, pero el archivo .txt que estaba rellenando tenía la mitad de las filas que necesitaban estar allí. Eliminar la lógica de la bandera lo arregló. Solo quería señalar eso si alguien tenía problemas con el enfoque. Aún así, +1
Milan Velebit
6

El código de Vince Yuan es genial, pero parece estar mal.

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);
        });
    });
}
Siente la física
fuente
¿podemos especificar la carpeta de destino?
6

Prefiero request () porque puedes usar http y https con él.

request('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
  .pipe(fs.createWriteStream('cat.jpg'))
mixdev
fuente
Parece que la solicitud ha quedado en desuso 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
fuente
5

Hola, creo que puedes usar el módulo child_process y el 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()

Además, cuando desee descargar grandes archivos múltiples, puede usar el módulo de clúster para usar más núcleos de CPU.

Wenningzhang
fuente
4

Puede 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
fuente
2
Está devolviendo el carácter basura si el nombre del archivo no es ascii, como si el nombre del archivo está en japonés.
Deepak Goel
44
¿Crees que ajax-requestno es una biblioteca de terceros?
Murat Çorlu
4

Descargue usando promesa, que resuelve una secuencia legible. poner lógica extra para manejar la redirección.

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
fuente
1
302 también es un código de estado HTTP para la redirección de URL, por lo que debe usar este [301,302] .indexOf (res.statusCode)! == -1 en la instrucción if
sidanmor
Las preguntas eran específicas para no incluir modos de terceros :)
David Gatti
3

Si está utilizando el método express use res.download (). de lo contrario, uso del módulo fs.

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

(o)

   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
fuente
3

✅Así que si usa tubería , cerraría todas las demás transmisiones y se aseguraría de que no haya pérdidas de memoria.

Ejemplo de trabajo:

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

De mi respuesta a "¿Cuál es la diferencia entre .pipe y .pipeline en streams" .

Idan Dagan
fuente
2

Ruta: tipo img: 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
fuente
0

Sin biblioteca, podría tener errores solo para señalar. Aquí hay algunos:

  • No se puede manejar la redirección de http, como esta url https://calibre-ebook.com/dist/portable que es binaria.
  • el módulo http no puede https url, obtendrá Protocol "https:" not supported.

Aquí mi sugerencia:

  • Llame a la herramienta del sistema como wgetocurl
  • use alguna herramienta como node-wget-promise que también es muy fácil de usar. var wget = require('node-wget-promise'); wget('http://nodejs.org/images/logo.svg');
Geng Jiawen
fuente
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
fuente
0

Puede intentar usar res.redirectla URL de descarga del archivo https y luego descargará el archivo.

Me gusta: res.redirect('https//static.file.com/file.txt');

Yin
fuente
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
fuente
0

Aquí hay otra forma de manejarlo sin dependencia de terceros y también buscando redireccionamientos:

        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
fuente
0

download.js (es decir, /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
fuente
-3

Podemos usar el módulo de nodo de descarga y es muy simple, consulte a continuación https://www.npmjs.com/package/download

Iyyappan Subramani
fuente
2
La pregunta es cómo hacerlo "sin usar bibliotecas de terceros".
ma11hew28
-4
var requestModule=require("request");

requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));
Chandrakant Thakkar
fuente
55
Los volcados de código generalmente no son útiles y se pueden rechazar o eliminar. Valdría la pena editar al menos para explicar qué está haciendo el código para los futuros visitantes.
Bugs