148 votes

Analyser des fichiers journaux volumineux en Node.js - lire ligne par ligne

Je dois analyser des fichiers journaux volumineux (5 à 10 Go) en Javascript/Node.js (j'utilise Cube).

La ligne de conduite ressemble à quelque chose comme :

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

Nous devons lire chaque ligne, faire un peu d'analyse syntaxique (par exemple, enlever les caractères 5 , 7 et SUCCESS ), puis de pomper ces données dans Cube ( https://github.com/square/cube ) en utilisant leur client JS.

Tout d'abord, quelle est la manière canonique dans Node de lire un fichier, ligne par ligne ?

Cette question semble être assez courante sur Internet :

Beaucoup de réponses semblent pointer vers un tas de modules tiers :

Cependant, cela semble être une tâche assez basique - il y a sûrement un moyen simple dans la stdlib pour lire un fichier texte, ligne par ligne ?

Ensuite, je dois traiter chaque ligne (par exemple, convertir l'horodatage en un objet Date, et extraire les champs utiles).

Quelle est la meilleure façon de le faire, en maximisant le débit ? Existe-t-il un moyen de ne pas bloquer la lecture de chaque ligne ou l'envoi au Cube ?

Troisièmement, je suppose que l'utilisation des séparations de chaînes de caractères et de l'équivalent JS de contains (IndexOf != -1 ?) sera beaucoup plus rapide que les regex ? Quelqu'un a-t-il une grande expérience de l'analyse de quantités massives de données textuelles dans Node.js ?

A la vôtre, Victor

247voto

Gerard Points 92

J'ai cherché une solution pour analyser de très gros fichiers (gigaoctets) ligne par ligne en utilisant un flux. Toutes les bibliothèques et exemples tiers ne répondaient pas à mes besoins car ils ne traitaient pas les fichiers ligne par ligne (comme 1 , 2 , 3 , 4 ..) ou lisaient le fichier entier en mémoire.

La solution suivante peut analyser de très gros fichiers, ligne par ligne, en utilisant stream & pipe. Pour les tests, j'ai utilisé un fichier de 2.1 gb avec 17.000.000 d'enregistrements. L'utilisation de la mémoire vive n'a pas dépassé 60 mb.

Tout d'abord, installez le flux d'événements paquet :

npm install event-stream

Ensuite :

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

enter image description here

Faites-moi savoir comment ça se passe !

88voto

user568109 Points 21253

Vous pouvez utiliser la fonction intégrée readline voir la documentation aquí . J'utilise flux pour créer un nouveau flux de sortie.

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

Les fichiers volumineux prendront un certain temps à être traités. Dites-nous si cela fonctionne.

35voto

Ambodi Points 636

J'ai vraiment aimé @gerard qui mérite en fait d'être la réponse correcte ici. J'ai fait quelques améliorations :

  • Le code est dans une classe (modulaire)
  • L'analyse syntaxique est incluse
  • La capacité de reprise est donnée à l'extérieur au cas où un travail asynchrone est enchaîné à la lecture du CSV, comme l'insertion dans la base de données, ou une requête HTTP.
  • Lire par morceaux/tailles qui l'utilisateur peut déclarer. J'ai pris soin de l'encodage dans le flux aussi, au cas où vous avez des fichiers avec des encodages différents.

Voici le code :

'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

Donc, en gros, voici comment vous allez l'utiliser :

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

Je l'ai testé avec un fichier CSV de 35 Go et cela a fonctionné pour moi, c'est pourquoi j'ai choisi de le construire sur @gerard Les commentaires sont les bienvenus.

24voto

Eugene Ilyushin Points 468

J'ai utilisé https://www.npmjs.com/package/line-by-line pour lire plus de 1 000 000 de lignes d'un fichier texte. Dans ce cas, la capacité occupée de la RAM était d'environ 50-60 mégaoctets.

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

18voto

Jaime Gómez Points 1327

La documentation Node.js propose un exemple très élégant utilisant le module Readline.

Exemple : Lire le flux de fichiers ligne par ligne

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

Note : nous utilisons l'option crlfDelay pour reconnaître toutes les instances de CR LF (' \r\n ) comme un simple saut de ligne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X