【问题标题】:node.js axios download file stream and writeFilenode.js axios下载文件流和writeFile
【发布时间】:2019-08-17 21:00:58
【问题描述】:

我想用axios 下载一个pdf 文件并用fs.writeFile 保存在磁盘(服务器端)上,我试过了:

axios.get('https://xxx/my.pdf', {responseType: 'blob'}).then(response => {
    fs.writeFile('/temp/my.pdf', response.data, (err) => {
        if (err) throw err;
        console.log('The file has been saved!');
    });
});

文件已保存,但内容已损坏...

如何正确保存文件?

【问题讨论】:

  • 你得到控制台日志“文件已保存”,文件被创建,只是内容有误?
  • 你在哪里调用 axios.get ?它不会等待文件被写入。更好地承诺 fs 或使用 fs-extra 或使用 fs 的承诺方法。并使用类似 return fs.writeFile(...)
  • @RolandStarke 是的,文件已保存
  • 我已经发布了一种更简洁的方法来使用下面的节点流管道解决问题。它与公认答案提出的概念相同。 stackoverflow.com/a/64925465/3476378

标签: javascript node.js axios


【解决方案1】:

实际上,我认为之前接受的答案有一些缺陷,因为它不能正确处理 writestream,所以如果你在 Axios 给你响应之后调用“then()”,你最终会得到一个部分下载的文件.

当下载稍大的文件时,这是一个更合适的解决方案:

export async function downloadFile(fileUrl: string, outputLocationPath: string) {
  const writer = createWriteStream(outputLocationPath);

  return Axios({
    method: 'get',
    url: fileUrl,
    responseType: 'stream',
  }).then(response => {

    //ensure that the user can call `then()` only when the file has
    //been downloaded entirely.

    return new Promise((resolve, reject) => {
      response.data.pipe(writer);
      let error = null;
      writer.on('error', err => {
        error = err;
        writer.close();
        reject(err);
      });
      writer.on('close', () => {
        if (!error) {
          resolve(true);
        }
        //no need to call the reject here, as it will have been called in the
        //'error' stream;
      });
    });
  });
}

这样,您可以调用downloadFile(),在返回的promise上调用then(),并确保下载的文件将完成处理。

或者,如果你使用更现代的 NodeJS 版本,你可以试试这个:

import * as stream from 'stream';
import { promisify } from 'util';

const finished = promisify(stream.finished);

export async function downloadFile(fileUrl: string, outputLocationPath: string): Promise<any> {
  const writer = createWriteStream(outputLocationPath);
  return Axios({
    method: 'get',
    url: fileUrl,
    responseType: 'stream',
  }).then(async response => {
    response.data.pipe(writer);
    return finished(writer); //this is a Promise
  });
}

【讨论】:

  • 这是正确的,完全解决了与部分数据错误相关的问题
  • 这应该是公认的答案。它修复了部分下载错误
  • 我在下面发布了一个更简洁的方法,与您使用流管道的相同概念:stackoverflow.com/a/64925465/3476378
  • 最好在将响应流传输到写入流之前注册关闭和错误事件处理程序。
  • response.data.pipe 不是函数
【解决方案2】:

您可以简单地使用response.data.pipefs.createWriteStream 将响应通过管道传输到文件

axios({
    method: "get",
    url: "https://xxx/my.pdf",
    responseType: "stream"
}).then(function (response) {
    response.data.pipe(fs.createWriteStream("/temp/my.pdf"));
});

【讨论】:

  • 非常感谢!!一直在寻找这个
  • 这个答案不完整,因为当你下载一些较大的文件时,管道会给你多个事件。这段代码不会等到整个文件都下载完毕后才能调用then。查看我的解决方案,找到我认为更完整的解决方案。
  • response.data.pipe 不是函数
  • 如果不在本地存储上下载文件,那么我尝试了 node.js 中的 res.sendFile
【解决方案3】:
// This works perfectly well! 
const axios = require('axios'); 

axios.get('http://www.sclance.com/pngs/png-file-download/png_file_download_1057991.png', {responseType: "stream"} )  
.then(response => {  
// Saving file to working directory  
    response.data.pipe(fs.createWriteStream("todays_picture.png"));  
})  
    .catch(error => {  
    console.log(error);  
});  

【讨论】:

  • 欢迎来到 StackOverflow!您可能需要提供一些解释以配合您的代码示例。
  • 这不会正常工作,因为它不会等到文件完成下载后再继续承诺链。
【解决方案4】:

文件损坏的问题是由于节点流中的背压。您可能会发现此链接对阅读很有用:https://nodejs.org/es/docs/guides/backpressuring-in-streams/

我不太喜欢在 JS 代码中使用 Promise 基声明性对象,因为我觉得它会污染实际的核心逻辑并使代码难以阅读。最重要的是,您必须提供事件处理程序和侦听器以确保代码完成。

下面给出了与公认答案提出的相同逻辑的更简洁的方法。它使用流管道的概念。

const util = require('util');
const stream = require('stream');
const pipeline = util.promisify(stream.pipeline);

const downloadFile = async () => {
  try {
    const request = axios.get('https://xxx/my.pdf', {
      responseType: 'stream',
    });
    await pipeline(request.data, fs.createWriteStream('/temp/my.pdf'));
    console.log('download pdf pipeline successful');   
  } catch (error) {
    console.error('download pdf pipeline failed', error);
  }
}

exports.downloadFile = downloadFile

我希望你觉得这很有用。

【讨论】:

  • 为什么响应类型是 blob 而不是流?
  • 我得到“stream.on is not a function”错误
  • 我让它像这样工作: const resp = await axios.get(....);等待管道(resp.data, fs.createWriteStream(...))
  • @1252748 blob 是仅浏览器的选项。
【解决方案5】:

node fileSystem writeFile 默认将数据编码为 UTF8。这可能是您的问题。

尝试将您的编码设置为null 并跳过对接收​​到的数据进行编码:

fs.writeFile('/temp/my.pdf', response.data, {encoding: null}, (err) => {...}

如果您只声明编码而没有其他选项,您也可以将编码标记为字符串(而不是选项对象)。字符串将作为编码值处理。像这样:

fs.writeFile('/temp/my.pdf', response.data, 'null', (err) => {...}

更多阅读fileSystem API write_file

【讨论】:

  • 虽然此代码可能会解决问题,但 including an explanation 关于如何以及为何解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提出问题的人。请edit您的答案添加解释并说明适用的限制和假设。
  • @double-beep tnx 供您发表评论。我已经编辑了一些解释,并从node fileSystem API 阅读了有关 writeFile 函数的材料。 :)
【解决方案6】:

我试过了,我确信使用response.data.pipefs.createWriteStream可以工作。


另外,我想补充一下我的情况和解决方案

情况:

  • 使用koa开发node.js服务器
  • 使用axios通过url获取pdf
  • 使用pdf-parse解析pdf
  • 提取pdf的一些信息并以json的形式返回给浏览器

解决方案:

const Koa = require('koa');
const app = new Koa();
const axios = require('axios')
const fs = require("fs")
const pdf = require('pdf-parse');
const utils = require('./utils')

app.listen(process.env.PORT || 3000)

app.use(async (ctx, next) => {
      let url = 'https://path/name.pdf'
      let resp = await axios({
          url: encodeURI(url),
          responseType: 'arraybuffer'
        })

        let data = await pdf(resp.data)

        ctx.body = {
            phone: utils.getPhone(data.text),
            email: utils.getEmail(data.text),
        }
})

这种方案不需要写文件和读文件,效率更高。

【讨论】:

    【解决方案7】:

    以下来自https://gist.github.com/senthilmpro/072f5e69bdef4baffc8442c7e696f4eb?permalink_comment_id=3620639#gistcomment-3620639 的代码对我有用

    const res = await axios.get(url, { responseType: 'arraybuffer' });
    fs.writeFileSync(downloadDestination, res.data);
    

    【讨论】:

      【解决方案8】:
      import download from "downloadjs";
      
      export const downloadFile = async (fileName) => {
          axios({
              method: "get",
              url: `/api/v1/users/resume/${fileName}`,
              responseType: "blob",
          }).then(function (response) {
              download(response.data, fileName);
          });
      };
      

      这对我来说很好

      【讨论】:

        【解决方案9】:

        这是我使用节点 js 运行的示例代码 有一个同步税错误

        应该是 writeFile 而不是 WriteFile

        const axios = require('axios');
        const fs = require('fs');
        axios.get('http://www.africau.edu/images/default/sample.pdf', {responseType: 'blob'}).then(response => {
          fs.writeFile('./my.pdf', response.data, (err) => {
                if (err) throw err;
                console.log('The file has been saved!');
            });
        });
        
        

        文件保存后可能看起来像在文本编辑器中,但文件已正确保存

        %PDF-1.3
        %����
        
        1 0 obj
        <<
        /Type /Catalog
        /Outlines 2 0 R
        /Pages 3 0 R
        >>
        endobj
        
        2 0 obj
        <<
        /Type /Outlines
        /Count 0
        >>
        endobj
        
        3 0 obj
        <<
        /Type /Pages
        /Count 2
        /Kids [ 4 0 R 6 0 R ] 
        >>
        endobj
        

        【讨论】:

        • 答案如何?您没有解释任何与 OP 发布的内容不同的内容。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-25
        • 1970-01-01
        • 2022-06-13
        • 2021-06-25
        • 1970-01-01
        • 2020-02-26
        • 2021-07-07
        相关资源
        最近更新 更多