Fazendo download de imagens com node.js [fechado]

169

Estou tentando escrever um script para baixar imagens usando o node.js. Isto é o que eu tenho até agora:

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

Eu, no entanto, quero tornar isso mais robusto:

  1. Existem bibliotecas que fazem isso e fazem isso melhor?
  2. Existe uma chance de que os cabeçalhos de resposta estejam (sobre tamanho, tipo de conteúdo)?
  3. Existem outros códigos de status com os quais devo me preocupar? Devo me preocupar com redirecionamentos?
  4. Acho que li em algum lugar que a binarycodificação será descontinuada. O que eu faço então?
  5. Como faço para que isso funcione no Windows?
  6. Existem outras maneiras de melhorar esse script?

Motivo: para um recurso semelhante ao imgur em que os usuários podem me fornecer um URL, faço o download dessa imagem e a hospedo novamente em vários tamanhos.

Jonathan Ong
fonte

Respostas:

401

Eu sugiro usar o módulo de solicitação . Baixar um arquivo é tão simples quanto o seguinte 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
fonte
1
Legal! Existe uma maneira de verificar o tamanho e o tipo de conteúdo antes de realmente fazer o download?
Jonathan Ong
2
Para onde ele faz o download das imagens?
Gofilord
17
Não está funcionando para mim (Imagem corrompida
Darth
2
@Gofilord, faça o download da imagem para o diretório raiz.
Dang
1
Você pode alterar a localização de onde eles são salvos? Se você os quisesse em uma pasta específica?
AKL012
34

Eu me deparei com esse problema há alguns dias atrás, para obter uma resposta pura do NodeJS, sugiro usar o Stream para mesclar os pedaços.

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

As versões mais recentes do Nó não funcionarão bem com cadeias binárias, portanto, mesclar partes com cadeias não é uma boa ideia ao trabalhar com dados binários.

* Apenas tome cuidado ao usar 'data.read ()', pois ele esvaziará o fluxo para a próxima operação 'read ()'. Se você quiser usá-lo mais de uma vez, guarde-o em algum lugar.

Nihey Takizawa
fonte
7
Por que não transmitir o download diretamente para o disco?
geon
teve muitos problemas ao agrupar as cordas ao criar um arquivo corrompido, mas foi o que aconteceu
Shaho
28

Você pode usar o Axios (um cliente HTTP baseado em promessas para Node.js) para baixar imagens na ordem que você escolher em um ambiente assíncrono :

npm i axios

Em seguida, você pode usar o seguinte exemplo básico para começar a baixar imagens:

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
fonte
2
Ótimo exemplo! Mas o código pouco legível, tente o padrão estilo: D
camwhite
3
@camwhite Eu prefiro ponto e vírgula . ;)
Grant Miller
1
Você realmente deve anexar os eventos 'finish' e 'error' ao fluxo de gravação, envolvê-los em uma promessa e retornar a promessa. Caso contrário, você pode tentar acessar uma imagem que ainda não foi completamente baixada.
jwerre
A espera não garantiria o download completo da imagem antes de tentar acessar? @jwerre
FabricioG
@jwerre @FabricioG Eu atualizei a função download_imagede capturar o 'finish' e 'erro' evento para a promessa voltou
Beeno Tung
10

se você quiser baixar o progresso, tente o seguinte:

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

Como usar:

  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: você deve instalar os módulos request e request-progress usando:

npm install request request-progress --save
Alnamrouti com tarifa
fonte
2
Isso funcionou muito bem, mas queria sugerir a adição de um statusCodecheque. Um statusCode 500, por exemplo, não atingirá o 'on("error", e). Ao adicionar um on('response', (response) => console.error(response.statusCode))que muito facilita a depuração,
mateuscb
1
Você pode editar minha resposta :)
Fareed Alnamrouti 10/11/16
4

Com base no exposto, se alguém precisar lidar com erros nos fluxos de gravação / leitura, usei esta versão. Observe que, stream.read()no caso de um erro de gravação, é necessário que possamos concluir a leitura e acionar closeo fluxo de leitura.

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
fonte
2
stream.read()parece estar desatualizado, gera um erronot 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
fonte
1
sua função não aciona o retorno de chamada
crockpotveggies
4

Esta é uma extensão da resposta de Cezary. Se você deseja fazer o download para um diretório específico, use isso. Além disso, use const em vez de var. É seguro assim.

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
fonte