如何使用Fetch API下载和保存文件?(Node.js)

Glo*_*omy 9 javascript xmlhttprequest fetch node.js fetch-api

我有一个可能很大的文件(100+ Mb)的url,如何使用访存将其保存在本地目录中?

我环顾四周,但是关于如何执行此操作似乎没有很多资源/教程。

谢谢!

ant*_*nok 40

这里较旧的答案涉及node-fetch,但因为Node.js v18.x这可以在没有额外依赖的情况下完成。

获取响应的主体是一个网络流fs可以使用将其转换为 Node流Readable.fromWeb,然后将其通过管道传输到由 创建的写入流中fs.createWriteStream。如果需要,可以将生成的流转换为Promise使用 Promise 版本的stream.finished.

const fs = require('fs');
const { Readable } = require('stream');
const { finished } = require('stream/promises');

const stream = fs.createWriteStream('output.txt');
const { body } = await fetch('https://example.com');
await finished(Readable.fromWeb(body).pipe(stream));
Run Code Online (Sandbox Code Playgroud)

  • @RonH不幸的是,根据/sf/ask/4454108011/,看起来有2个不同的“ReadableStream”定义-r。您应该能够将“body”从“stream/web”转换为正确的“ReadableStream”;即 `import { ReadableStream } from 'stream/web';` 和 `body as ReadableStream<any>`。 (7认同)
  • “‘ReadableStream<Uint8Array>’类型的参数不可分配给‘ReadableStream<any>’类型的参数。类型“ReadableStream<Uint8Array>”缺少类型“ReadableStream<any>”中的以下属性:值、[Symbol.asyncIterator]ts(2345)` (5认同)
  • 这也可以很好地压缩在一行 `const download = async (url, path) => Readable.fromWeb((await fetch(url)).body).pipe(fs.createWriteStream(path))` (2认同)
  • 在开始写入流之前是否会下载整个文件(“await fetch(...)”)? (2认同)
  • @1252748 `await fetch(...)` 在完全接收到响应标头之后但在接收到响应正文之前完成。正文在到达时将流式传输到文件中。当正文流仍在进行时,可以省略第二个“await”来执行其他任务。 (2认同)

Ahm*_*sih 17

如果您想避免像在另一个非常好的答案中那样显式地创建 Promise,并且可以构建整个 100+ MB 文件的缓冲区,那么您可以做一些更简单的事情:

const fetch = require('node-fetch');
const {writeFile} = require('fs');
const {promisify} = require('util');
const writeFilePromise = promisify(writeFile);

function downloadFile(url, outputPath) {
  return fetch(url)
      .then(x => x.arrayBuffer())
      .then(x => writeFilePromise(outputPath, Buffer.from(x)));
}
Run Code Online (Sandbox Code Playgroud)

但另一个答案将更节省内存,因为它将接收到的数据流直接通过管道传输到文件中,而不会将所有数据流累积到缓冲区中。


小智 11

使用Fetch API,您可以编写一个可以从如下网址下载的函数:

const downloadFile = (async (url, path) => {
  const res = await fetch(url);
  const fileStream = fs.createWriteStream(path);
  await new Promise((resolve, reject) => {
      res.body.pipe(fileStream);
      res.body.on("error", (err) => {
        reject(err);
      });
      fileStream.on("finish", function() {
        resolve();
      });
    });
});
Run Code Online (Sandbox Code Playgroud)

  • 这会产生错误:res.body.pipe 不是函数。NodeJS v18 (10认同)
  • 您甚至可以通过编写“res.body.on('error',reject);”和“fileStream.on('finish',resolve);”来使其更短一些。 (3认同)
  • @tinkerr 尝试导入并使用“node-fetch”而不是正常的获取 (3认同)
  • 这在节点 v18 上不起作用。我认为 /sf/answers/5230597291/ 是 2023 年更好的解决方案。 (2认同)

Iho*_*yuk 11

const {createWriteStream} = require('fs');
const {pipeline} = require('stream/promises');
const fetch = require('node-fetch');

const downloadFile = async (url, path) => pipeline(
    (await fetch(url)).body,
    createWriteStream(path)
);
Run Code Online (Sandbox Code Playgroud)