在Node.js中读取CSV文件的内容

12

我正在尝试在nodejs中实现一个模块(刚开始学习nodejs),该模块有以下要求:

  1. 上传.csv文件。
  2. 读取csv文件的内容。

目前用于restful api的框架是"express": "~4.2.0"和multer用于文件上传。

现在我已经在我的app.js中如下配置了multer。

app.use(multer({
  onFileUploadData : function(file, data){
    console.log('onFileUploadData Called with data - '+ data);
  }
}));

在我的路由文件中,我有一个如下所示的POST端点

app.post('/sample.csv',lead.processCSV);

以下的ajax调用正在调用此路由:

$.ajax({
            xhrFields: {withCredentials: true},
            url: '/sample.csv',
            type: 'POST',
            success: function (data) {
                $scope.handleResponse(data);
            },
            error: function (error, xhr) {
                angular.element('#csvUploadBusyIcon').hide();
                alert('Oops! Upload failed');
            },
            data: formData,
            cache: false,
            contentType: false,
            processData: false
        });

现在我想要获取csv文件的内容,也就是说当所有内容都已加载完毕后,我应该处理我的lead.processCSV方法。

另外,我是否需要其他模块来处理csv文件,或者multer对我来说已经足够了?

任何指导方向的建议都将是有益的。谢谢提前。

2个回答

25
有一个很棒的Node项目对我帮助很大。你应该去看看。 我们要使用的是他们的csv-parse模块。它能够获取流作为输入,并逐行读取它,而不会阻塞事件循环,这样当你处理文件时,你的服务器不会卡住,其他请求仍然可以正常处理。
既然你说你刚开始学习nodejs,你应该快速搜索并了解中间件在请求处理过程中是如何工作的。 作为请求处理的简化,中间件是一个function(req, res, next)。使用req获取请求数据,使用res发送响应,使用next将您的请求和响应对象发送到下一个中间件。这样,您可以分部地处理请求,流的最后一个中间件会将响应发送给客户端(例如res.send(200))。
Multer({...})调用返回一个中间件函数。当一个请求到达此中间件时,multer将尝试下载用户在post请求中发送的任何文件。当你说app.use(Multer({...}))时,你正在要求multer尝试从包含文件的任何post请求中下载文件。如果不是所有的路由都期望上传文件,这是一种安全风险。
好的,说完了,以下是我编写的处理您的用例的示例代码:
//Important Security advice: 
//don't add multer as a middleware to all requests. 
//If you do this, people will be able to upload files
//in ALL YOUR 'post' handlers!!! 

var Multer = require('multer');
var Parse = require('csv-parse');
var fs = require('fs')

function parseCSVFile(sourceFilePath, columns, onNewRecord, handleError, done){
    var source = fs.createReadStream(sourceFilePath);

    var linesRead = 0;

    var parser = Parse({
        delimiter: ',', 
        columns:columns
    });

    parser.on("readable", function(){
        var record;
        while (record = parser.read()) {
            linesRead++;
            onNewRecord(record);
        }
    });

    parser.on("error", function(error){
        handleError(error)
    });

    parser.on("end", function(){
        done(linesRead);
    });

    source.pipe(parser);
}

//We will call this once Multer's middleware processed the request
//and stored file in req.files.fileFormFieldName

function parseFile(req, res, next){
    var filePath = req.files.file.path;
    console.log(filePath);
    function onNewRecord(record){
        console.log(record)
    }

    function onError(error){
        console.log(error)
    }

    function done(linesRead){
        res.send(200, linesRead)
    }

    var columns = true; 
    parseCSVFile(filePath, columns, onNewRecord, onError, done);

}

//this is the route handler with two middlewares. 
//First:  Multer middleware to download file. At some point,
//this middleware calls next() so process continues on to next middleware
//Second: use the file as you need

app.post('/upload', [Multer({dest:'./uploads'}), parseFile]);

我希望这可以帮到你。请确保了解node中路由中间件的工作原理:它们是优质代码的关键。
马塞尔

谢谢你的回答,Marcel。有没有办法在不用包的情况下处理CSV文件?这样需要很长的时间吗? - Ilyas karim

0

我有一个类似的请求要处理CSV文件,我尝试实现了您的解决方案:只要我在console log中使用它,它就可以工作。我尝试将“record”变量存储在一个名为“results”的数组中,但我只得到了一个空数组[],并在呈现此空数组后接收了console.log响应,呈现解析的CSV数据。

所以这似乎是同步问题...我的意思是,处理CSV文件需要一段时间。因此,我尝试压缩您的代码,并将其转换为Promise,然后执行它。因此,在Promise执行之后,我的数组已经准备好使用了。

  1. 注意:我是初学者,因此可能包含一些错误。到目前为止,它对我来说运行良好。
  2. 注意:我的CSV测试文件的内容是:
title, type, value, category
Loan, income, 1500, Others
Website Hosting, outcome, 50, Others
Ice cream, outcome, 3, Food

注意:与您的情况有一些不同:我从路由'/import'接收一个单个文件。我使用Insomnina Designer应用程序发送一个名为importFile的多部分表单体文件。
注意:我导入了您使用的相同库,并且我也使用了中间件的概念。
注意:在这种情况下,我只期望一个文件,因此我使用multer({dest: './upload'}).single('importFile')。也可以使用.any()。
注意:我正在使用typescript,因此对于JS来说,只需要在某些变量声明之后删除:@type,例如
注意:我留下了选项1-仅使用数组和选项2-使用对象。
const results: object[] = [];
becomes:
const results = [];

让我们来看代码:

import { Router, Request, Response } from 'express';
import csv from 'csv-parse';
import multer from 'multer';
import fs from 'fs';

// used on option 2 due typescript
interface CSVTransactionDTO {
  title: string;
  value: number;
  type: 'income' | 'outcome';
  category: string;
}

app.post(
  '/import', // route name
  multer({ dest: './upload' }).single('importFile'), // middleware to download one file (csv)
  async (request: Request, response: Response) => {//last middleware with CSV parsing with arrow function
    const filePath = request.file.path;
    
    
let rowCounter = 0;
    const results: string[] = [];// option 1
    const newTransactions: CSVTransactionDTO[] = [];// option 2
    
    function parseCSVPromise(): Promise<void> {
      return new Promise((resolve, reject) => {
        const ConfigCSV = {
          // delimiter:';',//other delimiters different from default = ','
          from_line: 2, // data starts here
          trim: true, // ignore white spaces immediately around the delimiter (comma)
        };

        fs.createReadStream(filePath)
          .pipe(csv(ConfigCSV))
          .on('data', /* async */ row => {
            rowCounter += 1;// counter of how many rows were processed
            // console.log(data); // just test
            results.push(row); // Option1 - The simplest way is to push a complete row

            const [title, type, value, category] = row;// Option2, process it as an object
            newTransactions.push({title, type, value, category});// Option2, process it as an object
          })
          .on('error', error => {
            reject(error);
            throw new Error('Fail to process CSV file');
          })
          .on('end', () => {
            resolve();// ends the promise when CSV Parse send 'end' flag
          });
      });
    }


    await parseCSVPromise(); // now using the created promise - await finishing parsingCSV
    console.log('option1', results);// option1
    console.log('option2',newTransactions);// option2
    return response.json({ resultsCounter, results }); // For testing only - interrupting the rote execution
    

    // continue processing results and send it to dataBase...
    //await fs.promises.unlink(filePath); // optionally you can delete the file parsed/processed
    

选项1的响应:

 [
  [ 'Loan', 'income', '1500', 'Others' ],
  [ 'Website Hosting', 'outcome', '50', 'Others' ],
  [ 'Ice cream', 'outcome', '3', 'Food' ]
 ]
  

选项2的响应:

  [
    { title: 'Loan',            type: 'income',  value: '1500', category: 'Others' },
    { title: 'Website Hosting', type: 'outcome', value:   '50', category: 'Others' },
    { title: 'Ice cream',       type: 'outcome', value:    '3', category: 'Food' }
  ]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接