Як завантажити файл з Node.js (без використання сторонніх бібліотек)?


443

Як завантажити файл з Node.js без використання сторонніх бібліотек ?

Мені нічого особливого не потрібно. Я хочу лише завантажити файл із заданої URL-адреси, а потім зберегти його у заданій теці.


5
"завантажити файл з node.js" - ви маєте на увазі завантаження на сервер? або отримати файл з віддаленого сервера за допомогою вашого сервера? або подати файл клієнту для завантаження з вашого сервера node.js?
Йосиф

66
"Я хочу лише завантажити файл із заданої URL-адреси, а потім зберегти його у заданій директорії", це здається досить зрозумілим. :)
Мішель Тіллі

34
Джозеф робить неправильне твердження, що всі вузлові процеси - це серверні процеси
lededje

1
@lededje Що заважає серверному процесу завантажувати файл і зберігати його у каталозі на сервері? Це переважно зробити.
Герман

Відповіді:


598

Ви можете створити HTTP- GETзапит і передати його responseв файл, що записується:

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

const file = fs.createWriteStream("file.jpg");
const request = http.get("http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg", function(response) {
  response.pipe(file);
});

Якщо ви хочете підтримати збір інформації в командному рядку - наприклад, вказати цільовий файл чи каталог або URL -, перевірте щось на зразок Commander .


3
Я отримав наступний висновок на консоль , коли я запустив цей скрипт: node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ Error: connect ECONNREFUSED at errnoException (net.js:646:11) at Object.afterConnect [as oncomplete] (net.js:637:18) .
Андерсон Грін

Спробуйте використовувати іншу URL-адресу в http.getрядку; можливо http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg(і замінити file.pngна file.jpg).
Мішель Тіллі

8
Чи правильно цей код закриває файл, коли сценарій закінчується, або він втрачає дані?
філк

2
@quantumpotato Подивіться на відповідь, яку ви отримуєте від свого запиту
Мішель Тіллі

6
Це залежить від типу URL-адреси req, якщо ви звертаєтесь із запитом, httpsви повинні використовувати httpsйого, інакше це призведе до помилки.
Krishnadas PC

523

Не забудьте впоратися з помилками! Наступний код заснований на відповіді Августо Романа.

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  }).on('error', function(err) { // Handle errors
    fs.unlink(dest); // Delete the file async. (But we don't check the result)
    if (cb) cb(err.message);
  });
};

2
@ Вінс-юань download()сам pipeздатний?
rasx

@theGrayFox Оскільки код у цій відповіді набагато довший від прийнятого. :)
pootow

2
@Abdul Здається, що ви дуже новачок у node.js / javascript. Погляньте на цей підручник: tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm Це не складно.
Вінс Юань

1
@ Abdul, можливо, було б добре, якщо ви поділитесь з рештою класу тим, що ви зрозуміли?
Curtwagner1984

5
Чи є спосіб побачити швидкість завантаження? Можливо, можна відслідковувати скільки мб / с? Дякую!
Тіно Каер

137

Як сказала Мішель Тіллі, але з відповідним контрольним потоком:

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);
    });
  });
}

Не чекаючи finishподії, наївні сценарії можуть закінчитися неповним файлом.

Редагувати: Дякую @Augusto Роман за те, що він вказав, що cbслід передати file.close, а не викликати прямо.


3
зворотний дзвінок мене бентежить. якщо я зараз посилаюсь download(), як би це зробити? Що б я поставив як cbаргумент? Я маю, download('someURI', '/some/destination', cb)але не розумію, що поставити на тесту
Абдул

1
@Abdul Ви визначаєте зворотний виклик з функцією, лише якщо вам потрібно щось зробити, коли файл успішно отриманий.
CatalinBerta

65

Якщо говорити про помилки обробки, то ще краще слухати помилки із запитом. Я б навіть підтвердив, перевіривши код відповіді. Тут він вважається успішним лише для 200 кодів відповідей, але інші коди можуть бути хорошими.

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

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);

    const request = http.get(url, (response) => {
        // check if response is success
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        response.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request error too
    request.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result) 
        return cb(err.message);
    });
};

Незважаючи на відносну простоту цього коду, я б радив використовувати модуль запиту, оскільки він обробляє багато інших протоколів (привіт HTTPS!), Які не підтримуються в початковому режимі http.

Це було б зроблено так:

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

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);
    const sendReq = request.get(url);

    // verify response code
    sendReq.on('response', (response) => {
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        sendReq.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request errors
    sendReq.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result)
        return cb(err.message);
    });
};

2
Модуль запиту просто працює прямо для HTTP. Класно!
Thiago C. S Ventura

@ventura yep, btw, також є вбудований модуль https, який тепер може захищати безпечні з'єднання.
Бузут

Це більше схильний до помилок без сумнівів. У будь-якому випадку, коли використання модуля запиту є варіантом, я б радив це як більш високий рівень, так і простіший та ефективніший.
Бузут

2
@ Алекс, ні, це повідомлення про помилку і повернення є. Тож якщо response.statusCode !== 200cb on finishніколи не буде викликаний.
Бузут

1
Дякуємо, що показали приклад за допомогою модуля запиту.
Піт Елвін

48

Відповідь gfxmonk має дуже тугу перегону даних між зворотним викликом та file.close()завершенням. file.close()насправді приймає зворотний виклик, який викликається після завершення закриття. Інакше негайне використання файлу може не вдатися (дуже рідко!).

Повне рішення:

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  });
}

Не чекаючи завершення події, наївні сценарії можуть закінчитися неповним файлом. Не плануючи cbзворотний виклик через закриття, ви можете отримати гонку між доступом до файлу та фактично готовим файлом.


2
Для чого ви зберігаєте запит у змінну?
polkovnikov.ph

він "зберігає" його в змінну, щоб вона не стала глобальною змінною за замовчуванням.
філк

@philk Як ви знаєте, що глобальна змінна створюється, якщо var request =її видалити?
ma11hew28

Ви маєте рацію, запит зберігати не потрібно, його все одно не використовують. Це ви маєте на увазі?
філк

17

Можливо, node.js змінився, але, схоже, є проблеми з іншими рішеннями (використовуючи node v8.1.2):

  1. Вам не потрібно дзвонити file.close()на finishзахід. За замовчуванням fs.createWriteStreamфункція "autoClose" встановлюється: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close()слід викликати помилку. Можливо, це не потрібно, коли файл видалено ( unlink()), але зазвичай це: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. Файл Temp не видалено statusCode !== 200
  4. fs.unlink() без зворотного дзвінка застарілий (виводить попередження)
  5. Якщо destфайл існує; це перекрито

Нижче наведено модифіковане рішення (з використанням ES6 та обіцянок), яке вирішує ці проблеми.

const http = require("http");
const fs = require("fs");

function download(url, dest) {
    return new Promise((resolve, reject) => {
        const file = fs.createWriteStream(dest, { flags: "wx" });

        const request = http.get(url, response => {
            if (response.statusCode === 200) {
                response.pipe(file);
            } else {
                file.close();
                fs.unlink(dest, () => {}); // Delete temp file
                reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`);
            }
        });

        request.on("error", err => {
            file.close();
            fs.unlink(dest, () => {}); // Delete temp file
            reject(err.message);
        });

        file.on("finish", () => {
            resolve();
        });

        file.on("error", err => {
            file.close();

            if (err.code === "EEXIST") {
                reject("File already exists");
            } else {
                fs.unlink(dest, () => {}); // Delete temp file
                reject(err.message);
            }
        });
    });
}

1
Два коментарі з цього приводу: 1) він, ймовірно, повинен відхиляти об'єкти помилок, а не рядки; 2) fs.unlink спокійно проковтне помилки, які, можливо, не обов'язково будуть те, що ви хочете зробити
Річард Нієнабер

1
Це чудово працює! І якщо ваші URL - адреси з допомогою HTTPS, просто замінити const https = require("https");наconst http = require("http");
Russ

15

Рішення із затримкою часу, запобігання витоку пам'яті:

Наступний код заснований на відповіді Брендона Тіллі:

var http = require('http'),
    fs = require('fs');

var request = http.get("http://example12345.com/yourfile.html", function(response) {
    if (response.statusCode === 200) {
        var file = fs.createWriteStream("copy.html");
        response.pipe(file);
    }
    // Add timeout.
    request.setTimeout(12000, function () {
        request.abort();
    });
});

Не створюйте файл, коли ви отримуєте помилку, і бажайте використовувати тайм-аут, щоб закрити ваш запит після X секунди.


1
це лише файл, не має протоколу чи сервера для завантаження з ...http.get("http://example.com/yourfile.html",function(){})
mjz19910

Чи є витік пам'яті у цій відповіді: stackoverflow.com/a/22793628/242933 ?
ma11hew28

Ви можете додати тайм-аут, як і я http.get. Витік пам'яті відбувається лише в тому випадку, якщо файл завантажується занадто багато часу.
А-312

13

для тих, хто прийшов у пошуках способу, що базується на обіцянках у стилі es6, я думаю, це було б щось на зразок:

var http = require('http');
var fs = require('fs');

function pDownload(url, dest){
  var file = fs.createWriteStream(dest);
  return new Promise((resolve, reject) => {
    var responseSent = false; // flag to make sure that response is sent only once.
    http.get(url, response => {
      response.pipe(file);
      file.on('finish', () =>{
        file.close(() => {
          if(responseSent)  return;
          responseSent = true;
          resolve();
        });
      });
    }).on('error', err => {
        if(responseSent)  return;
        responseSent = true;
        reject(err);
    });
  });
}

//example
pDownload(url, fileLocation)
  .then( ()=> console.log('downloaded file no issues...'))
  .catch( e => console.error('error while downloading', e));

2
responseSetпрапор викликав, чому я не мав часу досліджувати, мій файл буде завантажений неповно. Жодних помилок не з’явилось, але файл .txt, який я заповнював, мав половину рядків, які там мали бути. Видалення логіки прапора виправлено. Просто хотілося вказати на те, чи є у когось проблеми з підходом. І все-таки +1
Мілан Велебіт

6

Код Вінса Юана великий, але, здається, щось не так.

function download(url, dest, callback) {
    var file = fs.createWriteStream(dest);
    var request = http.get(url, function (response) {
        response.pipe(file);
        file.on('finish', function () {
            file.close(callback); // close() is async, call callback after close completes.
        });
        file.on('error', function (err) {
            fs.unlink(dest); // Delete the file async. (But we don't check the result)
            if (callback)
                callback(err.message);
        });
    });
}

чи можемо ми вказати папку призначення?

6

Я віддаю перевагу request (), оскільки ви можете використовувати як http, так і https.

request('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
  .pipe(fs.createWriteStream('cat.jpg'))

Схоже, що Запит був застарілий github.com/request/request/isissue/3142 "As of Feb 11th 2020, request is fully deprecated. No new changes are expected to land. In fact, none have landed for some time."
Майкл

5
const download = (url, path) => new Promise((resolve, reject) => {
http.get(url, response => {
    const statusCode = response.statusCode;

    if (statusCode !== 200) {
        return reject('Download error!');
    }

    const writeStream = fs.createWriteStream(path);
    response.pipe(writeStream);

    writeStream.on('error', () => reject('Error writing to file!'));
    writeStream.on('finish', () => writeStream.close(resolve));
});}).catch(err => console.error(err));

5

Привіт , Я думаю, ви можете використовувати модуль child_process і команду curl.

const cp = require('child_process');

let download = async function(uri, filename){
    let command = `curl -o ${filename}  '${uri}'`;
    let result = cp.execSync(command);
};


async function test() {
    await download('http://zhangwenning.top/20181221001417.png', './20181221001417.png')
}

test()

Крім того, коли ви хочете завантажити великі «декілька файлів», ви можете використовувати кластерний модуль, щоб використовувати більше ядер процесора.


4

Ви можете використовувати https://github.com/douzi8/ajax-request#download

request.download('http://res.m.ctrip.com/html5/Content/images/57.png', 
  function(err, res, body) {}
);

2
Він повертає символ сміття, якщо ім'я файлу не є ascii, як, наприклад, ім'я файлу японською мовою.
Діпак Гоел

4
Як ви вважаєте, ajax-requestце не стороння бібліотека?
Murat Çorlu

4

Завантажте за допомогою обіцянок, які вирішують читабельний потік. введіть додаткову логіку для обробки переадресації.

var http = require('http');
var promise = require('bluebird');
var url = require('url');
var fs = require('fs');
var assert = require('assert');

function download(option) {
    assert(option);
    if (typeof option == 'string') {
        option = url.parse(option);
    }

    return new promise(function(resolve, reject) {
        var req = http.request(option, function(res) {
            if (res.statusCode == 200) {
                resolve(res);
            } else {
                if (res.statusCode === 301 && res.headers.location) {
                    resolve(download(res.headers.location));
                } else {
                    reject(res.statusCode);
                }
            }
        })
        .on('error', function(e) {
            reject(e);
        })
        .end();
    });
}

download('http://localhost:8080/redirect')
.then(function(stream) {
    try {

        var writeStream = fs.createWriteStream('holyhigh.jpg');
        stream.pipe(writeStream);

    } catch(e) {
        console.error(e);
    }
});

1
302 також є кодом статусу HTTP для переадресації URL-адреси, тому вам слід скористатися цим [301,302] .indexOf (res.statusCode)! == -1 у виписці if
sidanmor

Питання були конкретні, щоб не включати сторонні режими :)
Девід Гетті

3

Якщо ви використовуєте метод експрес-використання res.download (). в іншому випадку використовується модуль fs.

app.get('/read-android', function(req, res) {
   var file = "/home/sony/Documents/docs/Android.apk";
    res.download(file) 
}); 

(або)

   function readApp(req,res) {
      var file = req.fileName,
          filePath = "/home/sony/Documents/docs/";
      fs.exists(filePath, function(exists){
          if (exists) {     
            res.writeHead(200, {
              "Content-Type": "application/octet-stream",
              "Content-Disposition" : "attachment; filename=" + file});
            fs.createReadStream(filePath + file).pipe(res);
          } else {
            res.writeHead(400, {"Content-Type": "text/plain"});
            res.end("ERROR File does NOT Exists.ipa");
          }
        });  
    }

3

OТак, якщо ви використовуєте трубопровід , він закрив би всі інші потоки та переконався у відсутності витоків пам'яті.

Робочий приклад:

const http = require('http');
const { pipeline } = require('stream');
const fs = require('fs');

const file = fs.createWriteStream('./file.jpg');

http.get('http://via.placeholder.com/150/92c952', response => {
  pipeline(
    response,
    file,
    err => {
      if (err)
        console.error('Pipeline failed.', err);
      else
        console.log('Pipeline succeeded.');
    }
  );
});

З моєї відповіді на тему "Яка різниця між .pipe та .pipeline у ​​потоках" .


2

Шлях: тип img: jpg випадковий uniqid

    function resim(url) {

    var http = require("http");
    var fs = require("fs");
    var sayi = Math.floor(Math.random()*10000000000);
    var uzanti = ".jpg";
    var file = fs.createWriteStream("img/"+sayi+uzanti);
    var request = http.get(url, function(response) {
  response.pipe(file);
});

        return sayi+uzanti;
}

0

Без бібліотеки можна було б просто відзначити. Ось декілька:

  • Не вдається обробити переспрямування http, як ця URL-адреса https://calibre-ebook.com/dist/portable яка є двійковою.
  • Модуль http не може https URL-адресу, ви отримаєте Protocol "https:" not supported.

Ось моя пропозиція:

  • Виклик системного інструменту, як wget абоcurl
  • використовуйте такий інструмент, як node-wget-promis, який також дуже простий у використанні. var wget = require('node-wget-promise'); wget('http://nodejs.org/images/logo.svg');

0
function download(url, dest, cb) {

  var request = http.get(url, function (response) {

    const settings = {
      flags: 'w',
      encoding: 'utf8',
      fd: null,
      mode: 0o666,
      autoClose: true
    };

    // response.pipe(fs.createWriteStream(dest, settings));
    var file = fs.createWriteStream(dest, settings);
    response.pipe(file);

    file.on('finish', function () {
      let okMsg = {
        text: `File downloaded successfully`
      }
      cb(okMsg);
      file.end(); 
    });
  }).on('error', function (err) { // Handle errors
    fs.unlink(dest); // Delete the file async. (But we don't check the result)
    let errorMsg = {
      text: `Error in file downloadin: ${err.message}`
    }
    if (cb) cb(errorMsg);
  });
};

0

Ви можете спробувати скористатись res.redirectURL-адресою завантаження файлів https, і тоді воно буде завантажувати файл.

Подібно до: res.redirect('https//static.file.com/file.txt');


0
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.cryptocompare.com/media/19684/doge.png', 'icons/taskks12.png', function(){
    console.log('done');
});

0

Ось ще один спосіб вирішити цю проблему без сторонніх залежностей, а також пошуку переспрямувань:

        var download = function(url, dest, cb) {
            var file = fs.createWriteStream(dest);
            https.get(url, function(response) {
                if ([301,302].indexOf(response.statusCode) !== -1) {
                    body = [];
                    download(response.headers.location, dest, cb);
                  }
              response.pipe(file);
              file.on('finish', function() {
                file.close(cb);  // close() is async, call cb after close completes.
              });
            });
          }

0

download.js (тобто /project/utils/download.js)

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

const download = (uri, filename, callback) => {
    request.head(uri, (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);
    });
};

module.exports = { download };


app.js

... 
// part of imports
const { download } = require('./utils/download');

...
// add this function wherever
download('https://imageurl.com', 'imagename.jpg', () => {
  console.log('done')
});


-4
var requestModule=require("request");

requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));

5
Демпінги коду, як правило, не корисні і можуть бути знижені або видалені. Варто було б редагувати, щоб хоча б пояснити, що робить код для майбутніх відвідувачів.
Клопи
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.