Как скачать файл с 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.