J'ai besoin d'analyser de gros fichiers journaux (5-10 Go) en Javascript/Node.js (j'utilise un cube).
La logline 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, effectuer une analyse syntaxique (par exemple effacer 5
, 7
et SUCCESS
), puis pomper ces données dans un cube ( https://github.com/square/cube ) à l'aide de leur client JS.
Premièrement, quelle est la manière canonique dans Node de lire dans un fichier, ligne par ligne?
Cela semble être une question assez courante en ligne:
Un grand nombre de réponses semblent pointer vers des modules tiers:
Cependant, cela semble être une tâche assez simple - il y a sûrement un moyen simple dans stdlib de lire un fichier texte, ligne par ligne?
Deuxièmement, je dois ensuite traiter chaque ligne (par exemple, convertir l’horodatage en un objet Date et extraire des champs utiles).
Quelle est la meilleure façon de faire cela, en maximisant le débit? Existe-t-il un moyen qui ne bloque ni la lecture de chaque ligne ni son envoi à Cube?
Troisièmement - je suppose que l’utilisation des divisions de chaîne et l’équivalent JS de conte (IndexOf! = -1?) Seront beaucoup plus rapides que les expressions rationnelles? Quelqu'un a-t-il beaucoup d'expérience dans l'analyse de grandes quantités de données texte dans Node.js?
A bientôt, Victor
J'ai cherché une solution pour analyser de très gros fichiers (gbs) ligne par ligne à l'aide d'un flux. Toutes les bibliothèques tierces et les exemples ne répondaient pas à mes besoins car ils traitaient les fichiers pas ligne par ligne (comme 1, 2, 3, 4 ..) ou lisaient le fichier entier en mémoire.
La solution suivante permet d’analyser des fichiers très volumineux, ligne par ligne, à l’aide de stream & pipe. Pour les tests, j'ai utilisé un fichier de 2,1 gb avec 17 000 000 enregistrements. L'utilisation du bélier n'a pas dépassé 60 mb.
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.')
})
);
S'il vous plaît laissez-moi savoir comment ça se passe!
Vous pouvez utiliser le package readline
intégré, voir docs ici . J'utilise stream 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 gros fichiers prendront un certain temps à traiter. Dites si cela fonctionne.
J'ai vraiment aimé @gerard answer qui mérite en fait d'être la bonne réponse ici. J'ai fait quelques améliorations:
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, fondamentalement, voici comment vous allez l'utiliser:
let reader = CSVReader('path_to_file.csv')
reader.read(() => reader.continue())
J'ai testé cela avec un fichier CSV de 35 Go et cela a fonctionné pour moi. C'est pourquoi j'ai choisi de le construire sur la réponse de @gerard , les retours sont les bienvenus.
J'ai utilisé https://www.npmjs.com/package/line-by-line pour lire plus de 1 000 000 lignes d'un fichier texte. Dans ce cas, la capacité occupée de 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.
});
En plus de lire le gros fichier ligne par ligne, vous pouvez également le lire morceau par morceau. Pour plus d'informations, reportez-vous à cet article
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);
J'ai eu le même problème encore. Après avoir comparé plusieurs modules qui semblent avoir cette fonctionnalité, j'ai décidé de le faire moi-même, c'est plus simple que je ne le pensais.
Gist: https://Gist.github.com/deemstone/8279565
var fetchBlock = lineByline(filepath, onEnd);
fetchBlock(function(lines, start){ ... }); //lines{array} start{int} lines[0] No.
Il couvre le fichier ouvert dans une fermeture, que fetchBlock()
retourné va chercher un bloc dans le fichier, end split en tableau (traitera le segment du dernier extrait)
J'ai défini la taille de bloc à 1024 pour chaque opération de lecture. Cela peut avoir des bugs, mais la logique du code est évidente, essayez vous-même.
J'ai créé un module de nœud pour lire de gros fichiers de manière asynchrone du texte ou 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);
}
};
Sauvegardez simplement le fichier sous le nom file-reader.js et utilisez-le comme ceci:
var FileReader = require('./file-reader');
var fileReader = new FileReader();
fileReader.readJSON(__dirname + '/largeFile.json', function(jsonObj){/*callback logic here*/});
node-byline utilise des flux, je préférerais donc celui-ci pour vos gros fichiers.
pour vos conversions de date, je voudrais utiliser moment.js .
pour maximiser votre débit, vous pouvez envisager d’utiliser un cluster de logiciels. Il existe quelques Nice-modules qui encapsulent assez bien le module cluster natif-nœud. j'aime cluster-master d'isaacs. par exemple. vous pouvez créer un cluster de x travailleurs qui calculent tous un fichier.
pour effectuer une analyse comparative entre les divisions et les expressions rationnelles, utilisez benchmark.js . Je n'ai pas testé jusqu'à maintenant. benchmark.js est disponible en tant que module de nœud
La documentation Node.js offre 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}`);
});
Remarque: nous utilisons l’option crlfDelay pour reconnaître toutes les instances de CR LF ('\ r\n') sous la forme d’un saut de ligne.
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);
})();
Sur la base de this questions, j'ai implémenté une classe que vous pouvez utiliser pour lire un fichier de manière synchrone, ligne par ligne, avec fs.readSync()
. Vous pouvez faire cette "pause" et "reprendre" en utilisant une promesse Q
(jQuery
semble nécessiter un DOM, vous ne pouvez donc pas l'exécuter avec 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;
}
}
};
}