繁体   English   中英

node.js axios 下载文件 stream 和 writeFile

[英]node.js axios download file stream and writeFile

我想用 axios 下载 pdf 文件并用axios保存在磁盘(服务器端) 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 给你响应之后调用“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(response => {
    response.data.pipe(writer);
    return finished(writer); //this is a Promise
  });
}

您可以简单地使用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"));
});

文件损坏的问题是由于节点流中的背压 您可能会发现此链接有助于阅读: 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 = await 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

希望这个对你有帮助。

// 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);  
});  

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

我试过了,我确信使用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),
        }
})

在这个解决方案中,它不需要写文件和读文件,效率更高。

这对我有用,它还会为图像文件创建一个临时文件,以防未指定输出文件路径:

const fs = require('fs')
const axios = require('axios').default
const tmp = require('tmp');

const downloadFile = async (fileUrl, outputLocationPath) => {
    if(!outputLocationPath) {
        outputLocationPath = tmp.fileSync({ mode: 0o644, prefix: 'kuzzle-listener-', postfix: '.jpg' });
    }
    let path = typeof outputLocationPath === 'object' ? outputLocationPath.name : outputLocationPath
    const writer = fs.createWriteStream(path)
    const response = await axios.get(fileUrl, { responseType: 'arraybuffer' })
    return new Promise((resolve, reject) => {
        if(response.data instanceof Buffer) {
            writer.write(response.data)
            resolve(outputLocationPath.name)
        } else {
            response.data.pipe(writer)
            let error = null
            writer.on('error', err => {
                error = err
                writer.close()
                reject(err)
            })
            writer.on('close', () => {
                if (!error) {
                    resolve(outputLocationPath.name)
                }
            })
        }
    })
}

这是一个非常简单的 Jest 测试:

it('when downloadFile should downloaded', () => {
    downloadFile('https://i.ytimg.com/vi/HhpbzPMCKDc/hq720.jpg').then((file) => {
        console.log('file', file)
        expect(file).toBeTruthy()
        expect(file.length).toBeGreaterThan(10)
    })
})

以下代码取自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);

Lorenzo 的回答可能是最好的答案,因为它使用的是内置的 axios。 如果您只想要缓冲区,这是一种简单的方法:

const downloadFile = url => axios({ url, responseType: 'stream' })
  .then(({ data }) => {
    const buff = []
    data.on('data', chunk => buff.push(chunk))
    return new Promise((resolve, reject) => {
      data.on('error', reject)
      data.on('close', () => resolve(Buffer.concat(buff)))
    })
  })

// better
const downloadFile = url => axios({ url, responseType: 'arraybuffer' }).then(res => res.data)

const res = await downloadFile(url)
fs.writeFileSync(downloadDestination, res)

我仍然可能使用'arraybuffer' responseType

如果你只想要文件使用这个

const media_data =await axios({url: url, method: "get",  responseType: "arraybuffer"})
writeFile("./image.jpg", Buffer.from(media_data.data), {encoding: "binary"}, console.log)

有一种更简单的方法可以用几行来完成:

const fileResponse = await axios({
                url: fileUrl,
                method: "GET",
                responseType: "stream",
            });

            // Write file to disk (here I use fs.promise but you can use writeFileSync it's equal
            await fsPromises.writeFile(filePath, fileResponse.data);

Axios具有处理streams的内部能力,您不必为此干预低级节点 API。

查看https://axios-http.com/docs/req_config (在文档中找到您可以使用的所有类型的responseType部分)。

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);
    });
};

这对我来说很好

这是我使用节点 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM