Przetwarzanie pliku CSV przy użyciu NodeJS


125

W nodejs chcę przeanalizować plik .csv zawierający 10000 rekordów i wykonać pewne operacje na każdym wierszu. Próbowałem użyć http://www.adaltas.com/projects/node-csv . Nie mogłem zatrzymać tego w każdym rzędzie. To tylko czyta wszystkie 10000 rekordów. Muszę wykonać następujące czynności:

  1. czytaj csv linia po linii
  2. wykonać czasochłonną operację na każdej linii
  3. przejdź do następnej linii

Czy ktoś może tu zasugerować jakieś alternatywne pomysły?



1
Czy dodałeś wywołania zwrotne dla każdego wiersza? W przeciwnym razie po prostu odczyta je wszystkie asynchronicznie.
Ben Fortune

Odpowiedzi:


81

Wygląda na to, że musisz użyć jakiegoś rozwiązania opartego na strumieniu, istniały już takie biblioteki, więc zanim wymyślisz siebie na nowo, wypróbuj tę bibliotekę, która obejmuje również obsługę walidacji. https://www.npmjs.org/package/fast-csv


27
NodeCSV jest również dobrze obsługiwany i ma o około jeden rząd więcej użytkowników. npmjs.com/package/csv
steampowered

4
fast-csv jest szybki, łatwy w użyciu i do rozpoczęcia pracy.
Roger Garzon Nieto,

1
Czy obsługuje adres URL?
DMS-KH

57

Użyłem w ten sposób: -

var fs = require('fs'); 
var parse = require('csv-parse');

var csvData=[];
fs.createReadStream(req.file.path)
    .pipe(parse({delimiter: ':'}))
    .on('data', function(csvrow) {
        console.log(csvrow);
        //do something with csvrow
        csvData.push(csvrow);        
    })
    .on('end',function() {
      //do something with csvData
      console.log(csvData);
    });

2
Może robię coś źle, ale kiedy to uruchamiam, parsenie jest zdefiniowane. Czy jest coś, czego mi brakuje? Kiedy uruchamiam, npm install csv-parsea następnie w moim kodzie dodaję var parse = require("csv-parse");, to działa. Jesteś pewien, że twój działa? Tak czy inaczej, uwielbiam to rozwiązanie (nawet jeśli muszę dołączyć csv-parsemoduł
Ian

1
masz rację @lan, powinien zawierać csv-parsemoduł.
winnica

1
Wspaniale, dziękujemy za zweryfikowanie i zaktualizowanie odpowiedzi!
Ian

3
Niezłe rozwiązanie. Pracuje dla mnie.
Sun Bee

3
niestety to jest złe - mam błędy z dużymi plikami i długimi liniami .... (błędy pamięci - choć inne sposoby odczytu - działa)
Seti

55

Moje obecne rozwiązanie używa modułu asynchronicznego do wykonywania w serii:

var fs = require('fs');
var parse = require('csv-parse');
var async = require('async');

var inputFile='myfile.csv';

var parser = parse({delimiter: ','}, function (err, data) {
  async.eachSeries(data, function (line, callback) {
    // do something with the line
    doSomething(line).then(function() {
      // when processing finishes invoke the callback to move to the next one
      callback();
    });
  })
});
fs.createReadStream(inputFile).pipe(parser);

1
Myślę, że brakuje ci niektórych ')'?
Steven Luong C,

Myślę, że dodanie „)” na końcu wierszy 14 i 15 powinno rozwiązać problem.
Jon

@ShashankVivek - w tej starej odpowiedzi (z 2015 r.) „Async” to używana biblioteka npm. Więcej na ten temat tutaj caolan.github.io/async - aby zrozumieć, dlaczego może to pomaga blog.risingstack.com/node-hero-async-programming-in-node-js Ale javascript bardzo ewoluował od 2015 roku, a jeśli twoje pytanie jest więcej o async w ogóle, a następnie przeczytaj ten najnowszy artykuł medium.com/@tkssharma/ ...
prule

15
  • To rozwiązanie używa csv-parserzamiast csv-parseużywanego w niektórych z powyższych odpowiedzi.
  • csv-parserpojawił się około 2 lata później csv-parse.
  • Oba rozwiązują ten sam cel, ale osobiście znalazłem csv-parserlepsze, ponieważ dzięki temu łatwo jest obsługiwać nagłówki.

Najpierw zainstaluj parser csv:

npm install csv-parser

Więc załóżmy, że masz taki plik csv:

NAME, AGE
Lionel Messi, 31
Andres Iniesta, 34

Możesz wykonać wymaganą operację jako:

const fs = require('fs'); 
const csv = require('csv-parser');

fs.createReadStream(inputFilePath)
.pipe(csv())
.on('data', function(data){
    try {
        console.log("Name is: "+data.NAME);
        console.log("Age is: "+data.AGE);

        //perform the operation
    }
    catch(err) {
        //error handler
    }
})
.on('end',function(){
    //some final operation
});  

Więcej informacji można znaleźć w sekcji


13

Aby wstrzymać przesyłanie strumieniowe w fast-csv , możesz wykonać następujące czynności:

let csvstream = csv.fromPath(filePath, { headers: true })
    .on("data", function (row) {
        csvstream.pause();
        // do some heavy work
        // when done resume the stream
        csvstream.resume();
    })
    .on("end", function () {
        console.log("We are done!")
    })
    .on("error", function (error) {
        console.log(error)
    });

csvstream.pause () i resume () są tym, czego szukałem! Moim aplikacjom zawsze zabrakło pamięci, ponieważ odczytywały dane znacznie szybciej niż to, co mogło przetworzyć.
ehrhardt

@adnan Dzięki za wskazanie tego. Nie ma o tym wzmianki w dokumentacji i tego też szukałem.
Piyush Beli,

10

Projekt node-csv, do którego się odwołujesz, jest całkowicie wystarczający do zadania przekształcenia każdego wiersza dużej części danych CSV z dokumentów pod adresem : http://csv.adaltas.com/transform/ :

csv()
  .from('82,Preisner,Zbigniew\n94,Gainsbourg,Serge')
  .to(console.log)
  .transform(function(row, index, callback){
    process.nextTick(function(){
      callback(null, row.reverse());
    });
});

Z własnego doświadczenia mogę powiedzieć, że jest to również dość szybka implementacja, pracowałem z nią na zestawach danych z blisko 10k rekordami, a czasy przetwarzania były na rozsądnym poziomie kilkudziesięciu milisekund dla całego zestawu.

W związku z sugestią rozwiązania opartego na strumieniu jurki : Node-csv IS oparty na strumieniu i zgodny z API przesyłania strumieniowego Node.js.


8

Plik fast-csv npm może czytać dane wiersz po wierszu z pliku csv.

Oto przykład:

let csv= require('fast-csv');

var stream = fs.createReadStream("my.csv");

csv
 .parseStream(stream, {headers : true})
 .on("data", function(data){
     console.log('I am one line of data', data);
 })
 .on("end", function(){
     console.log("done");
 });

1
fast-csv@4.0.2 ma no, fromStream()a na stronie projektu brakuje przykładów i dokumentacji.
Cees Timmerman

3

Potrzebowałem asynchronicznego czytnika csv i początkowo próbowałem odpowiedzi @Pransh Tiwari, ale nie mogłem go uruchomić z awaiti util.promisify(). W końcu natknąłem się na node-csvtojson , który robi prawie to samo co csv-parser, ale z obietnicami. Oto przykład użycia csvtojson w akcji:

const csvToJson = require('csvtojson');

const processRecipients = async () => {
    const recipients = await csvToJson({
        trim:true
    }).fromFile('./recipients.csv');

    // Code executes after recipients are fully loaded.
    recipients.forEach((recipient) => {
        console.log(recipient.name, recipient.email);
    });
};

2

Wypróbuj wtyczkę linia po linii npm.

npm install line-by-line --save

5
Zainstalowanie wtyczki nie było pytaniem, które zostało zadane. O wiele bardziej korzystne byłoby dodanie kodu wyjaśniającego, jak używać wtyczki i / lub dlaczego OP powinien jej używać .
domdambrogia

2

to jest moje rozwiązanie, aby uzyskać plik CSV z zewnętrznego adresu URL

const parse = require( 'csv-parse/lib/sync' );
const axios = require( 'axios' );
const readCSV = ( module.exports.readCSV = async ( path ) => {
try {
   const res = await axios( { url: path, method: 'GET', responseType: 'blob' } );
   let records = parse( res.data, {
      columns: true,
      skip_empty_lines: true
    } );

    return records;
 } catch ( e ) {
   console.log( 'err' );
 }

} );
readCSV('https://urltofilecsv');

2

Obejście problemu wykonania tego zadania z await / async :

const csv = require('csvtojson')
const csvFilePath = 'data.csv'
const array = await csv().fromFile(csvFilePath);

2

Ok, więc jest tu wiele odpowiedzi i nie sądzę, aby odpowiadały one na twoje pytanie, które moim zdaniem jest podobne do mojego.

Musisz wykonać operację, taką jak kontakt z bazą danych lub interfejsem API trzeciej części, która zajmie trochę czasu i jest asynchroniczna. Nie chcesz ładować całego dokumentu do pamięci, ponieważ jest zbyt duży lub z innego powodu, więc musisz czytać wiersz po wierszu, aby go przetworzyć.

Przeczytałem dokumenty fs i może wstrzymać czytanie, ale użycie wywołania .on ('data') sprawi, że większość z tych odpowiedzi będzie ciągła i spowoduje problem.


AKTUALIZACJA: Wiem więcej informacji o strumieniach niż kiedykolwiek chciałem

Najlepszym sposobem na to jest utworzenie strumienia z możliwością zapisu. Spowoduje to przesłanie danych csv do twojego zapisywalnego strumienia, którym możesz zarządzać wywołaniami asyncronus. Potok będzie zarządzał buforem z powrotem do czytnika, więc nie skończysz z dużym zużyciem pamięci

Prosta wersja

const parser = require('csv-parser');
const stripBom = require('strip-bom-stream');
const stream = require('stream')

const mySimpleWritable = new stream.Writable({
  objectMode: true, // Because input is object from csv-parser
  write(chunk, encoding, done) { // Required
    // chunk is object with data from a line in the csv
    console.log('chunk', chunk)
    done();
  },
  final(done) { // Optional
    // last place to clean up when done
    done();
  }
});
fs.createReadStream(fileNameFull).pipe(stripBom()).pipe(parser()).pipe(mySimpleWritable)

Wersja klasy

const parser = require('csv-parser');
const stripBom = require('strip-bom-stream');
const stream = require('stream')
// Create writable class
class MyWritable extends stream.Writable {
  // Used to set object mode because we get an object piped in from csv-parser
  constructor(another_variable, options) {
    // Calls the stream.Writable() constructor.
    super({ ...options, objectMode: true });
    // additional information if you want
    this.another_variable = another_variable
  }
  // The write method
  // Called over and over, for each line in the csv
  async _write(chunk, encoding, done) {
    // The chunk will be a line of your csv as an object
    console.log('Chunk Data', this.another_variable, chunk)

    // demonstrate await call
    // This will pause the process until it is finished
    await new Promise(resolve => setTimeout(resolve, 2000));

    // Very important to add.  Keeps the pipe buffers correct.  Will load the next line of data
    done();
  };
  // Gets called when all lines have been read
  async _final(done) {
    // Can do more calls here with left over information in the class
    console.log('clean up')
    // lets pipe know its done and the .on('final') will be called
    done()
  }
}

// Instantiate the new writable class
myWritable = new MyWritable(somevariable)
// Pipe the read stream to csv-parser, then to your write class
// stripBom is due to Excel saving csv files with UTF8 - BOM format
fs.createReadStream(fileNameFull).pipe(stripBom()).pipe(parser()).pipe(myWritable)

// optional
.on('finish', () => {
  // will be called after the wriables internal _final
  console.log('Called very last')
})

STARA METODA:

PROBLEM Z czytelnym

const csv = require('csv-parser');
const fs = require('fs');

const processFileByLine = async(fileNameFull) => {

  let reading = false

  const rr = fs.createReadStream(fileNameFull)
  .pipe(csv())

  // Magic happens here
  rr.on('readable', async function(){
    // Called once when data starts flowing
    console.log('starting readable')

    // Found this might be called a second time for some reason
    // This will stop that event from happening
    if (reading) {
      console.log('ignoring reading')
      return
    }
    reading = true
    
    while (null !== (data = rr.read())) {
      // data variable will be an object with information from the line it read
      // PROCESS DATA HERE
      console.log('new line of data', data)
    }

    // All lines have been read and file is done.
    // End event will be called about now so that code will run before below code

    console.log('Finished readable')
  })


  rr.on("end", function () {
    // File has finished being read
    console.log('closing file')
  });

  rr.on("error", err => {
    // Some basic error handling for fs error events
    console.log('error', err);
  });
}

Zauważysz readingflagę. Zauważyłem, że z jakiegoś powodu tuż pod koniec pliku .on („czytelny”) jest wywoływany po raz drugi na małych i dużych plikach. Nie jestem pewien dlaczego, ale to blokuje to od drugiego procesu odczytującego te same elementy zamówienia.


1

Używam tego prostego: https://www.npmjs.com/package/csv-parser

Bardzo prosty w użyciu:

const csv = require('csv-parser')
const fs = require('fs')
const results = [];

fs.createReadStream('./CSVs/Update 20191103C.csv')
  .pipe(csv())
  .on('data', (data) => results.push(data))
  .on('end', () => {
    console.log(results);
    console.log(results[0]['Lowest Selling Price'])
  });

1

Używałem, csv-parseale w przypadku większych plików występowały problemy z wydajnością, jedną z lepszych bibliotek, które znalazłem, jest Papa Parse , dokumenty są dobre, dobre wsparcie, lekkie, bez zależności.

zainstalować papaparse

npm install papaparse

Stosowanie:

  • async / await
const fs = require('fs');
const Papa = require('papaparse');

const csvFilePath = 'data/test.csv'

// Function to read csv which returns a promise so you can do async / await.

const readCSV = async (filePath) => {
  const csvFile = fs.readFileSync(filePath)
  const csvData = csvFile.toString()  
  return new Promise(resolve => {
    Papa.parse(csvData, {
      header: true,
      transformHeader: header => header.trim(),
      complete: results => {
        console.log('Complete', results.data.length, 'records.'); 
        resolve(results.data);
      }
    });
  });
};

const test = async () => {
  let parsedData = await readCSV(csvFilePath); 
}

test()
  • oddzwonić
const fs = require('fs');
const Papa = require('papaparse');

const csvFilePath = 'data/test.csv'

const file = fs.createReadStream(csvFilePath);

var csvData=[];
Papa.parse(file, {
  header: true,
  transformHeader: header => header.trim(),
  step: function(result) {
    csvData.push(result.data)
  },
  complete: function(results, file) {
    console.log('Complete', csvData.length, 'records.'); 
  }
});

Uwaga header: truejest opcją w konfiguracji, zobacz dokumentację, aby uzyskać inne opcje


0
fs = require('fs');
fs.readFile('FILENAME WITH PATH','utf8', function(err,content){
if(err){
    console.log('error occured ' +JSON.stringify(err));
 }
 console.log('Fileconetent are ' + JSON.stringify(content));
})

0

Możesz przekonwertować csv na format json za pomocą modułu csv-to-json, a następnie możesz łatwo użyć pliku json w swoim programie


-1

npm zainstaluj csv

Przykładowy plik CSV Będziesz potrzebować pliku CSV do analizy, więc albo już go masz, albo możesz skopiować tekst poniżej i wkleić go do nowego pliku i nazwać ten plik "mycsv.csv"

ABC, 123, Fudge
532, CWE, ICECREAM
8023, POOP, DOGS
441, CHEESE, CARMEL
221, ABC, HOUSE
1
ABC, 123, Fudge
2
532, CWE, ICECREAM
3
8023, POOP, DOGS
4
441, CHEESE, CARMEL
5
221, ABC, HOUSE

Przykładowy odczyt kodu i analiza pliku CSV

Utwórz nowy plik i wstaw do niego następujący kod. Przeczytaj koniecznie, co dzieje się za kulisami.

    var csv = require('csv'); 
    // loads the csv module referenced above.

    var obj = csv(); 
    // gets the csv module to access the required functionality

    function MyCSV(Fone, Ftwo, Fthree) {
        this.FieldOne = Fone;
        this.FieldTwo = Ftwo;
        this.FieldThree = Fthree;
    }; 
    // Define the MyCSV object with parameterized constructor, this will be used for storing the data read from the csv into an array of MyCSV. You will need to define each field as shown above.

    var MyData = []; 
    // MyData array will contain the data from the CSV file and it will be sent to the clients request over HTTP. 

    obj.from.path('../THEPATHINYOURPROJECT/TOTHE/csv_FILE_YOU_WANT_TO_LOAD.csv').to.array(function (data) {
        for (var index = 0; index < data.length; index++) {
            MyData.push(new MyCSV(data[index][0], data[index][1], data[index][2]));
        }
        console.log(MyData);
    });
    //Reads the CSV file from the path you specify, and the data is stored in the array we specified using callback function.  This function iterates through an array and each line from the CSV file will be pushed as a record to another array called MyData , and logs the data into the console to ensure it worked.

var http = require('http');
//Load the http module.

var server = http.createServer(function (req, resp) {
    resp.writeHead(200, { 'content-type': 'application/json' });
    resp.end(JSON.stringify(MyData));
});
// Create a webserver with a request listener callback.  This will write the response header with the content type as json, and end the response by sending the MyData array in JSON format.

server.listen(8080);
// Tells the webserver to listen on port 8080(obviously this may be whatever port you want.)
1
var csv = require('csv'); 
2
// loads the csv module referenced above.
3

4
var obj = csv(); 
5
// gets the csv module to access the required functionality
6

7
function MyCSV(Fone, Ftwo, Fthree) {
8
    this.FieldOne = Fone;
9
    this.FieldTwo = Ftwo;
10
    this.FieldThree = Fthree;
11
}; 
12
// Define the MyCSV object with parameterized constructor, this will be used for storing the data read from the csv into an array of MyCSV. You will need to define each field as shown above.
13

14
var MyData = []; 
15
// MyData array will contain the data from the CSV file and it will be sent to the clients request over HTTP. 
16

17
obj.from.path('../THEPATHINYOURPROJECT/TOTHE/csv_FILE_YOU_WANT_TO_LOAD.csv').to.array(function (data) {
18
    for (var index = 0; index < data.length; index++) {
19
        MyData.push(new MyCSV(data[index][0], data[index][1], data[index][2]));
20
    }
21
    console.log(MyData);
22
});
23
//Reads the CSV file from the path you specify, and the data is stored in the array we specified using callback function.  This function iterates through an array and each line from the CSV file will be pushed as a record to another array called MyData , and logs the data into the console to ensure it worked.
24

25
var http = require('http');
26
//Load the http module.
27

28
var server = http.createServer(function (req, resp) {
29
    resp.writeHead(200, { 'content-type': 'application/json' });
30
    resp.end(JSON.stringify(MyData));
31
});
32
// Create a webserver with a request listener callback.  This will write the response header with the content type as json, and end the response by sending the MyData array in JSON format.
33

34
server.listen(8080);
35
// Tells the webserver to listen on port 8080(obviously this may be whatever port you want.)
Things to be aware of in your app.js code
In lines 7 through 11, we define the function called 'MyCSV' and the field names.

If your CSV file has multiple columns make sure you define this correctly to match your file.

On line 17 we define the location of the CSV file of which we are loading.  Make sure you use the correct path here.

Uruchom aplikację i sprawdź działanie Otwórz konsolę i wpisz następujące polecenie:

Aplikacja węzła 1 Aplikacja węzła W konsoli powinny pojawić się następujące dane wyjściowe:

[  MYCSV { Fieldone: 'ABC', Fieldtwo: '123', Fieldthree: 'Fudge' },
   MYCSV { Fieldone: '532', Fieldtwo: 'CWE', Fieldthree: 'ICECREAM' },
   MYCSV { Fieldone: '8023', Fieldtwo: 'POOP', Fieldthree: 'DOGS' },
   MYCSV { Fieldone: '441', Fieldtwo: 'CHEESE', Fieldthree: 'CARMEL' },
   MYCSV { Fieldone: '221', Fieldtwo: 'ABC', Fieldthree: 'HOUSE' }, ]

1 [MYCSV {Fieldone: 'ABC', Fieldtwo: '123', Fieldthree: 'Fudge'}, 2 MYCSV {Fieldone: '532', Fieldtwo: 'CWE', Fieldthree: 'ICECREAM'}, 3 MYCSV {Fieldone: '8023', Fieldtwo: 'POOP', Fieldthree: 'DOGS'}, 4 MYCSV {Fieldone: '441', Fieldtwo: 'CHEESE', Fieldthree: 'CARMEL'}, 5 MYCSV {Fieldone: '221', Fieldtwo: 'ABC', Fieldthree: 'HOUSE'},] Teraz powinieneś otworzyć przeglądarkę internetową i przejść do swojego serwera. Powinieneś zobaczyć, jak wyprowadza dane w formacie JSON.

Zakończenie Korzystając z node.js i jego modułu CSV możemy szybko i łatwo odczytać i wykorzystać dane przechowywane na serwerze oraz udostępnić je klientowi na żądanie

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.