Как скачать файл с Node.js (без использования сторонних библиотек)?


443

Как загрузить файл с Node.js без использования сторонних библиотек ?

Мне не нужно ничего особенного. Я хочу только загрузить файл с указанного URL, а затем сохранить его в указанном каталоге.


5
"скачать файл с помощью node.js" - вы имеете в виду загрузку на сервер? или получить файл с удаленного сервера, используя ваш сервер? или передать файл клиенту для загрузки с вашего сервера node.js?
Иосиф

67
«Я только хочу скачать файл с заданного 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
Это зависит от типа req url, если вы запрашиваете, httpsвы должны использовать, httpsиначе это вызовет ошибку.
Кришнадас ПК

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
Есть ли способ увидеть скорость загрузки? Как можно отследить сколько мб / с? Спасибо!
Тино Каер

138

Как сказала Мишель Тилли, но с соответствующим потоком управления:

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 Roman за указание, что cbдолжно быть передано file.close, а не вызвано явно.


3
обратный звонок сбивает меня с толку. если я сейчас призываю download(), как бы я это сделал? Что бы я привел в качестве cbаргумента? У меня есть, download('someURI', '/some/destination', cb)но я не понимаю, что положить в 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. Круто!
Тиаго С. С Вентура

@ventura да, кстати, есть также нативный 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обратный вызов через close, вы можете получить гонку между доступом к файлу и фактически готовым файлом.


2
Зачем вы храните запрос в переменной?
polkovnikov.ph

он «сохраняет» его в переменной, чтобы он не стал глобальной переменной по умолчанию.
Филк

@philk Как вы знаете, глобальная переменная создается, если var request =удаляется?
ma11hew28

Вы правы, нет необходимости сохранять запрос, он все равно не используется. Это ты о чем?
Филк

17

Возможно, файл node.js изменился, но, похоже, есть некоторые проблемы с другими решениями (с использованием узла 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. Временный файл не удален на 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) он, вероятно, должен отклонять объекты Error, а не строки, 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. Утечка памяти происходит только в том случае, если загрузка файла занимает слишком много времени.
A-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/issues/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это не сторонняя библиотека?
Мурат Чорлу

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

IfТак если вы используете конвейер , он закроет все остальные потоки и удостоверится, что нет утечек памяти.

Рабочий пример:

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 случайный уникальный

    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-обещание, который также очень прост в использовании. 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.