Javascript 如何使用Node.js解析脏CSV?

Javascript 如何使用Node.js解析脏CSV?,javascript,node.js,parsing,csv,Javascript,Node.js,Parsing,Csv,由于许多错误,我对无法正确解析的CSV文件摸不着头脑。我提取了一个示例,您可以在此处下载: 主要错误(或产生错误的原因)有: 引号和逗号(尝试使用R解析文件时出现许多错误) 空行 字段内意外的换行 在将数据加载到R之前,我首先决定使用正则表达式逐行清理数据,但无法解决问题,而且速度很慢(200Mo文件) 因此,我决定在Node.js下使用一个包含以下代码的文件: 'use strict'; const Fs = require('fs'); const Csv = require('cs

由于许多错误,我对无法正确解析的CSV文件摸不着头脑。我提取了一个示例,您可以在此处下载:

主要错误(或产生错误的原因)有:

  • 引号和逗号(尝试使用R解析文件时出现许多错误)
  • 空行
  • 字段内意外的换行
在将数据加载到R之前,我首先决定使用正则表达式逐行清理数据,但无法解决问题,而且速度很慢(200Mo文件)

因此,我决定在Node.js下使用一个包含以下代码的文件:

'use strict';

const Fs  = require('fs');
const Csv = require('csv');

let input       = 'data_stack.csv';
let readStream  = Fs.createReadStream(input);
let option      = {delimiter: ',', quote: '"', escape: '"', relax: true};

let parser = Csv.parse(option).on('data', (data) => {
    console.log(data)
});

readStream.pipe(parser)
但是:

  • 某些行被正确解析(字符串数组)
  • 有些字段未被解析(所有字段都是一个字符串)
  • 某些行仍然为空(可以通过向选项中添加
    跳过空行:true
    来解决)
  • 我不知道如何处理意外的断线
我不知道如何使这个CSV干净,无论是使用R还是使用Node.js

有什么帮助吗

编辑:

遵循@Danny_ds解决方案,我可以正确解析它。现在我无法正确地将其重新字符串化

使用
console.log()我得到了一个合适的对象,但当我试图将其字符串化时,我没有得到一个干净的CSV(仍然有换行符和空行)

以下是我正在使用的代码:

'use strict';

const Fs  = require('fs');
const Csv = require('csv');


let input  = 'data_stack.csv';
let output = 'data_output.csv';

let readStream  = Fs.createReadStream(input);
let writeStream = Fs.createWriteStream(output);

let opt  = {delimiter: ',', quote: '"', escape: '"', relax: true, skip_empty_lines: true};


let transformer = Csv.transform(data => {
    let dirty = data.toString();
    let replace = dirty.replace(/\r\n"/g, '\r\n').replace(/"\r\n/g, '\r\n').replace(/""/g, '"');

    return replace;
});

let parser = Csv.parse(opt);
let stringifier = Csv.stringify();

readStream.pipe(transformer).pipe(parser).pipe(stringifier).pipe(writeStream);
编辑2:

下面是最后一个有效的代码:

'use strict';

const Fs  = require('fs');
const Csv = require('csv');


let input  = 'data_stack.csv';
let output = 'data_output.csv';

let readStream  = Fs.createReadStream(input);
let writeStream = Fs.createWriteStream(output);

let opt  = {delimiter: ',', quote: '"', escape: '"', relax: true, skip_empty_lines: true};


let transformer = Csv.transform(data => {
    let dirty = data.toString();
    let replace = dirty
        .replace(/\r\n"/g, '\r\n')
        .replace(/"\r\n/g, '\r\n')
        .replace(/""/g, '"');

    return replace;
});

let parser = Csv.parse(opt);

let cleaner = Csv.transform(data => {
    let clean = data.map(l => {
        if (l.length > 100 || l[0] === '+') {
            return l = "Encoding issue";
        }
        return l;
    });
    return clean;
});

let stringifier = Csv.stringify();

readStream.pipe(transformer).pipe(parser).pipe(cleaner).pipe(stringifier).pipe(writeStream);

谢谢大家

以下是我的评论:

数据太混乱,无法一步修复,请不要尝试

首先确定双引号和/或逗号是否可能是数据的一部分。如果不是,请使用简单的正则表达式删除双引号

接下来,每行应该有14个逗号。以文本形式读取文件,然后依次计算每行的逗号数。如果小于14,则检查下一行,如果逗号之和为14,则合并两行。如果总和小于14,请检查下一行并继续,直到有14个逗号。如果下一行超过14,则会出现严重错误,因此请记下行号-您可能需要手动修复。保存结果文件

幸运的是,您现在将拥有一个可以作为CSV处理的文件。如果没有,请返回部分整理过的文件,我们可以尝试进一步提供帮助


不用说,你应该处理一份原件的副本,你不可能第一次就把它弄对:)

数据没有太乱,无法处理。有一个明确的模式

一般步骤:

  • 暂时删除混合格式的内部字段(以双引号(或多引号)开头,并包含所有类型的字符)
  • 从引用行的开始和结束处删除引用,以获得干净的CSV
  • 将数据拆分为列
  • 替换删除的字段
  • 上面的步骤1是最重要的。如果应用此步骤,则新行、空行、引号和逗号的问题将消失。如果查看数据,您可以看到第7、8和9列包含混合数据。但是始终由2个引号分隔

    good,clean,data,here,"""<-BEGINNING OF FIELD DATA> Oh no
    ++\n\n<br/>whats happening,, in here, pages of chinese
    characters etc END OF FIELD ->""",more,clean,data
    
    我不知道如何使这个CSV干净,无论是用R还是用 Node.js

    事实上,情况并不像看上去那么糟

    使用以下步骤可以轻松地将此文件转换为有效的csv:

    • 将所有
      替换为
    • 将所有
      \n“
      替换为
      \n
    • 将所有
      “\n
      替换为
      \n
    \n
    表示换行,而不是文件中出现的字符“
    \n

    请注意,在示例文件中,
    \n
    实际上是
    \r\n
    0x0d
    0x0a
    ),因此,根据您使用的软件,您可能需要在上述示例中替换
    \r\n
    中的
    \n
    。此外,在您的示例中,最后一行后面有一个换行符,因此最后一个字符的引号也将被替换,但您可能希望在原始文件中对此进行检查

    这将生成一个有效的csv文件:

    仍然会有多行字段,但这可能是有意的。但现在这些字段被正确引用,任何合适的csv解析器都应该能够处理多行字段


    看起来原始数据有一个转义引号字符的额外过程:

    • 如果原始字段包含一个
      ,则引用它们,如果这些字段已经包含引号,则使用另一个引号转义引号-这是正确的方法

    • 但是,所有包含引号的行似乎都被再次引用(实际上将这些行转换为一个带引号的字段),并且该行中的所有引号都用另一个引号转义

    • 显然,多行字段出了问题。在多行之间也添加了引号,这不是正确的方法


    哇,这是一个乱七八糟的CSV!你需要分多个阶段来修复它。首先是修复似乎嵌入在某些行中的换行符。接下来我将整理随机引号。如果你不希望数据中有逗号,请删除引号。你能将CSV文件上载到其他地方吗?gist maybeHere是另一个链接:谢谢丽安,关于你的第一点(顺便说一句,第二点)只有一件事。我如何计算逗号,而不考虑可以包含在带引号字符串中的封闭逗号。通过应用正则表达式删除双引号,我将封闭逗号保留在野外。这就是为什么我问数据是否可以包含逗号。如果可以,我不确定您是否可以在不手动检查和可能的情况下修复数据即使这样也不行。并非所有CSV数据都包含嵌入逗号,这就是为什么数据周围的引号实际上是可选的。尽管在您的案例中,有许多不匹配的引号,这是一个值得关注的问题,因为
    fs.readFile('./data_stack.csv', (e, data) => {
    
        // Take out fields that are delimited with double+ quotes
        var dirty = data.toString();
        var matches = dirty.match(/""[\s\S]*?""/g);
        matches.forEach((m,i) => {
            dirty = dirty.replace(m, "<REPL-" + i + ">");
        });
    
        var cleanData =   dirty
            .split('\n') // get lines
    
            // ignore first line with column names
            .filter((l, i) => i > 0)
    
            // remove first and last quotation mark if exists
            .map(l => l[0] === '"' ? l.substring(1, l.length-2) : l) // remove quotes from quoted lines
    
            // split into columns
            .map(l => l.split(','))
    
            // return replaced fields back to data (columsn 7,8 and 9)
            .map(col => {
    
                if (col.length > 9) {
                    col[7] = returnField(col[7]);
                    col[8] = returnField(col[8]);
                    col[9] = returnField(col[9]);
                }
                return col;
    
                function returnField(f) {
                    if (f) {
                        var repls = f.match(/<.*?>/g)
                        if (repls)
                            repls.forEach(m => {
                                var num = +m.split('-')[1].split('>')[0];
                                f = f.replace(m, matches[num]);
                            });
                    }
                    return f;
                }
            })
    
        return cleanData
    });
    
      ...,
      [ '19403',
        '560e348d2adaffa66f72bfc9',
        'done',
        '276',
        '2015-10-02T07:38:53.172Z',
        '20151002',
        '560e31f69cd6d5059668ee16',
        '""560e336ef3214201030bf7b5""',
        'a+�a��a+�a+�a��a+�a��a+�a��',
        '',
        '560e2e362adaffa66f72bd99',
        '55f8f041b971644d7d861502',
        'foo',
        'foo',
        'foo@bar.com',
        'bar.com' ],
      [ '20388',
        '560ce1a467cf15ab2cf03482',
        'update',
        '231',
        '2015-10-01T07:32:52.077Z',
        '20151001',
        '560ce1387494620118c1617a',
        '""""""Final test, with a comma""""""',
        '',
        '',
        '55e6dff9b45b14570417a908',
        '55e6e00fb45b14570417a92f',
        'foo',
        'foo',
        'foo@bar.com',
        'bar.com' ],