Node.js'de büyük günlük dosyalarını ayrıştırma - satır satır okuma


126

Javascript / Node.js'de büyük (5-10 Gb) günlük dosyalarının bazılarını ayrıştırmam gerekiyor (Cube kullanıyorum).

Logline şuna benzer:

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

Biz (örn çıkarmaz bazı ayrıştırma yapmak, her satır okumak gerekir 5, 7ve SUCCESSsonra Küp içine bu verileri (pompa) https://github.com/square/cube onların JS istemcisi kullanarak).

İlk olarak, Node'da bir dosyada satır satır okumanın kurallı yolu nedir?

İnternette oldukça yaygın bir soru gibi görünüyor:

Yanıtların çoğu, bir grup üçüncü taraf modülüne işaret ediyor gibi görünüyor:

Bununla birlikte, bu oldukça basit bir görev gibi görünüyor - elbette, stdlib içinde bir metin dosyasını satır satır okumak için basit bir yol var?

İkinci olarak, daha sonra her satırı işlemem gerekiyor (örneğin, zaman damgasını bir Tarih nesnesine dönüştürmek ve yararlı alanları ayıklamak).

Verimi en üst düzeye çıkararak bunu yapmanın en iyi yolu nedir? Her satırı okumayı veya Cube'a göndermeyi engellemeyen bir yol var mı?

Üçüncüsü - dize bölmeleri kullandığımı tahmin ediyorum ve contains'un JS eşdeğeri (IndexOf! = -1?) Normal ifadelerden çok daha hızlı olacak Node.js'de büyük miktarda metin verisini ayrıştırma konusunda çok tecrübesi olan var mı?

Şerefe, Victor


Düğümde, yerleşik 'yakalamalar' ve JSON'a çıktılar içeren bir grup normal ifade dizesi alan bir günlük ayrıştırıcı oluşturdum. Bir hesaplama yapmak istiyorsanız, her çekimde işlevleri bile çağırabilirsiniz. İstediğinizi yapabilir: npmjs.org/package/logax
Jess

Yanıtlar:


209

Çok büyük dosyaları (gbs) bir akış kullanarak satır satır ayrıştırmak için bir çözüm aradım. Tüm üçüncü taraf kitaplıkları ve örnekleri, dosyaları satır satır değil (1, 2, 3, 4 .. gibi) işledikleri veya tüm dosyayı belleğe okudukları için ihtiyaçlarımı karşılamadı

Aşağıdaki çözüm, çok büyük dosyaları akış ve boruyu kullanarak satır satır ayrıştırabilir. Test için 17.000.000 kayıt içeren 2.1 gb dosya kullandım. Ram kullanımı 60 mb'yi geçmedi.

İlk olarak, olay akışı paketini kurun :

npm install event-stream

Sonra:

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

görüntü açıklamasını buraya girin

Lütfen nasıl gittiğini bana bildirin!


6
Bilginize, bu kod eşzamanlı değil. Eşzamansız. console.log(lineNr)Kodunuzun son satırından sonra eklerseniz , dosya eşzamansız olarak okunduğu için son satır sayısını göstermez.
jfriend00

4
Teşekkürler, bu gerçekten duraklatıldığını ve olması gerektiği zaman devam ettiğini bulabildiğim tek çözümdü. Readline yapmadı.
Brent

3
Harika bir örnek ve aslında duraklıyor. Ek olarak, dosyayı erken okumayı durdurmaya karar verirseniz, şunu kullanabilirsinizs.end();
zipzit

2
Tılsım gibi çalıştı. 150 milyon dokümanı elasticsearch indeksine indekslemek için kullandı. readlinemodül bir acıdır. Durmuyor ve 40-50 milyondan sonra her seferinde arızaya neden oluyordu. Bir gün boşa gitti. Cevap için çok teşekkürler. Bu mükemmel çalışıyor
Mandeep Singh

3
olay akışının güvenliği ihlal edildi: medium.com/intrinsic/… ancak 4+ görünüşe göre güvenli blog.npmjs.org/post/180565383195/…
John Vandivier

72

Dahili readlinepaketi kullanabilirsiniz, buradaki belgelere bakın . Kullandığım akışı yeni bir çıkış akışı oluşturmak için.

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

Büyük dosyaların işlenmesi biraz zaman alacaktır. İşe yarayıp yaramadığını söyle.


2
Yazıldığı gibi, cubestuff tanımlanmadığı için ikinciden son satıra kadar başarısız olur.
Greg

2
Kullanarak readline, "şeyler yap" alanında eşzamansız eylemler gerçekleştirmek için okuma akışını duraklatmak / devam ettirmek mümkün müdür?
jchook

1
@jchook readline, duraklatmayı / devam ettirmeyi denediğimde bana birçok sorun veriyordu. Akış aşağı süreç
yavaşsa

31

Burada doğru cevap olmayı hak eden @gerard cevabını gerçekten beğendim . Bazı iyileştirmeler yaptım:

  • Kod bir sınıfta (modüler)
  • Ayrıştırma dahildir
  • Eşzamansız bir işin, DB'ye ekleme gibi CSV'yi okumaya zincirlenmesi veya bir HTTP isteği olması durumunda, devam etme yeteneği dışarıya verilir.
  • Kullanıcının bildirebileceği yığınlar / toplu iş boyutlarında okuma. Farklı kodlamada dosyalarınız olması durumunda, akıştaki kodlamayı da hallettim.

İşte kod:

'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

Yani temel olarak, işte bunu nasıl kullanacağınız:

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

Bunu 35GB'lık bir CSV dosyasıyla test ettim ve benim için işe yaradı ve bu yüzden @gerard'ın cevabı üzerine inşa etmeyi seçtim , geri bildirimler memnuniyetle karşılanıyor.


ne kadar zaman aldı?
Z. Khullah

Görünüşe göre, pause()bunda arama yok, değil mi?
Vanuan

Ayrıca, bu, sonunda geri arama işlevini çağırmaz. Yani batchSize 100, dosyaların boyutu 150 ise, yalnızca 100 öğe işlenir. Yanlış mıyım?
Vanuan

16

Bir metin dosyasından 1 000 000'den fazla satırı okumak için https://www.npmjs.com/package/line-by-line kullandım . Bu durumda, dolu bir RAM kapasitesi yaklaşık 50-60 megabayttı.

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

'satır satır' seçilen cevaptan daha fazla hafıza verimlidir. Bir csv'deki 1 milyon satır için, seçilen yanıt benim düğüm işlemimi düşük 800 megabayt değerinde yaptı. "Satır satır" kullanıldığında, tutarlı bir şekilde düşük 700'lerdeydi. Bu modül aynı zamanda kodu temiz ve okunması kolay tutar. Toplamda yaklaşık 18 milyon okumam gerekecek, bu yüzden her mb önemli!
Neo

Bunun standart "yığın" yerine kendi olay "satırını" kullanması çok yazık, yani "boru" yu kullanamayacaksınız.
Rene Wooller

Saatler süren test ve araştırmalardan sonra, aslında lr.cancel()yöntem üzerinde duran tek çözüm budur . Bir 5Gig dosyasının ilk 1000 satırını 1 ms'de okur. Korku veren !!!!
Perez Lamed van Niekerk

6

Büyük dosyayı satır satır okumanın yanı sıra, yığınlar halinde de okuyabilirsiniz. Daha fazla bilgi için bu makaleye bakın

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

Aşağıdakilerin bir atama yerine bir karşılaştırma olması mümkün olabilir if(bytesRead = chunkSize)mi :?
Stefan Rein

4

Node.js Dokümantasyonu, Readline modülünü kullanarak çok zarif bir örnek sunar.

Örnek: Dosya Akışını Satır Satır Okuyun

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

Not: CR LF'nin ('\ r \ n') tüm örneklerini tek bir satır sonu olarak tanımak için crlfDelay seçeneğini kullanırız.


3

Henüz aynı sorunu yaşadım. Bu özelliğe sahip gibi görünen birkaç modülü karşılaştırdıktan sonra, kendim yapmaya karar verdim, düşündüğümden daha basit.

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

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

Bir kapanışta açılan dosyayı kapsar, fetchBlock()döndürülen dosyadan bir blok alır, diziye bölünür (son getirmeden itibaren segmenti ele alır).

Her okuma işlemi için blok boyutunu 1024 olarak ayarladım. Bunda hatalar olabilir, ancak kod mantığı açıktır, kendiniz deneyin.


2

node-byline akışları kullanır, bu yüzden büyük dosyalarınız için bunu tercih ederim.

tarih dönüşümleriniz için moment.js'yi kullanırdım .

veriminizi en üst düzeye çıkarmak için bir yazılım kümesi kullanmayı düşünebilirsiniz. düğüm yerel küme modülünü oldukça iyi saran bazı güzel modüller vardır. isaacs'dan cluster-master'ı seviyorum . örneğin, tümü bir dosyayı hesaplayan x çalışanlardan oluşan bir küme oluşturabilirsiniz.

regexes vs böler benchmarking için kullanmak benchmark.js . şimdiye kadar test etmedim. benchmark.js bir düğüm modülü olarak mevcuttur


2

Bu soruların cevabına dayanarak, bir dosyayı eşzamanlı olarak satır satır okumak için kullanabileceğiniz bir sınıf uyguladım fs.readSync(). Bunu bir Qsöz kullanarak "duraklat" ve "devam ettir" yapabilirsiniz ( jQuerybir DOM gerektiriyor, bu yüzden birlikte çalıştıramazsınız 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

Büyük dosyayı eşzamansız olarak metin veya JSON okumak için bir düğüm modülü yaptım. Büyük dosyalarda test edildi.

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

Dosyayı dosya-reader.js olarak kaydedin ve şu şekilde kullanın:

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

7
Görünüşe göre Gerard'ın cevabını kopyaladınız. Kopyaladığınız bölüm için Gerard'a kredi vermelisiniz.
Paul Lynch
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.