Téléchargement d'images avec node.js [fermé]

169

J'essaye d'écrire un script pour télécharger des images en utilisant node.js. Voici ce que j'ai jusqu'à présent:

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

Cependant, je veux rendre cela plus robuste:

  1. Existe-t-il des bibliothèques qui font cela et qui le font mieux?
  2. Y a-t-il une chance que les en-têtes de réponse se trouvent (sur la longueur, sur le type de contenu)?
  3. Y a-t-il d'autres codes de statut dont je devrais me préoccuper? Dois-je me soucier des redirections?
  4. Je pense avoir lu quelque part que l' binaryencodage allait être obsolète. Que dois-je faire alors?
  5. Comment puis-je faire fonctionner cela sur Windows?
  6. Y a-t-il d'autres moyens d'améliorer ce script?

Pourquoi: pour une fonctionnalité similaire à imgur où les utilisateurs peuvent me donner une URL, je télécharge cette image et je réhéberge l'image dans plusieurs tailles.

Jonathan Ong
la source

Réponses:

401

Je suggère d'utiliser le module de demande . Le téléchargement d'un fichier est aussi simple que le code suivant:

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
la source
1
Cool! Existe-t-il un moyen de vérifier la taille et le type de contenu avant de le télécharger?
Jonathan Ong
2
Où télécharge-t-il les images?
Gofilord
18
Ne fonctionne pas pour moi (Image corrompue
Darth
2
@Gofilord télécharge l'image dans votre répertoire racine.
dang
1
Pouvez-vous changer l'emplacement où ils sont enregistrés? Si vous les vouliez dans un dossier spécifique?
AKL012
34

J'ai rencontré ce problème il y a quelques jours, pour une réponse pure NodeJS, je suggérerais d'utiliser Stream pour fusionner les morceaux ensemble.

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

Les dernières versions de Node ne fonctionneront pas bien avec les chaînes binaires, donc fusionner des morceaux avec des chaînes n'est pas une bonne idée lorsque vous travaillez avec des données binaires.

* Faites juste attention lorsque vous utilisez 'data.read ()', cela videra le flux pour la prochaine opération 'read ()'. Si vous souhaitez l'utiliser plusieurs fois, rangez-le quelque part.

Nihey Takizawa
la source
7
Pourquoi ne pas diffuser le téléchargement directement sur le disque?
geon
eu beaucoup de problèmes avec le découpage des chaînes ensemble car cela créait un fichier corrompu, mais cela l'a fait
Shaho
28

Vous pouvez utiliser Axios (un client HTTP basé sur la promesse pour Node.js) pour télécharger des images dans l'ordre de votre choix dans un environnement asynchrone :

npm i axios

Ensuite, vous pouvez utiliser l'exemple de base suivant pour commencer à télécharger des images:

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
la source
2
Excellent exemple! Mais le code à peine lisible, essayez le style standard : D
camwhite
3
@camwhite Je préfère les points-virgules . ;)
Grant Miller
1
Vous devriez vraiment attacher les événements 'finish' et 'error' au flux d'écriture, les envelopper dans une promesse et renvoyer la promesse. Sinon, vous pouvez essayer d'accéder à une image qui n'a pas encore été complètement téléchargée.
jwerre
L'attente ne s'assurerait-elle pas que l'image se télécharge complètement avant d'essayer d'y accéder? @jwerre
FabricioG
@jwerre @FabricioG J'ai mis à jour la fonction download_imagepour capturer les événements 'finish' et 'error' pour la promesse retournée
Beeno Tung
10

si vous voulez un téléchargement progressif, essayez ceci:

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

comment utiliser:

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

Remarque: vous devez installer les modules de requête et de progression de la requête en utilisant:

npm install request request-progress --save
Tarif Alnamrouti
la source
2
Cela fonctionnait très bien, mais je voulais suggérer d'ajouter un statusCodechèque. Un statusCode 500 par exemple, n'atteindra pas le 'on("error", e). En ajoutant un, on('response', (response) => console.error(response.statusCode))il facilite grandement le débogage,
mateuscb
1
Vous pouvez modifier ma réponse :)
Tarif Alnamrouti
4

Sur la base de ce qui précède, si quelqu'un a besoin de gérer des erreurs dans les flux d'écriture / lecture, j'ai utilisé cette version. Notez stream.read()qu'en cas d'erreur d'écriture, c'est nécessaire pour que nous puissions terminer la lecture et déclencher closesur le flux de lecture.

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
la source
2
stream.read()semble être obsolète, lance une erreurnot 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
la source
1
votre fonction ne déclenche pas le rappel
crockpotveggies
4

Ceci est une extension de la réponse de Cezary. Si vous souhaitez le télécharger dans un répertoire spécifique, utilisez ceci. Utilisez également const au lieu de var. C'est sûr de cette façon.

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
la source