Descargando imágenes con node.js [cerrado]

169

Estoy tratando de escribir un script para descargar imágenes usando node.js. Esto es lo que tengo hasta ahora:

var maxLength = 10 // 10mb
var download = function(uri, callback) {
  http.request(uri)
    .on('response', function(res) {
      if (res.headers['content-length'] > maxLength*1024*1024) {
        callback(new Error('Image too large.'))
      } else if (!~[200, 304].indexOf(res.statusCode)) {
        callback(new Error('Received an invalid status code.'))
      } else if (!res.headers['content-type'].match(/image/)) {
        callback(new Error('Not an image.'))
      } else {
        var body = ''
        res.setEncoding('binary')
        res
          .on('error', function(err) {
            callback(err)
          })
          .on('data', function(chunk) {
            body += chunk
          })
          .on('end', function() {
            // What about Windows?!
            var path = '/tmp/' + Math.random().toString().split('.').pop()
            fs.writeFile(path, body, 'binary', function(err) {
              callback(err, path)
            })
          })
      }
    })
    .on('error', function(err) {
      callback(err)
    })
    .end();
}

Sin embargo, quiero hacer esto más robusto:

  1. ¿Hay bibliotecas que hacen esto y lo hacen mejor?
  2. ¿Existe la posibilidad de que los encabezados de respuesta mientan (sobre la longitud, sobre el tipo de contenido)?
  3. ¿Hay otros códigos de estado que me interesen? ¿Debo molestarme con las redirecciones?
  4. Creo que leí en alguna parte que binary codificación quedará en desuso. ¿Qué hago entonces?
  5. ¿Cómo puedo hacer que esto funcione en Windows?
  6. ¿De alguna otra manera puedes mejorar este script?

Por qué: para una función similar a imgur donde los usuarios me pueden dar una URL, descargo esa imagen y vuelvo a alojar la imagen en varios tamaños.

Jonathan Ong
fuente

Respuestas:

401

Sugeriría usar el módulo de solicitud . Descargar un archivo es tan simple como el siguiente código:

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.google.com/images/srpr/logo3w.png', 'google.png', function(){
  console.log('done');
});
Cezary Wojtkowski
fuente
1
¡Frio! ¿Hay alguna forma de verificar el tamaño y el tipo de contenido antes de descargarlo?
Jonathan Ong
2
¿Dónde descarga las imágenes?
Gofilord
18
No funciona para mí (Imagen corrupta
Darth
2
@Gofilord es descargar la imagen a su directorio raíz.
Dang
1
¿Se puede cambiar la ubicación de donde se guardan? Si los quería en una carpeta específica?
AKL012
34

Me encontré con este problema hace unos días, por una respuesta pura de NodeJS, sugeriría usar Stream para fusionar los fragmentos.

var http = require('http'),                                                
    Stream = require('stream').Transform,                                  
    fs = require('fs');                                                    

var url = 'http://www.google.com/images/srpr/logo11w.png';                    

http.request(url, function(response) {                                        
  var data = new Stream();                                                    

  response.on('data', function(chunk) {                                       
    data.push(chunk);                                                         
  });                                                                         

  response.on('end', function() {                                             
    fs.writeFileSync('image.png', data.read());                               
  });                                                                         
}).end();

Las versiones más recientes de Node no funcionarán bien con cadenas binarias, por lo que fusionar fragmentos con cadenas no es una buena idea cuando se trabaja con datos binarios.

* Solo tenga cuidado al usar 'data.read ()', vaciará el flujo para la próxima operación 'read ()'. Si desea usarlo más de una vez, guárdelo en algún lugar.

Nihey Takizawa
fuente
77
¿Por qué no transmitir la descarga directamente al disco?
geon
Tuve
28

Puede usar Axios (un cliente HTTP basado en promesas para Node.js) para descargar imágenes en el orden que elija en un entorno asíncrono :

npm i axios

Luego, puede usar el siguiente ejemplo básico para comenzar a descargar imágenes:

const fs = require('fs');
const axios = require('axios');

/* ============================================================
  Function: Download Image
============================================================ */

const download_image = (url, image_path) =>
  axios({
    url,
    responseType: 'stream',
  }).then(
    response =>
      new Promise((resolve, reject) => {
        response.data
          .pipe(fs.createWriteStream(image_path))
          .on('finish', () => resolve())
          .on('error', e => reject(e));
      }),
  );

/* ============================================================
  Download Images in Order
============================================================ */

(async () => {
  let example_image_1 = await download_image('https://example.com/test-1.png', 'example-1.png');

  console.log(example_image_1.status); // true
  console.log(example_image_1.error); // ''

  let example_image_2 = await download_image('https://example.com/does-not-exist.png', 'example-2.png');

  console.log(example_image_2.status); // false
  console.log(example_image_2.error); // 'Error: Request failed with status code 404'

  let example_image_3 = await download_image('https://example.com/test-3.png', 'example-3.png');

  console.log(example_image_3.status); // true
  console.log(example_image_3.error); // ''
})();
Grant Miller
fuente
2
Gran ejemplo! Pero código apenas legible, pruebe el estilo estándar : D
camwhite
3
@camwhite Prefiero punto y coma . ;)
Grant Miller el
1
Realmente debe adjuntar eventos de 'finalización' y 'error' a la secuencia de escritura, envolverlos en una Promesa y devolver la promesa. De lo contrario, puede intentar acceder a una imagen que aún no se ha descargado por completo.
jwerre
¿La espera no se aseguraría de que la imagen se descargue completamente antes de intentar acceder? @jwerre
FabricioG
@jwerre @FabricioG He actualizado la función download_imagepara capturar el evento 'finalizar' y 'error' para la promesa devuelta
Beeno Tung
10

si quieres progreso descarga intenta esto:

var fs = require('fs');
var request = require('request');
var progress = require('request-progress');

module.exports = function (uri, path, onProgress, onResponse, onError, onEnd) {
    progress(request(uri))
    .on('progress', onProgress)
    .on('response', onResponse)
    .on('error', onError)
    .on('end', onEnd)
    .pipe(fs.createWriteStream(path))
};

cómo utilizar:

  var download = require('../lib/download');
  download("https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png", "~/download/logo.png", function (state) {
            console.log("progress", state);
        }, function (response) {
            console.log("status code", response.statusCode);
        }, function (error) {
            console.log("error", error);
        }, function () {
            console.log("done");
        });

nota: debe instalar los módulos request y request-progress utilizando:

npm install request request-progress --save
Alnamrouti Fareed
fuente
2
Esto funcionó muy bien, pero quería sugerir agregar un statusCodecheque. Un 500 statusCode, por ejemplo, no golpeará el 'on("error", e). Al agregar un on('response', (response) => console.error(response.statusCode))
archivo
1
Puedes editar mi respuesta :)
Fareed Alnamrouti
4

Sobre la base de lo anterior, si alguien necesita manejar errores en las secuencias de escritura / lectura, utilicé esta versión. Tenga stream.read()en cuenta que en caso de un error de escritura, es necesario para que podamos terminar de leer y disparar closeen la secuencia de lectura.

var download = function(uri, filename, callback){
  request.head(uri, function(err, res, body){
    if (err) callback(err, filename);
    else {
        var stream = request(uri);
        stream.pipe(
            fs.createWriteStream(filename)
                .on('error', function(err){
                    callback(error, filename);
                    stream.read();
                })
            )
        .on('close', function() {
            callback(null, filename);
        });
    }
  });
};
VladFr
fuente
2
stream.read()parece estar desactualizado, arroja un errornot a function
bendulum
4
var fs = require('fs'),
http = require('http'),
https = require('https');

var Stream = require('stream').Transform;

var downloadImageToUrl = (url, filename, callback) => {

    var client = http;
    if (url.toString().indexOf("https") === 0){
      client = https;
     }

    client.request(url, function(response) {                                        
      var data = new Stream();                                                    

      response.on('data', function(chunk) {                                       
         data.push(chunk);                                                         
      });                                                                         

      response.on('end', function() {                                             
         fs.writeFileSync(filename, data.read());                               
      });                                                                         
   }).end();
};

downloadImageToUrl('https://www.google.com/images/srpr/logo11w.png', 'public/uploads/users/abc.jpg');
Chandan Chhajer
fuente
1
su función no activa la devolución de llamada
crockpotveggies
4

Esta es una extensión de la respuesta de Cezary. Si desea descargarlo a un directorio específico, use esto. Además, use const en lugar de var. Es seguro de esta manera.

const fs = require('fs');
const request = require('request');
var download = function(uri, filename, callback){
  request.head(uri, function(err, res, body){    
    request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
  });
};

download('https://www.google.com/images/srpr/logo3w.png', './images/google.png', function(){
  console.log('done');
});
Ahsan Ahmed
fuente