Herunterladen von Bildern mit node.js [geschlossen]

169

Ich versuche, ein Skript zum Herunterladen von Bildern mit node.js zu schreiben. Das habe ich bisher:

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

Ich möchte dies jedoch robuster machen:

  1. Gibt es Bibliotheken, die dies tun und dies besser machen?
  2. Gibt es eine Chance, dass Antwortheader lügen (ungefähr Länge, Inhaltstyp)?
  3. Gibt es andere Statuscodes, die mich interessieren sollten? Sollte ich mich um Weiterleitungen kümmern?
  4. Ich glaube, ich habe irgendwo gelesen, dass die binaryCodierung veraltet sein wird. Was mache ich dann?
  5. Wie kann ich das unter Windows zum Laufen bringen?
  6. Gibt es noch andere Möglichkeiten, wie Sie dieses Skript verbessern können?

Warum: Für eine ähnliche Funktion wie imgur, bei der Benutzer mir eine URL geben können, lade ich dieses Bild herunter und stelle das Bild in mehreren Größen erneut bereit.

Jonathan Ong
quelle

Antworten:

401

Ich würde vorschlagen, das Anforderungsmodul zu verwenden . Das Herunterladen einer Datei ist so einfach wie der folgende Code:

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
quelle
1
Cool! Gibt es eine Möglichkeit, Größe und Inhaltstyp zu überprüfen, bevor Sie sie tatsächlich herunterladen?
Jonathan Ong
2
Wohin werden die Bilder heruntergeladen?
Gofilord
17
Funktioniert nicht für mich (Bild beschädigt
Darth
2
@Gofilord lädt das Bild in Ihr Stammverzeichnis herunter.
Dang
1
Können Sie den Speicherort ändern? Wenn Sie sie in einem bestimmten Ordner haben wollten?
AKL012
33

Ich bin vor einigen Tagen auf dieses Problem gestoßen. Für eine reine NodeJS-Antwort würde ich vorschlagen, Stream zu verwenden, um die Chunks zusammenzuführen.

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

Die neuesten Node-Versionen funktionieren nicht gut mit binären Zeichenfolgen. Daher ist das Zusammenführen von Chunks mit Zeichenfolgen keine gute Idee, wenn Sie mit binären Daten arbeiten.

* Seien Sie vorsichtig, wenn Sie 'data.read ()' verwenden. Dadurch wird der Stream für die nächste Operation 'read ()' geleert. Wenn Sie es mehrmals verwenden möchten, bewahren Sie es irgendwo auf.

Nihey Takizawa
quelle
7
Warum nicht den Download direkt auf die Festplatte streamen?
Geon
Ich hatte viele Probleme beim Zusammenfügen von Zeichenfolgen, da eine beschädigte Datei erstellt wurde, aber das hat es geschafft
Shaho,
27

Sie können Axios (einen auf Versprechen basierenden HTTP-Client für Node.js) verwenden, um Bilder in der Reihenfolge Ihrer Wahl in einer asynchronen Umgebung herunterzuladen :

npm i axios

Anschließend können Sie anhand des folgenden Basisbeispiels mit dem Herunterladen von Bildern beginnen:

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
quelle
2
Tolles Beispiel! Aber kaum lesbarer Code, versuchen Sie den Standardstil : D
camwhite
3
@ Camwhite Ich bevorzuge Semikolons . ;)
Grant Miller
1
Sie sollten dem Schreib-Stream wirklich "Finish" - und "Error" -Ereignisse hinzufügen, sie in ein Versprechen einschließen und das Versprechen zurückgeben. Andernfalls können Sie versuchen, auf ein Bild zuzugreifen, das noch nicht vollständig heruntergeladen wurde.
Jwerre
Würde das Warten nicht sicherstellen, dass das Bild vollständig heruntergeladen wird, bevor versucht wird, darauf zuzugreifen? @jwerre
FabricioG
@jwerre @FabricioG Ich habe die Funktion aktualisiert download_image, um das Ereignis 'Beenden' und 'Fehler' für das zurückgegebene Versprechen zu
erfassen
10

Wenn Sie den Fortschritt herunterladen möchten, versuchen Sie Folgendes:

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

wie benutzt man:

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

Hinweis: Sie sollten sowohl Anforderungs- als auch Anforderungsfortschrittsmodule installieren, indem Sie:

npm install request request-progress --save
Fareed Alnamrouti
quelle
2
Dies funktionierte hervorragend, wollte aber vorschlagen, einen statusCodeScheck hinzuzufügen . Ein 500 statusCode zum Beispiel trifft den nicht 'on("error", e). Durch Hinzufügen eines on('response', (response) => console.error(response.statusCode))erleichtert es das Debuggen erheblich,
mateuscb
1
Sie können meine Antwort bearbeiten :)
Fareed Alnamrouti
4

Aufbauend auf dem oben Gesagten habe ich diese Version verwendet, wenn jemand Fehler in den Schreib- / Lesestreams behandeln muss. Beachten Sie, dass stream.read()im Falle eines Schreibfehlers dieser erforderlich ist, damit wir das Lesen beenden und den Lesestream auslösen können close.

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
quelle
2
stream.read()scheint veraltet zu sein, wirft einen Fehlernot 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
quelle
1
Ihre Funktion löst nicht den Rückruf aus
crockpotveggies
4

Dies ist eine Erweiterung von Cezarys Antwort. Wenn Sie es in ein bestimmtes Verzeichnis herunterladen möchten, verwenden Sie dieses. Verwenden Sie außerdem const anstelle von var. Auf diese Weise ist es sicher.

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
quelle