簡體   English   中英

管道中的 csv-parse 錯誤處理

[英]csv-parse error handling in pipe

作為我正在構建的應用程序的一部分,我正在使用csv-parse讀取和操作大型(大約 5.5GB,800 萬行)csv 文件。 我的過程運行得相對順利,但被困在一個項目上 - 捕獲由不一致的列數引發的錯誤。

我正在使用管道函數,因為它與應用程序的其余部分配合得很好,但我的問題是,如何將解析器拋出的錯誤重定向到日志並允許進程繼續?

我認識到我可以使用relax_column_count選項來跳過列數不一致的記錄,而該選項幾乎就足夠了。 問題在於,出於數據質量評估的目的,我需要記錄這些記錄,以便我可以返回並查看導致列數不正確的原因(該過程是一個具有許多潛在故障點的提要)。

作為旁注,我知道解決此問題的最簡單方法是清理此過程上游的數據,但不幸的是我無法控制數據源。


例如,在示例集中,我收到以下錯誤:

事件.js:141
扔er; // 未處理的“錯誤”事件
錯誤:行(行號)上的列與標題不匹配


示例數據(實際上不是我的數據,而是展示了同樣的問題):

year, month, value1, value2
2012, 10, A, B
2012, 11, B, C,
2012, 11, C, D,
2013, 11, D, E,
2013, 11, E, F,
2013, 11, F, 
2013, 11, G, G,
2013, 1, H, H,
2013, 11, I, I,
2013, 12, J, J,
2014, 11, K, K,
2014, 4, L, L,
2014, 11, M, M,
2014, 5, N, 
2014, 11, O, N,
2014, 6, P, O,
2015, 11, Q, P,
2015, 11, R, Q,
2015, 11, S, R,
2015, 11, T, S, 

代碼:

const fs = require('fs');
const parse = require('csv-parse');
const stringify = require('csv-stringify');
const transform = require('stream-transform');

const paths = {
    input: './sample.csv',
    output: './output.csv',
    error: './errors.csv',
}

var input  = fs.createReadStream(paths.input);
var output = fs.createWriteStream(paths.output);
var error  = fs.createWriteStream(paths.error);

var stringifier = stringify({
    header: true,
    quotedString: true,
});
var parser = parse({
    relax: true,
    delimiter: ',', 
    columns: true, 
    //relax_column_count: true,
})
var transformer = transform((record, callback) => {
    callback(null, record);
}, {parallel: 10});

input.pipe(parser).pipe(transformer).pipe(stringifier).pipe(output);

想法?

我開發了一個解決這個問題的方法。 它不使用管道 API ,而是使用 CSV 包的回調 API 它不像我希望的那樣優雅,但它是功能性的,並且具有顯式錯誤處理的好處,這不會導致過程在不一致的列數上停止。

該過程逐行讀取文件,根據settings對象 ( settings.mapping ) 中的預期字段列表解析該行,然后轉換、字符串化並將結果輸出行寫入新的 csv。

我將其設置為記錄由於列數與文件頭不一致以及一些額外數據(執行日期時間、行號和整行作為診斷信息的文本)而導致的錯誤。我沒有設置其他錯誤類型的日志記錄,因為它們都位於 csv 結構錯誤的下游,但您也可以修改代碼以編寫這些錯誤。(您也可以將它們寫入 JSON 或 MySQL 數據庫,但有一件事一次)。

好消息是,與直接方法相比,使用這種方法並沒有對性能造成巨大影響。 我沒有做過任何正式的性能測試,但在 60MB 的文件上,兩種方法的性能大致相同(假設文件沒有不一致的行)。 明確的下一步是研究將寫入捆綁到磁盤以減少 I/O。

我仍然很想知道是否有更好的方法來做到這一點,所以如果您有想法,請務必發布答案! 與此同時,我想我會發布這個有效的答案,以防它對其他人在使用相同類型的格式不一致的源時苦苦掙扎時有用。


信用到期的信用,特別是兩個問題/答案:

  • 在 Node.js 中解析巨大的日志文件 - 逐行讀取
    • 該答案改編了將逐行讀取的拆分文件的答案中的一些核心代碼,這可以防止 csv-parse 組件在失敗的行處關閉(以代碼開銷為代價將文件進一步拆分到上游) . 實際上,我真的建議使用 iconv-lite,因為它在該帖子中已完成,但它與最低限度可重復的示例沒有密切關系,因此我在這篇文章中將其刪除。
  • 使用 node.js 流進行錯誤處理
    • 這通常有助於更好地了解管道的潛力和局限性。 看起來理論上有一種方法可以將基本上相當於管道分離器的內容放到來自解析器的出站管道上,但是考慮到我當前的時間限制以及與異步進程相關的挑戰,在流終止方面這將是相當不可預測的,我使用而是回調 API。

示例代碼:

'use strict'
// Dependencies
const es     = require('event-stream');
const fs     = require('fs');
const parse = require('csv-parse');
const stringify = require('csv-stringify');
const transform = require('stream-transform');

// Reference objects
const paths = {
    input: 'path to input.csv',
    output: 'path to output.csv',
    error: 'path to error output.csv',
}
const settings = {
    mapping: {
        // Each field is an object with the field name as the key
        // and can have additional properties for use in the transform 
        // component of this process
        // Example
        'year' : {
            import: true,
        }
    }
}

const metadata = {
    records: 0,
    error: 0
}

// Set up streams
var input  = fs.createReadStream(paths.input);
var errors  = fs.createWriteStream(paths.error,  {flags: 'ax'});
var output = fs.createWriteStream(paths.output, {flags: 'ax'});

// Begin process (can be refactored into function, but simplified here)
input
  .pipe(es.split()) // split based on row, assumes \n row endings
  .pipe(es.mapSync(line => { // synchronously process each line

    // Remove headers, specified through settings
    if (metadata.records === 0) return metadata.records++;
    var id = metadata.records;

    // Parse csv by row 
    parse(line, {
        relax: true,
        delimiter: ',', 
        columns: Object.keys(settings.mapping),
    }, (error, record) => {

        // Write inconsistent column error 
        if (error) {
            metadata.error++;
            errors.write(
                new Date() + ', Inconsistent Columns, ' + 
                 id + ', `' +  
                 line + '`\n'
            );
        }

    // Apply transform / reduce
    transform(record, (record) => {
        // Do stuff to record
        return record;
    }, (error, record) => {

        // Throw tranform errors
        if (error) {
            throw error;
        }

    // Stringify results and write to new csv
    stringify(record, {
           header: false,
           quotedString: true,
    }, (error, record) => {

        // Throw stringify errors
        if (error) {
            console.log(error);
        }

        // Write record to new csv file
        output.write(record);
    });
    });
    })

    // Increment record count
    metadata.records++;

  }))  
  .on('end', () => {
    metadata.records--;
    console.log(metadata)
  })    

暫無
暫無

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

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