簡體   English   中英

如何用Node.js解析臟CSV?

[英]How to parse a dirty CSV with Node.js?

由於許多錯誤,我正在抓住我無法正確解析的CSV文件。 我提取了一個可以在這里下載的樣本: 測試CSV文件

主要錯誤(或產生錯誤的原因)是:

  • 引號和逗號(嘗試使用R解析文件時出現許多錯誤)
  • 空行
  • 字段內意外的換行符

我首先決定逐行使用正則表達式來清理數據,然后將它們加載到R中,但無法解決問題,這是兩個慢(200Mo文件)

所以我決定在Node.js下使用CSV解析器 ,代碼如下:

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

但:

  • 正確解析了一些行(字符串數組)
  • 有些未解析(所有字段都是一個字符串)
  • 有些行仍然是空的(可以通過添加skip_empty_lines: true來解決skip_empty_lines: true對選項為skip_empty_lines: 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);

謝謝大家!

這些數據並沒有太多混亂。 有一個明確的模式。

一般步驟:

  1. 暫時刪除混合格式的內部字段(以雙(或更多)引號開頭並具有各種字符。
  2. 從引用行的開頭和結尾刪除引號,給出干凈的CSV
  3. 將數據拆分為列
  4. 替換刪除的字段

上面的步驟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

這是一個基於提供的文件的工作示例:

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

我不知道如何使這個CSV干凈,無論是R還是Node.js.

實際上,它並沒有它看起來那么糟糕。

使用以下步驟可以輕松地將此文件轉換為有效的csv:

  • ""替換所有"" "
  • 取代所有\\n"\\n
  • 將所有"\\n替換為\\n

\\n表示換行符,而不是字符“ \\n ”,它們也出現在您的文件中。

請注意,在您的示例文件中\\n實際上是\\r\\n0x0d0x0a ),因此根據您使用的軟件,您可能需要在上面的示例中替換\\n \\r\\n \\n \\r\\n中的\\r\\n 此外,在您的示例中,在最后一行之后有一個換行符,因此作為最后一個字符的引號也將被替換,但您可能希望在原始文件中檢查它。

這應該生成一個有效的csv文件:

在此輸入圖像描述 在此輸入圖像描述

仍然會有多行字段,但這可能是有意的。 但現在這些都被正確引用,任何體面的csv解析器都應該能夠處理多行字段。


看起來原始數據有一個額外的傳遞來轉義引號字符:

  • 如果原始字段包含a ,則引用它們,如果這些字段已包含引號,則引號將使用另一個引號進行轉義 - 這是正確的方法。

  • 但是,所有包含引號的行似乎都被再次引用(實際上將這些行轉換為一個引用字段),並且該行內的所有引號都使用另一個引號進行轉義。

  • 顯然,多行字段出了問題。 在多行之間也添加了引號,這不是正確的方法。

繼我的評論之后:

數據太亂了,不能一步到位,不要試試。

首先確定雙引號和/或逗號是否可能是數據的一部分。 如果不是,請使用簡單的正則表達式刪除雙引號。

接下來,每行應該有14個逗號。 將文件作為文本讀取,並依次計算每行的逗號數。 如果小於14,請檢查以下行,如果逗號之和為14,則合並2行。 如果總和小於14,請檢查下一行並繼續,直到您有14個逗號。 如果下一行超過14,則會出現嚴重錯誤,請記下行號 - 您可能需要手動修復。 保存生成的文件。

幸運的是,您現在將擁有一個可以作為CSV處理的文件。 如果沒有,請返回部分整理的文件,我們可以嘗試進一步提供幫助。

不用說你應該處理原件的副本,你不太可能第一次就把它弄好:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM