Pobieranie obrazów za pomocą node.js [zamknięte]

169

Próbuję napisać skrypt do pobierania obrazów za pomocą node.js. Oto, co mam do tej pory:

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

Chcę jednak uczynić to solidniejszym:

  1. Czy są biblioteki, które robią to i robią to lepiej?
  2. Czy jest szansa, że ​​nagłówki odpowiedzi kłamią (o długości, o typie treści)?
  3. Czy są jakieś inne kody statusu, które powinny mnie zainteresować? Czy powinienem zawracać sobie głowę przekierowaniami?
  4. Myślę, że gdzieś przeczytałem, że binarykodowanie zostanie wycofane. Co mam wtedy zrobić?
  5. Jak mogę to uruchomić w systemie Windows?
  6. Czy są jakieś inne sposoby na ulepszenie tego skryptu?

Dlaczego: w przypadku funkcji podobnej do imgur, w której użytkownicy mogą podać mi adres URL, pobieram ten obraz i ponownie hostuję obraz w wielu rozmiarach.

Jonathan Ong
źródło

Odpowiedzi:

401

Sugerowałbym użycie modułu żądań . Pobieranie pliku jest tak proste, jak poniższy kod:

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
źródło
1
Chłodny! Czy istnieje sposób sprawdzenia rozmiaru i typu zawartości przed pobraniem?
Jonathan Ong
2
Skąd pobiera obrazy?
Gofilord
17
Nie działa dla mnie (obraz uszkodzony
Darth
2
@Gofilord pobiera obraz do katalogu głównego.
dang
1
Czy możesz zmienić lokalizację, w której są zapisywane? Gdybyś chciał je mieć w określonym folderze?
AKL012
34

Napotkałem ten problem kilka dni temu, aby uzyskać czystą odpowiedź NodeJS, sugerowałbym użycie Stream do scalenia fragmentów razem.

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

Najnowsze wersje Node nie będą dobrze współpracować z ciągami binarnymi, więc łączenie fragmentów z ciągami nie jest dobrym pomysłem podczas pracy z danymi binarnymi.

* Po prostu bądź ostrożny używając 'data.read ()', spowoduje to opróżnienie strumienia dla następnej operacji 'read ()'. Jeśli chcesz go użyć więcej niż raz, przechowuj go gdzieś.

Nihey Takizawa
źródło
7
Dlaczego nie przesyłać strumieniowo pobierania bezpośrednio na dysk?
geon
miał wiele problemów z dzieleniem ciągów znaków, ponieważ utworzył uszkodzony plik, ale to się udało
Shaho
27

Możesz użyć Axios ( klienta HTTP opartego na obietnicy dla Node.js) do pobierania obrazów w wybranej kolejności w środowisku asynchronicznym :

npm i axios

Następnie możesz skorzystać z następującego podstawowego przykładu, aby rozpocząć pobieranie obrazów:

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
źródło
2
Świetny przykład! Ale ledwo czytelny kod, wypróbuj standardowy styl: D
camwhite
3
@camwhite Wolę średniki . ;)
Grant Miller,
1
Naprawdę powinieneś dołączyć zdarzenia „finish” i „error” do strumienia zapisu, opakować je w Obietnicę i zwrócić obietnicę. W przeciwnym razie możesz spróbować uzyskać dostęp do obrazu, który nie został jeszcze w całości pobrany.
jwerre
Czy oczekiwanie nie zapewniłoby całkowitego pobrania obrazu przed próbą uzyskania dostępu? @jwerre
FabricioG
@jwerre @FabricioG Zaktualizowałem funkcję, download_imageaby przechwytywać zdarzenie „zakończenie” i „błąd” dla zwróconej obietnicy
Beeno Tung
10

jeśli chcesz postęp pobierania, spróbuj tego:

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

jak używać:

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

uwaga: należy zainstalować oba moduły request i request-progress przy użyciu:

npm install request request-progress --save
Fareed Alnamrouti
źródło
2
To działało świetnie, ale chciał zasugerować dodanie statusCodeczeku. Na przykład kod statusu 500 nie trafi do 'on("error", e). Dodanie on('response', (response) => console.error(response.statusCode))
znacznika
1
Możesz edytować moją odpowiedź :)
Fareed Alnamrouti
4

Opierając się na powyższym, jeśli ktoś potrzebuje obsługi błędów w strumieniach zapisu / odczytu, użyłem tej wersji. Zwróć uwagę, że stream.read()w przypadku błędu zapisu jest to wymagane, abyśmy mogli zakończyć odczyt i wyzwolić closestrumień odczytu.

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
źródło
2
stream.read()wydaje się być nieaktualny, wyrzuca błądnot 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
źródło
1
twoja funkcja nie wyzwala wywołania zwrotnego
crockpotveggies
4

To jest rozszerzenie odpowiedzi Cezarego. Jeśli chcesz pobrać go do określonego katalogu, użyj tego. Użyj również const zamiast var. W ten sposób jest bezpieczny.

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
źródło