Node.js में विशाल लॉगफ़ाइल्स को पार्स करना - लाइन-बाय-लाइन में पढ़ा जाता है


126

मुझे जावास्क्रिप्ट (Node.js) (मैं क्यूब का उपयोग कर रहा हूं) में बड़े (5-10 जीबी) लॉगफाइल्स की कुछ पार्सिंग करने की आवश्यकता है।

लॉगलाइन कुछ इस तरह दिखती है:

10:00:43.343423 I'm a friendly log message. There are 5 cats, and 7 dogs. We are in state "SUCCESS".

हमें प्रत्येक पंक्ति को पढ़ने की ज़रूरत है, कुछ पार्सिंग (जैसे स्ट्रिप आउट 5, 7और SUCCESS) करें, फिर अपने जेएस क्लाइंट का उपयोग करके इस डेटा को क्यूब ( https://github.com/square/cube ) में पंप करें ।

सबसे पहले, एक फाइल में पढ़ने के लिए नोड में विहित तरीका क्या है, लाइन से लाइन?

यह ऑनलाइन काफी सामान्य प्रश्न लगता है:

बहुत सारे उत्तर तृतीय-पक्ष मॉड्यूल के एक समूह को इंगित करते हैं:

हालांकि, यह एक काफी बुनियादी कार्य की तरह लगता है - निश्चित रूप से, एक टेक्स्टफाइल, लाइन-बाय-लाइन में पढ़ने के लिए stdlib के भीतर एक सरल तरीका है?

दूसरे, मुझे तब प्रत्येक पंक्ति को संसाधित करने की आवश्यकता होती है (जैसे टाइमस्टैम्प को डेट ऑब्जेक्ट में बदलना, और उपयोगी फ़ील्ड निकालना)।

ऐसा करने का सबसे अच्छा तरीका क्या है, थ्रूपुट को अधिकतम करना? क्या कोई रास्ता है जो प्रत्येक पंक्ति में पढ़ने पर या इसे क्यूब पर भेजने पर ब्लॉक नहीं करेगा?

तीसरा - मैं स्ट्रिंग स्प्लिट्स का उपयोग करने का अनुमान लगा रहा हूं, और जेएस समतुल्य है (IndexOf! = -1?) रीजैक्स की तुलना में बहुत तेज होगा? क्या किसी को Node.js में भारी मात्रा में पाठ डेटा पार्स करने का अधिक अनुभव है?

चीयर्स, विक्टर


मैंने नोड में एक लॉग पार्सर का निर्माण किया जो जेन्स में निर्मित और 'आउटपुट' के साथ रेगेक्स स्ट्रिंग्स का एक गुच्छा लेता है। यदि आप एक कैल्क करना चाहते हैं, तो आप प्रत्येक कैप्चर पर फ़ंक्शन भी कॉल कर सकते हैं। यह वह कर सकता है जो आप चाहते हैं: npmjs.org/package/logax
जेस

जवाबों:


208

मैंने एक स्ट्रीम का उपयोग करके बहुत बड़ी फ़ाइलों (gbs) लाइन को पार्स करने के लिए एक समाधान की खोज की। सभी तृतीय-पक्ष पुस्तकालयों और उदाहरणों ने मेरी आवश्यकताओं के अनुरूप नहीं किया क्योंकि उन्होंने फ़ाइलों को लाइन द्वारा पंक्तिबद्ध नहीं किया था (जैसे 1, 2, 3, 4 ..) या पूरी फ़ाइल को मेमोरी में पढ़ें

निम्नलिखित समाधान बहुत बड़ी फ़ाइलों को पार्स कर सकते हैं, स्ट्रीम और पाइप का उपयोग करके लाइन द्वारा। परीक्षण के लिए मैंने 17.000.000 रिकॉर्ड के साथ 2.1 gb फ़ाइल का उपयोग किया। राम का उपयोग 60 एमबी से अधिक नहीं था।

सबसे पहले, इवेंट-स्ट्रीम पैकेज स्थापित करें :

npm install event-stream

फिर:

var fs = require('fs')
    , es = require('event-stream');

var lineNr = 0;

var s = fs.createReadStream('very-large-file.csv')
    .pipe(es.split())
    .pipe(es.mapSync(function(line){

        // pause the readstream
        s.pause();

        lineNr += 1;

        // process line here and call s.resume() when rdy
        // function below was for logging memory usage
        logMemoryUsage(lineNr);

        // resume the readstream, possibly from a callback
        s.resume();
    })
    .on('error', function(err){
        console.log('Error while reading file.', err);
    })
    .on('end', function(){
        console.log('Read entire file.')
    })
);

यहाँ छवि विवरण दर्ज करें

कृपया मुझे बतायेँ कि यह कैसा चलेगा!


6
FYI करें, यह कोड समकालिक नहीं है। यह अतुल्यकालिक है। यदि आप console.log(lineNr)अपने कोड की अंतिम पंक्ति के बाद सम्मिलित करते हैं, तो यह अंतिम पंक्ति की गिनती नहीं दिखाएगा क्योंकि फ़ाइल अतुल्यकालिक रूप से पढ़ी जाती है।
jfriend00

4
धन्यवाद, यह एकमात्र समाधान था जो मैं पा सकता था कि वास्तव में रुका हुआ था और इसे फिर से शुरू किया गया था। Readline नहीं किया।
ब्रेंट

3
बहुत बढ़िया उदाहरण है, और यह वास्तव में विराम देता है। इसके अतिरिक्त यदि आप फ़ाइल को जल्दी पढ़ने से रोकने का निर्णय लेते हैं तो आप उपयोग कर सकते हैंs.end();
zipzit

2
एक जादू की तरह काम किया। इसका उपयोग 150 मिलियन दस्तावेज़ों को इलेस्टिक्स खोज इंडेक्स में करने के लिए किया जाता है। readlineमॉड्यूल एक दर्द है। यह विराम नहीं देता है और 40-50 मिलियन के बाद हर बार विफलता का कारण बन रहा है। एक दिन बर्बाद हो गया। उत्तर के लिए बहुत बहुत धन्यवाद। यह पूरी तरह से काम करता है
मंदीप सिंह

3
ईवेंट-स्ट्रीम से छेड़छाड़ की गई: medium.com/intrinsic/… लेकिन 4+ जाहिरा तौर पर सुरक्षित ब्लॉग है। npmjs.org/post/180565383195/…
John Vandivier

72

आप इनबिल्ट readlineपैकेज का उपयोग कर सकते हैं , यहां डॉक्स देखें । मैं एक नई आउटपुट स्ट्रीम बनाने के लिए स्ट्रीम का उपयोग करता हूं ।

var fs = require('fs'),
    readline = require('readline'),
    stream = require('stream');

var instream = fs.createReadStream('/path/to/file');
var outstream = new stream;
outstream.readable = true;
outstream.writable = true;

var rl = readline.createInterface({
    input: instream,
    output: outstream,
    terminal: false
});

rl.on('line', function(line) {
    console.log(line);
    //Do your stuff ...
    //Then write to outstream
    rl.write(cubestuff);
});

बड़ी फ़ाइलों को संसाधित होने में कुछ समय लगेगा। बताओ अगर यह काम करता है।


2
जैसा कि लिखा गया है, दूसरी से अंतिम पंक्ति विफल हो जाती है क्योंकि क्यूबेस्टफ को परिभाषित नहीं किया गया है।
ग्रेग

2
का उपयोग करना readline, क्या "डू स्टफ" क्षेत्र में async क्रिया करने के लिए रीड स्ट्रीम को रोकना / फिर से शुरू करना संभव है?
jchook

1
@ ज़ुचुक readlineमुझे बहुत सारी समस्याएं दे रहा था, जब मैंने ठहराव / फिर से शुरू करने की कोशिश की। यह धारा ठीक से समस्या का एक बहुत बनाने रुकता नहीं है, तो नीचे की प्रक्रिया धीमी है
मनदीप सिंह

31

मुझे वास्तव में @gerard उत्तर पसंद आया, जो वास्तव में यहाँ सही उत्तर के योग्य है। मैंने कुछ सुधार किए:

  • कोड एक वर्ग (मॉड्यूलर) में है
  • परसिंग शामिल है
  • फिर से शुरू करने की क्षमता बाहर के मामले में दी गई है, अतुल्यकालिक नौकरी CSV को DB में सम्मिलित करने या HTTP अनुरोध के रूप में पढ़ने के लिए जंजीर है
  • चंक्स / बैटचे के आकारों में पढ़ना, जिसे उपयोगकर्ता घोषित कर सकता है। यदि आपके पास विभिन्न एन्कोडिंग में फाइलें हैं, तो मैंने स्ट्रीम में एन्कोडिंग का भी ध्यान रखा।

यहाँ कोड है:

'use strict'

const fs = require('fs'),
    util = require('util'),
    stream = require('stream'),
    es = require('event-stream'),
    parse = require("csv-parse"),
    iconv = require('iconv-lite');

class CSVReader {
  constructor(filename, batchSize, columns) {
    this.reader = fs.createReadStream(filename).pipe(iconv.decodeStream('utf8'))
    this.batchSize = batchSize || 1000
    this.lineNumber = 0
    this.data = []
    this.parseOptions = {delimiter: '\t', columns: true, escape: '/', relax: true}
  }

  read(callback) {
    this.reader
      .pipe(es.split())
      .pipe(es.mapSync(line => {
        ++this.lineNumber

        parse(line, this.parseOptions, (err, d) => {
          this.data.push(d[0])
        })

        if (this.lineNumber % this.batchSize === 0) {
          callback(this.data)
        }
      })
      .on('error', function(){
          console.log('Error while reading file.')
      })
      .on('end', function(){
          console.log('Read entirefile.')
      }))
  }

  continue () {
    this.data = []
    this.reader.resume()
  }
}

module.exports = CSVReader

तो मूल रूप से, यहां बताया गया है कि आप इसका उपयोग कैसे करेंगे:

let reader = CSVReader('path_to_file.csv')
reader.read(() => reader.continue())

मैंने 35GB CSV फ़ाइल के साथ इसका परीक्षण किया और इसने मेरे लिए काम किया और इसीलिए मैंने इसे @gerard के उत्तर पर बनाने के लिए चुना , फीडबैक का स्वागत है।


कितना समय लगा?
जेड। खुल्लाह

जाहिर है, यह pause()कॉल की कमी है , है ना?
वानुआन

इसके अलावा, यह कॉलबैक फ़ंक्शन को अंत में कॉल नहीं करता है। इसलिए यदि बैचसाइज़ 100 है, तो फ़ाइलों का आकार 150 है, केवल 100 वस्तुओं को संसाधित किया जाएगा। क्या मै गलत हु?
वानुआन

16

मैंने एक पाठ फ़ाइल से 1 000 000 से अधिक लाइनें पढ़ने के लिए https://www.npmjs.com/package/line-by-line का उपयोग किया । इस मामले में, रैम की एक अधिकृत क्षमता लगभग 50-60 मेगाबाइट थी।

    const LineByLineReader = require('line-by-line'),
    lr = new LineByLineReader('big_file.txt');

    lr.on('error', function (err) {
         // 'err' contains error object
    });

    lr.on('line', function (line) {
        // pause emitting of lines...
        lr.pause();

        // ...do your asynchronous line processing..
        setTimeout(function () {
            // ...and continue emitting lines.
            lr.resume();
        }, 100);
    });

    lr.on('end', function () {
         // All lines are read, file is closed now.
    });

'लाइन-बाय-लाइन' चयनित उत्तर की तुलना में अधिक स्मृति कुशल है। सीएसवी में 1 मिलियन लाइनों के लिए चयनित उत्तर में मेगाबाइट के निचले 800 में मेरी नोड प्रक्रिया थी। 'लाइन-बाय-लाइन' का उपयोग करते हुए यह लगातार 700 के दशक में कम था। यह मॉड्यूल कोड को साफ और पढ़ने में आसान भी रखता है। कुल मिलाकर मुझे लगभग 18 मिलियन पढ़ने की आवश्यकता होगी ताकि हर mb मायने रखता है!
नियो

यह शर्म की बात है कि यह मानक 'चंक' के बजाय स्वयं की घटना 'लाइन' का उपयोग करता है, जिसका अर्थ है कि आप 'पाइप' का उपयोग करने में सक्षम नहीं होंगे।
रेने वोलेर

घंटों परीक्षण और खोज के बाद यह एकमात्र समाधान है जो वास्तव में lr.cancel()विधि पर रोक देता है । 1ms में 5Gig फ़ाइल की पहली 1000 लाइनें पढ़ता है। बहुत बढ़िया!!!!
पेरेज़ लैम्ड वैन नीकेर्क

6

बड़ी फ़ाइल लाइन को लाइन से पढ़ने के अलावा, आप इसे चंक से भी पढ़ सकते हैं। इस लेख के बारे में अधिक जानकारी के लिए

var offset = 0;
var chunkSize = 2048;
var chunkBuffer = new Buffer(chunkSize);
var fp = fs.openSync('filepath', 'r');
var bytesRead = 0;
while(bytesRead = fs.readSync(fp, chunkBuffer, 0, chunkSize, offset)) {
    offset += bytesRead;
    var str = chunkBuffer.slice(0, bytesRead).toString();
    var arr = str.split('\n');

    if(bytesRead = chunkSize) {
        // the last item of the arr may be not a full line, leave it to the next chunk
        offset -= arr.pop().length;
    }
    lines.push(arr);
}
console.log(lines);

यह हो सकता है, कि एक असाइनमेंट के बजाय निम्नलिखित तुलना होनी चाहिए if(bytesRead = chunkSize):?
स्टीफन रीन

4

Node.js प्रलेखन रीडलाइन मॉड्यूल का उपयोग करके एक बहुत ही सुंदर उदाहरण प्रस्तुत करता है।

उदाहरण: फाइल स्ट्रीम लाइन-बाय-लाइन पढ़ें

const fs = require('fs');
const readline = require('readline');

const rl = readline.createInterface({
    input: fs.createReadStream('sample.txt'),
    crlfDelay: Infinity
});

rl.on('line', (line) => {
    console.log(`Line from file: ${line}`);
});

नोट: हम सीआरएफएफ ('\ r \ n') के सभी उदाहरणों को एकल पंक्ति विराम के रूप में पहचानने के लिए crlfDelay विकल्प का उपयोग करते हैं।


3

मुझे अभी भी यही समस्या थी। लगता है कि इस सुविधा के लिए कई मॉड्यूल की तुलना करने के बाद, मैंने इसे स्वयं करने का फैसला किया, यह जितना मैंने सोचा था उससे कहीं अधिक सरल है।

gist: https://gist.github.com/deemstone/8279565

var fetchBlock = lineByline(filepath, onEnd);
fetchBlock(function(lines, start){ ... });  //lines{array} start{int} lines[0] No.

यह एक क्लोजर में खोली गई फाइल को कवर करता है, जो fetchBlock()लौटा फाइल से एक ब्लॉक लाएगा, एंड टू स्प्लिट टू अरेज (पिछले सेंच से सेगमेंट को डील करेगा)।

मैंने प्रत्येक रीड ऑपरेशन के लिए ब्लॉक का आकार 1024 तक निर्धारित किया है। इसमें कीड़े हो सकते हैं, लेकिन कोड तर्क स्पष्ट है, इसे स्वयं आज़माएं।


2

नोड-बाइलाइन धाराओं का उपयोग करता है, इसलिए मैं आपकी विशाल फ़ाइलों के लिए पसंद करूंगा।

आपकी तिथि-रूपांतरणों के लिए मैं क्षण का उपयोग करता हूंjs

अपने थ्रूपुट को अधिकतम करने के लिए आप एक सॉफ्टवेयर-क्लस्टर का उपयोग करने के बारे में सोच सकते हैं। कुछ अच्छे मॉड्यूल हैं जो नोड-मूल क्लस्टर-मॉड्यूल को अच्छी तरह से लपेटते हैं। मुझे इसहाक से क्लस्टर-मास्टर पसंद है । उदाहरण के लिए, आप x श्रमिकों का एक समूह बना सकते हैं जो सभी एक फ़ाइल की गणना करते हैं।

बेंचमार्किंग स्प्लिट्स के लिए बनाम रेगीक्स बेंचमार्क.जेएस का उपयोग करते हैं । मैं havent यह अब तक का परीक्षण किया। बेंचमार्क.जेएस नोड-मॉड्यूल के रूप में उपलब्ध है


2

के आधार पर इस प्रश्न के उत्तर के मैंने एक वर्ग लागू किया है जिसका उपयोग आप एक फाइल को लाइन-बाय-लाइन पढ़ने के लिए कर सकते हैं fs.readSync()। आप इस "ठहराव" और "फिर से शुरू" एक Qवादे का उपयोग करके कर सकते हैं ( jQueryलगता है एक डोम की आवश्यकता है तो इसे साथ नहीं चला सकते nodejs):

var fs = require('fs');
var Q = require('q');

var lr = new LineReader(filenameToLoad);
lr.open();

var promise;
workOnLine = function () {
    var line = lr.readNextLine();
    promise = complexLineTransformation(line).then(
        function() {console.log('ok');workOnLine();},
        function() {console.log('error');}
    );
}
workOnLine();

complexLineTransformation = function (line) {
    var deferred = Q.defer();
    // ... async call goes here, in callback: deferred.resolve('done ok'); or deferred.reject(new Error(error));
    return deferred.promise;
}

function LineReader (filename) {      
  this.moreLinesAvailable = true;
  this.fd = undefined;
  this.bufferSize = 1024*1024;
  this.buffer = new Buffer(this.bufferSize);
  this.leftOver = '';

  this.read = undefined;
  this.idxStart = undefined;
  this.idx = undefined;

  this.lineNumber = 0;

  this._bundleOfLines = [];

  this.open = function() {
    this.fd = fs.openSync(filename, 'r');
  };

  this.readNextLine = function () {
    if (this._bundleOfLines.length === 0) {
      this._readNextBundleOfLines();
    }
    this.lineNumber++;
    var lineToReturn = this._bundleOfLines[0];
    this._bundleOfLines.splice(0, 1); // remove first element (pos, howmany)
    return lineToReturn;
  };

  this.getLineNumber = function() {
    return this.lineNumber;
  };

  this._readNextBundleOfLines = function() {
    var line = "";
    while ((this.read = fs.readSync(this.fd, this.buffer, 0, this.bufferSize, null)) !== 0) { // read next bytes until end of file
      this.leftOver += this.buffer.toString('utf8', 0, this.read); // append to leftOver
      this.idxStart = 0
      while ((this.idx = this.leftOver.indexOf("\n", this.idxStart)) !== -1) { // as long as there is a newline-char in leftOver
        line = this.leftOver.substring(this.idxStart, this.idx);
        this._bundleOfLines.push(line);        
        this.idxStart = this.idx + 1;
      }
      this.leftOver = this.leftOver.substring(this.idxStart);
      if (line !== "") {
        break;
      }
    }
  }; 
}

0
import * as csv from 'fast-csv';
import * as fs from 'fs';
interface Row {
  [s: string]: string;
}
type RowCallBack = (data: Row, index: number) => object;
export class CSVReader {
  protected file: string;
  protected csvOptions = {
    delimiter: ',',
    headers: true,
    ignoreEmpty: true,
    trim: true
  };
  constructor(file: string, csvOptions = {}) {
    if (!fs.existsSync(file)) {
      throw new Error(`File ${file} not found.`);
    }
    this.file = file;
    this.csvOptions = Object.assign({}, this.csvOptions, csvOptions);
  }
  public read(callback: RowCallBack): Promise < Array < object >> {
    return new Promise < Array < object >> (resolve => {
      const readStream = fs.createReadStream(this.file);
      const results: Array < any > = [];
      let index = 0;
      const csvStream = csv.parse(this.csvOptions).on('data', async (data: Row) => {
        index++;
        results.push(await callback(data, index));
      }).on('error', (err: Error) => {
        console.error(err.message);
        throw err;
      }).on('end', () => {
        resolve(results);
      });
      readStream.pipe(csvStream);
    });
  }
}
import { CSVReader } from '../src/helpers/CSVReader';
(async () => {
  const reader = new CSVReader('./database/migrations/csv/users.csv');
  const users = await reader.read(async data => {
    return {
      username: data.username,
      name: data.name,
      email: data.email,
      cellPhone: data.cell_phone,
      homePhone: data.home_phone,
      roleId: data.role_id,
      description: data.description,
      state: data.state,
    };
  });
  console.log(users);
})();

-1

मैंने बड़ी फाइल को अतुल्यकालिक पाठ या JSON पढ़ने के लिए एक नोड मॉड्यूल बनाया है। बड़ी फ़ाइलों पर परीक्षण किया गया।

var fs = require('fs')
, util = require('util')
, stream = require('stream')
, es = require('event-stream');

module.exports = FileReader;

function FileReader(){

}

FileReader.prototype.read = function(pathToFile, callback){
    var returnTxt = '';
    var s = fs.createReadStream(pathToFile)
    .pipe(es.split())
    .pipe(es.mapSync(function(line){

        // pause the readstream
        s.pause();

        //console.log('reading line: '+line);
        returnTxt += line;        

        // resume the readstream, possibly from a callback
        s.resume();
    })
    .on('error', function(){
        console.log('Error while reading file.');
    })
    .on('end', function(){
        console.log('Read entire file.');
        callback(returnTxt);
    })
);
};

FileReader.prototype.readJSON = function(pathToFile, callback){
    try{
        this.read(pathToFile, function(txt){callback(JSON.parse(txt));});
    }
    catch(err){
        throw new Error('json file is not valid! '+err.stack);
    }
};

बस फ़ाइल को file-reader.js के रूप में सहेजें, और इसे इस तरह उपयोग करें:

var FileReader = require('./file-reader');
var fileReader = new FileReader();
fileReader.readJSON(__dirname + '/largeFile.json', function(jsonObj){/*callback logic here*/});

7
मुझे लगता है कि आपने जेरार्ड के जवाब से नकल की है। आपके द्वारा कॉपी किए गए भाग के लिए आपको जेरार्ड क्रेडिट देना चाहिए।
पॉल लिंच
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.