如何parsing与Node.js肮脏的CSV?

由于许多错误,我无法正确parsingCSV文件,我正在摸索着。 我提取了一个样本,你可以在这里下载: testingCSV文件

主要的错误(或产生错误)是:

  • 引号和逗号(试图用Rparsing文件时出现很多错误)
  • 空行
  • 意外的换行符在一个字段中

我首先决定逐行使用正则expression式来清理数据,然后将它们加载到R中,但无法解决问题,而且是两个慢的(200Mo文件)

所以我决定使用下面的代码在Node.js下使用CSVparsing器 :

'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) 

但:

  • 一些行正确parsing(string数组)
  • 有些不被parsing(所有字段都是一个string)
  • 有些行仍然是空的(可以通过添加skip_empty_lines: true来解决skip_empty_lines: true选项)
  • 我不知道如何处理意外的换行符。

我不知道如何使这个CSV清洁,无论是与R也没有Node.js。

任何帮助?

编辑:

在@Danny_ds解决scheme之后,我可以正确parsing它。 现在我不能正确地将其归还。

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

谢谢大家!

我不知道如何使这个CSV清洁,无论是与R也没有Node.js。

其实它并不像看起来那么糟糕。

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

  • ""replace所有"" "
  • \n"replace所有\n"
  • "\n全部replace为\n

\n表示换行符,而不是出现在文件中的字符“ \n ”。

请注意,在您的示例文件\n实际上是\r\n0x0d0x0a ),所以根据您使用的软件,您可能需要在上面的示例中replace\n中的\r\n 。 另外,在你的例子中,在最后一行之后有一个换行符,所以作为最后一个字符的引用也将被replace,但是你可能想在原始文件中检查它。

这应该产生一个有效的csv文件:

在这里输入图像描述 在这里输入图像描述

仍然会有多行字段,但这可能是有意的。 但现在这些被正确引用,任何体面的CSVparsing器应该能够处理多行字段。


它看起来像原始数据已经有一个额外的转义字符转义:

  • 如果原始字段包含a ,则会被引用,如果这些字段已经包含引号,引号就会被另一个引号转义 – 这是正确的方法。

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

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

数据不是太搞混了。 有一个清晰的模式。

一般步骤:

  1. 暂时删除混合格式的内部字段(以双引号(或多引号)开头,并具有各种字符。
  2. 从引用行的开头和结尾删除引号,以提供干净的CSV
  3. 将数据拆分成列
  4. replace删除的字段

以上第一步是最重要的。 如果你应用这个,那么新行,空行,引号和逗号的问题就会消失。 如果您查看数据,您可以看到列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 

这是一个基于提供的文件的工作示例:

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

结果:

数据看起来很干净。 所有行都会生成与头部匹配的预期列数(显示的最后2行):

  ..., [ '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' ], 

继我的评论:

数据太乱了一步就修复,不要试试。

首先决定是否双引号和/或逗号可能是数据的一部分。 如果他们不是,用一个简单的正则expression式去除双引号。

接下来,每行应该有14个逗号。 以文本forms读取文件并依次计算每行的逗号数。 如果小于14,检查以下行,如果逗号总和为14,则合并2行。 如果总和小于14,请检查下一行并继续,直到您有14个逗号。 如果下一行超过14你有一个严重的错误,所以记下行号 – 你可能需要手工修复。 保存生成的文件。

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

它应该不用说,你应该处理一份原件,你不可能第一次得到它的权利:)