node.js axios descarga el flujo de archivos y escribe el archivo

8 minutos de lectura

avatar de usuario de ar099968
ar099968

quiero descargar un archivo pdf con axios y guardar en el disco (lado del servidor) con fs.writeFileYo he tratado:

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

el archivo se guarda pero el contenido está roto…

¿Cómo guardo correctamente el archivo?

  • obtiene el registro de la consola “El archivo se ha guardado” y el archivo se crea y solo el contenido es incorrecto?

    – Roland Starke

    27 de marzo de 2019 a las 10:19


  • donde estás llamando axios.get ? no esperará a que se escriba el archivo. mejor prometer el fs o usar fs-extra o usar métodos prometidos de fs. y usar como return fs.writeFile(…)

    – AZ_

    27 de marzo de 2019 a las 10:21


  • @RolandStarke sí, el archivo está guardado

    – ar099968

    27 de marzo de 2019 a las 10:27

  • He publicado un enfoque más limpio para resolver el problema usando canalizaciones de flujo de nodos a continuación. Está en el mismo concepto que propone la respuesta aceptada. stackoverflow.com/a/64925465/3476378

    – Amán Saraf

    20 de noviembre de 2020 a las 7:39


avatar de usuario de csotiriou
csotiriou

En realidad, creo que la respuesta previamente aceptada tiene algunas fallas, ya que no manejará el flujo de escritura correctamente, por lo que si llama “entonces ()” después de que Axios le haya dado la respuesta, terminará con un archivo parcialmente descargado.

Esta es una solución más apropiada cuando se descargan archivos un poco más grandes:

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

De esta manera, puede llamar downloadFile()llamar then() en la promesa devuelta, y asegurándose de que el archivo descargado haya completado el procesamiento.

O, si usa una versión más moderna de NodeJS, puede probar esto en su lugar:

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

  • Esto es correcto y resuelve exactamente el problema relacionado con el error de datos parciales

    – Giorgio Andreti

    27 de abril de 2020 a las 13:12

  • esta debería ser la respuesta aceptada. corrigió el error de descarga parcial

    – ariezona

    12 mayo 2020 a las 18:10

  • He publicado un enfoque más limpio sobre el mismo concepto de usar canalizaciones de transmisión a continuación: stackoverflow.com/a/64925465/3476378.

    – Amán Saraf

    20 de noviembre de 2020 a las 7:38

  • No estoy seguro de seguir. A medida que se descargan los bytes, se transmiten a un archivo y, una vez que se han transmitido todos los bytes, la Promesa finaliza y el resto del flujo de la aplicación continúa. El ‘entonces’ en el ejemplo se llama antes de que el archivo haya terminado de descargarse; consulte la documentación sobre el stream tipo de respuesta de axios.

    – csotiriou

    27 de febrero de 2021 a las 22:17

  • response.data.pipe no es una función

    – azar

    12 de junio de 2021 a las 5:56

Simplemente puede usar response.data.pipe y fs.createWriteStream canalizar la respuesta al archivo

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

  • ¡¡Muchas gracias!! Estaba buscando esto para siempre

    – Harrison Cramer

    26 de febrero de 2020 a las 1:16

  • Esta respuesta no está completa, porque cuando descarga algunos archivos más grandes, la tubería le dará más de un evento. Este código no espera hasta que se haya descargado todo el archivo antes de poder llamar then en eso. Eche un vistazo a mi solución para encontrar lo que considero una solución más completa.

    – csotiriou

    17 de abril de 2020 a las 10:33

  • response.data.pipe no es una función

    – Murat Serdar Akkuş

    11 de junio de 2020 a las 1:36

  • si no se descarga el archivo en el almacenamiento local, entonces cómo hacerlo, probé res.sendFile en node.js

    – sj

    16 de abril de 2021 a las 8:06

  • Para criticar esta solución, usted deber establezca el tipo de respuesta en “flujo”. Si no lo hace, se producirá un error cuando intente canalizarlo a otra secuencia.

    – Rashad Rivera

    21 oct a las 15:02

Avatar de usuario de Aman Saraf
Amán Saraf

El problema con el archivo roto se debe a contrapresión en flujos de nodos. Puede encontrar este enlace útil para leer: https://nodejs.org/es/docs/guides/backpressuring-in-streams/

No soy realmente un fanático del uso de objetos declarativos básicos de Promise en códigos JS, ya que siento que contamina la lógica central real y hace que el código sea difícil de leer. Además, debe aprovisionar controladores y oyentes de eventos para asegurarse de que el código esté completo.

A continuación se proporciona un enfoque más limpio sobre la misma lógica que propone la respuesta aceptada. Utiliza los conceptos de tuberías de corriente.

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

Espero que encuentres esto útil.

  • ¿Por qué la respuesta escribe blob y no transmite?

    – 1252748

    26 de febrero de 2021 a las 1:54

  • Recibo el error “stream.on no es una función” con este método

    – dz210

    7 de junio de 2021 a las 1:11

  • Lo tengo funcionando así: const resp = await axios.get(….); esperar canalización (resp.data, fs.createWriteStream(…))

    – dz210

    7 de junio de 2021 a las 3:28


  • @ 1252748 blob es una opción solo para navegador.

    – B45i

    27 de noviembre de 2021 a las 10:27

Avatar de usuario de Armand
Armando

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

avatar de usuario de fedesc
federal

sistema de archivos de nodo writeFile codifica los datos de forma predeterminada en UTF8. que podría ser un problema en su caso.

Intente configurar su codificación en null y omita la codificación de los datos recibidos:

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

también puede decalre la codificación como una cadena (en lugar de un objeto de opciones) si solo declara la codificación y ninguna otra opción. la cadena se manejará como valor de codificación. como tal:

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

más leer en fileSystem API write_file

  • Si bien este código puede resolver la pregunta, incluyendo una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación y probablemente resulte en más votos a favor. Recuerda que estás respondiendo la pregunta para lectores en el futuro, no solo para la persona que pregunta ahora. Edite su respuesta para agregar explicaciones y dar una indicación de las limitaciones y suposiciones que se aplican.

    – doble pitido

    27 de marzo de 2019 a las 11:42

  • @Double-beep tnx por su comentario. He editado con alguna explicación y leído material de node fileSystem API sobre la función writeFile. 🙂

    – federal

    27 de marzo de 2019 a las 11:56

Avatar de usuario de Lorenzo Regalado
lorenzo regalado

El siguiente código tomado de https://gist.github.com/senthilmpro/072f5e69bdef4baffc8442c7e696f4eb?permalink_comment_id=3620639#gistcomment-3620639 trabajó para mi

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

  • Si bien este código puede resolver la pregunta, incluyendo una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación y probablemente resulte en más votos a favor. Recuerda que estás respondiendo la pregunta para lectores en el futuro, no solo para la persona que pregunta ahora. Edite su respuesta para agregar explicaciones y dar una indicación de las limitaciones y suposiciones que se aplican.

    – doble pitido

    27 de marzo de 2019 a las 11:42

  • @Double-beep tnx por su comentario. He editado con alguna explicación y leído material de node fileSystem API sobre la función writeFile. 🙂

    – federal

    27 de marzo de 2019 a las 11:56

avatar de usuario de levy9527
impuesto9527

Lo he intentado, y estoy seguro de que usando response.data.pipe y fs.createWriteStream puede trabajar.


Además, quiero agregar mi situación y solución.

Situación:

  • usando koa para desarrollar un servidor node.js
  • usando axios para obtener un pdf a través de url
  • usando pdf-parse para analizar el pdf
  • extraiga información de pdf y devuélvala como json al navegador

Solución:

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

En esta solución, no necesita escribir y leer archivos, es más eficiente.

  • ¿Cómo es “más eficiente” almacenar en búfer todo el archivo de datos en la memoria antes de enviarlo que transmitirlo?

    – Rashad Rivera

    6 oct a las 14:09

  • ¿No entiendo tu punto? ¿A qué te refieres con “enviar”? En mi situación, solo necesito analizar el archivo pdf, no necesito responder

    – levy9527

    8 oct a las 8:03

¿Ha sido útil esta solución?