De auteur heeft Society of Women Engineers geselecteerd om een donatie te ontvangen als onderdeel van het Write for DOnations-programma.
Introductie
A CSV is a plain text file format for storing tabular data. The CSV file uses a comma delimiter to separate values in table cells, and a new line delineates where rows begin and end. Most spreadsheet programs and databases can export and import CSV files. Because CSV is a plain-text file, any programming language can parse and write to a CSV file. Node.js has many modules that can work with CSV files, such as node-csv
, fast-csv
, and papaparse
.
In deze tutorial zul je de node-csv
-module gebruiken om een CSV-bestand te lezen met behulp van Node.js streams, waarmee je grote datasets kunt lezen zonder veel geheugen te verbruiken. Je zult het programma aanpassen om gegevens die zijn geparseerd uit het CSV-bestand in een SQLite-database te plaatsen. Je zult ook gegevens uit de database ophalen, deze parsen met node-csv
, en Node.js streams gebruiken om ze in blokken naar een CSV-bestand te schrijven.
Deploy je Node-applicaties vanuit GitHub met behulp van DigitalOcean App Platform. Laat DigitalOcean zich richten op het schalen van je app.
Vereisten
Om deze tutorial te volgen, heb je nodig:
-
Node.js geïnstalleerd op je lokale of serveromgeving. Volg How to Install Node.js and Create a Local Development Environment om Node.js te installeren.
-
SQLite geïnstalleerd op uw lokale of serveromgeving, dat u kunt installeren door stap 1 te volgen in Hoe SQLite te installeren en gebruiken op Ubuntu 20.04. Kennis over hoe SQLite te gebruiken is nuttig en kan worden geleerd in stappen 2-7 van de installatiehandleiding.
-
Bekendheid met het schrijven van een Node.js programma. Zie Hoe uw eerste programma te schrijven en uit te voeren in Node.js.
-
Bekendheid met Node.js streams. Zie Hoe bestanden te verwerken met streams in Node.js.
Stap 1 — Het Opzetten van de Projectmap
In deze sectie zal je de projectmap creëren en pakketten downloaden voor jouw applicatie. Je zal ook een CSV dataset downloaden van Stats NZ, welke internationale migratiegegevens in Nieuw-Zeeland bevat.
Om te beginnen, maak een map genaamd csv_demo
en navigeer naar de map:
Vervolgens, initialiseer de map als een npm project gebruikmakend van het npm init
commando:
De -y
optie meldt npm init
om “ja” te zeggen tegen alle prompts. Dit commando creëert een package.json
met standaardwaarden die je op elk moment kan veranderen.
Met de map geïnitialiseerd als een npm project, kan je nu de nodige afhankelijkheden installeren: node-csv
en node-sqlite3
.
Voer het volgende commando in om node-csv
te installeren:
De module node-csv
is een verzameling van modules die je in staat stelt om gegevens te parsen en te schrijven naar een CSV-bestand. Het commando installeert alle vier modules die deel uitmaken van het node-csv
-pakket: csv-generate
, csv-parse
, csv-stringify
en stream-transform
. Je zult de module csv-parse
gebruiken om een CSV-bestand te parsen en de module csv-stringify
om gegevens naar een CSV-bestand te schrijven.
Installeer vervolgens de module node-sqlite3
:
De module node-sqlite3
maakt het mogelijk voor je app om te communiceren met de SQLite-database.
Na het installeren van de pakketten in je project, download het CSV-bestand voor de migratie naar Nieuw-Zeeland met het wget
-commando:
Het gedownloade CSV-bestand heeft een lange naam. Om er gemakkelijker mee te werken, hernoem je de bestandsnaam naar een kortere naam met het mv
-commando:
De nieuwe CSV-bestandsnaam, migration_data.csv
, is korter en gemakkelijker om mee te werken.
Open het bestand met nano
, of je favoriete teksteditor:
Zodra het bestand geopend is, zie je inhoud vergelijkbaar met dit:
year_month,month_of_release,passenger_type,direction,sex,age,estimate,standard_error,status
2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,344,0,Final
2001-01,2020-09,Long-term migrant,Arrivals,Male,0-4 years,341,0,Final
...
De eerste regel bevat de kolomnamen, en alle volgende regels bevatten de gegevens die overeenkomen met elke kolom. Een komma scheidt elk stukje gegevens. Dit karakter staat bekend als een delimiter omdat het de velden aangeeft. Je bent niet beperkt tot het gebruik van komma’s. Andere populaire delimiters zijn dubbelepunten (:
), puntkomma’s (;
) en tabs (\t
). Je moet weten welke delimiter wordt gebruikt in het bestand, aangezien de meeste modules dit vereisen om de bestanden te parsen.
Na het bekijken van het bestand en het identificeren van de scheidingsteken, verlaat je je migratie_data.csv
bestand met behulp van CTRL+X
.
Je hebt nu de benodigde afhankelijkheden geïnstalleerd voor je project. In de volgende sectie zal je een CSV-bestand lezen.
Stap 2 — CSV-bestanden lezen
In deze sectie zal je node-csv
gebruiken om een CSV-bestand te lezen en de inhoud ervan in de console te loggen. Je zal de createReadStream()
-methode van de fs
-module gebruiken om de data uit het CSV-bestand te lezen en een leesbare stream te creëren. Vervolgens zal je de stream doorsturen naar een andere stream geïnitialiseerd met de csv-parse
-module om de brokken data te parseren. Zodra de brokken data zijn geparseerd, kan je ze in de console loggen.
Maak een readCSV.js
bestand aan in je favoriete editor:
In je readCSV.js
bestand, importeer de fs
en csv-parse
modules door de volgende regels toe te voegen:
In de eerste regel definieer je de variabele fs
en wijs je deze toe aan het fs
-object dat de Node.js require()
-methode retourneert bij het importeren van de module.
In de tweede regel haal je de parse
methode uit het object dat wordt geretourneerd door de require()
methode en wijs je deze toe aan de parse
variabele met behulp van de destructuring syntax.
Voeg de volgende regels toe om het CSV-bestand te lezen:
De createReadStream()
methode van de fs
module accepteert een argument van de bestandsnaam die je wilt lezen, in dit geval migration_data.csv
. Vervolgens creëert het een leesbare stream, die een groot bestand neemt en het opbreekt in kleinere brokken. Een leesbare stream stelt je in staat om alleen gegevens ervan te lezen en er niet naar te schrijven.
Nadat de leesbare stream is gecreëerd, stuurt de pipe()
methode van Node brokken gegevens door van de leesbare stream naar een andere stream. De tweede stream wordt gemaakt wanneer de parse()
methode van de csv-parse
module wordt aangeroepen binnen de pipe()
methode. De csv-parse
module implementeert een transform stream (een leesbare en beschrijfbare stream), die een gegevensbrok neemt en het omvormt tot een andere vorm. Bijvoorbeeld, wanneer het een brok ontvangt zoals 2001-01,2020-09,Langdurige migrant,Aankomsten,Vrouw,0-4 jaar,344
, zal de parse()
methode het omvormen tot een array.
De parse()
methode neemt een object aan dat eigenschappen accepteert. Het object configureert en biedt vervolgens meer informatie over de gegevens die de methode zal analyseren. Het object neemt de volgende eigenschappen aan:
-
delimiter
definieert het teken dat elk veld in de rij scheidt. De waarde,
vertelt de parser dat komma’s de velden afbakenen. -
from_line
definieert de regel waar de parser moet beginnen met het parseren van de rijen. Met de waarde2
zal de parser regel 1 overslaan en beginnen bij regel 2. Omdat je de gegevens later in de database zal invoegen, helpt deze eigenschap je om te voorkomen dat de kolomnamen in de eerste rij van de database worden ingevoegd.
Vervolgens hecht je een streaminggebeurtenis aan met behulp van de Node.js on()
-methode. Een streaminggebeurtenis stelt de methode in staat om een deel van de gegevens te consumeren als een bepaalde gebeurtenis wordt afgevuurd. Het data
-gebeurtenis wordt geactiveerd wanneer gegevens die zijn getransformeerd door de parse()
-methode gereed zijn om te worden geconsumeerd. Om toegang te krijgen tot de gegevens, geef je een callback door aan de on()
-methode, die een parameter met de naam row
accepteert. De row
-parameter is een gegevensfragment dat is omgezet in een array. Binnen de callback log je de gegevens in de console met behulp van de console.log()
-methode.
Voordat je het bestand uitvoert, voeg je meer streamgebeurtenissen toe. Deze streamgebeurtenissen behandelen fouten en schrijven een succesbericht naar de console wanneer alle gegevens in het CSV-bestand zijn verwerkt.
Nog steeds in je readCSV.js
-bestand, voeg het gemarkeerde code toe:
De end
-gebeurtenis wordt uitgezonden wanneer alle gegevens in het CSV-bestand zijn gelezen. Wanneer dat gebeurt, wordt de callback uitgevoerd en wordt een bericht gelogd dat aangeeft dat het is voltooid.
Als ergens tijdens het lezen en parseren van de CSV-gegevens een fout optreedt, wordt de error
-gebeurtenis uitgezonden, die de callback oproept en het foutbericht in de console logt.
Je volledige bestand zou er nu als volgt uit moeten zien:
Sla op en verlaat je readCSV.js
-bestand met CTRL+X
.
Vervolgens voer je het bestand uit met het node
-commando:
De output ziet er ongeveer als volgt uit (aangepast voor beknoptheid):
Output[
'2001-01',
'2020-09',
'Long-term migrant',
'Arrivals',
'Female',
'0-4 years',
'344',
'0',
'Final'
]
...
[
'2021-09',
...
'70',
'Provisional'
]
finished
Alle rijen in het CSV-bestand zijn omgezet in arrays met behulp van de csv-parse
-transformatiestream. Omdat er telkens een logboek wordt gemaakt wanneer een stukje van de stream wordt ontvangen, lijkt het alsof de gegevens worden gedownload in plaats van allemaal tegelijkertijd worden weergegeven.
In deze stap lees je gegevens in een CSV-bestand en zet je deze om in arrays. Vervolgens voeg je gegevens uit een CSV-bestand in de database in.
Stap 3 — Gegevens invoegen in de Database
Het invoegen van gegevens vanuit een CSV-bestand in de database met behulp van Node.js geeft je toegang tot een uitgebreide bibliotheek van modules die je kunt gebruiken om gegevens te verwerken, schoon te maken of te verbeteren voordat je ze in de database invoegt.
In dit gedeelte zul je een verbinding met de SQLite-database tot stand brengen met behulp van de node-sqlite3
-module. Vervolgens zul je een tabel in de database maken, het readCSV.js
-bestand kopiëren en aanpassen om alle gegevens uit het CSV-bestand in de database in te voegen.
Maak een db.js
-bestand aan in je editor:
Voeg in je db.js
-bestand de volgende regels toe om de fs
– en node-sqlite3
-modules te importeren:
In de derde regel definieer je het pad van de SQLite-database en sla je dit op in de variabele filepath
. Het databasebestand bestaat nog niet, maar het zal nodig zijn voor node-sqlite3
om een verbinding met de database tot stand te brengen.
Voeg in hetzelfde bestand de volgende regels toe om Node.js met een SQLite-database te verbinden:
Hier definieer je een functie genaamd connectToDatabase()
om een verbinding met de database tot stand te brengen. Binnen de functie roep je de existsSync()
methode van de fs
module aan in een if
statement, die controleert of het databasebestand bestaat in de projectdirectory. Als de if
voorwaarde evalueert naar true
, instantieer je de Database()
klasse van SQLite’s node-sqlite3
module met het databasebestandpad. Zodra de verbinding tot stand is gebracht, retourneert de functie het verbindingsobject en verlaat deze.
Echter, als de if
statement evalueert naar false
(als het databasebestand niet bestaat), zal de uitvoering overschakelen naar het else
blok. In het else
blok instantieer je de Database()
klasse met twee argumenten: het databasebestandpad en een callback.
Het eerste argument is het pad van het SQLite databasebestand, dat ./population.db
is. Het tweede argument is een callback die automatisch wordt aangeroepen wanneer de verbinding met de database succesvol tot stand is gebracht of als er een fout is opgetreden. De callback neemt een error
object als parameter, dat null
is als de verbinding succesvol is. Binnen de callback controleert de if
statement of het error
object is ingesteld. Als het evalueert naar true
, logt de callback een foutmelding en retourneert. Als het evalueert naar false
, log je een succesmelding waarin wordt bevestigd dat de verbinding tot stand is gebracht.
Momenteel stellen de if
en else
blokken de verbindingsobject in. U geeft een terugroepfunctie door bij het aanroepen van de Database
klasse in het else
blok om een tabel in de database te maken, maar alleen als het databasebestand niet bestaat. Als het databasebestand al bestaat, zal de functie het if
blok uitvoeren, verbinding maken met de database, en het verbindingsobject retourneren.
Om een tabel te maken als het databasebestand niet bestaat, voegt u de gemarkeerde code toe:
Nu roept de connectToDatabase()
de createTable()
functie aan, die het verbindingsobject dat is opgeslagen in de db
variabele accepteert als argument.
Buiten de connectToDatabase()
functie definieert u de createTable()
functie, die het verbindingsobject db
als parameter accepteert. U roept de exec()
methode aan op het db
verbindingsobject dat een SQL-opdracht als argument neemt. De SQL-opdracht maakt een tabel met de naam migration
met 7 kolommen. De kolomnamen komen overeen met de koppen in het migration_data.csv
bestand.
Tenslotte roept u de connectToDatabase()
functie aan en exporteert u het verbindingsobject dat door de functie wordt geretourneerd zodat het kan worden hergebruikt in andere bestanden.
Sla uw db.js
bestand op en sluit het.
Met de databaseverbinding tot stand gebracht, zult u nu het readCSV.js
bestand kopiëren en aanpassen om de rijen die zijn geparseerd door de csv-parse
module in de database in te voegen.
Kopieer en hernoem het bestand naar insertData.js
met het volgende commando:
Open het bestand insertData.js
in uw editor:
Voeg de gemarkeerde code toe:
In de derde regel importeert u het verbindingsobject vanuit het bestand db.js
en slaat u het op in de variabele db
.
Binnen de data
-gebeurtenis-callback die is gekoppeld aan de fs
-modulestream, roept u de methode serialize()
aan op het verbindingsobject. De methode zorgt ervoor dat een SQL-instructie volledig wordt uitgevoerd voordat een andere begint met uitvoeren, wat kan helpen bij het voorkomen van racecondities in de database waarbij het systeem tegelijkertijd concurrerende bewerkingen uitvoert.
De methode serialize()
neemt een callback aan. Binnen de callback roept u de methode run
aan op het db
-verbindingsobject. De methode accepteert drie argumenten:
-
Het eerste argument is een SQL-instructie die zal worden doorgegeven en uitgevoerd in de SQLite-database. De
run()
-methode accepteert alleen SQL-instructies die geen resultaten retourneren. DeINSERT INTO migration VALUES (?, ..., ?
-instructie voegt een rij in de tabelmigration
in, en de?
zijn aanduidingen die later worden vervangen door de waarden in het tweede argument van derun()
-methode. -
Het tweede argument is een array
[rij[0], ... rij[5], rij[6]]
. In de vorige sectie ontvangt deparse()
-methode een blok gegevens van de leesbare stream en transformeert het in een array. Aangezien de gegevens als een array worden ontvangen, moet je voor elk veldwaarde array-indexen gebruiken om ze te benaderen zoals[rij[1], ..., rij[6]]
, enz. -
Het derde argument is een callback die wordt uitgevoerd wanneer de gegevens zijn ingevoegd of als er een fout is opgetreden. De callback controleert of er een fout is opgetreden en logt het foutbericht. Als er geen fouten zijn, logt de functie een succesbericht in de console met behulp van de
console.log()
-methode, waardoor je weet dat er een rij is ingevoegd samen met de id.
Eindelijk, verwijder de einde
en fout
gebeurtenissen uit je bestand. Vanwege de asynchrone aard van de methoden van node-sqlite3
, worden de einde
en fout
gebeurtenissen uitgevoerd voordat de gegevens in de database worden ingevoegd, dus ze zijn niet langer nodig.
Sla je bestand op en sluit af.
Voer het bestand insertData.js
uit met node
:
Afhankelijk van je systeem kan het even duren, maar node
zou de onderstaande uitvoer moeten retourneren:
OutputConnected to the database successfully
Inserted a row with the id: 1
Inserted a row with the id: 2
...
Inserted a row with the id: 44308
Inserted a row with the id: 44309
Inserted a row with the id: 44310
Het bericht, vooral de id’s, bewijst dat de rij uit het CSV-bestand is opgeslagen in de database.
Je kunt nu een CSV-bestand lezen en de inhoud ervan in de database invoegen. Vervolgens ga je een CSV-bestand schrijven.
Stap 4 — CSV-bestanden schrijven
In dit gedeelte haal je gegevens op uit de database en schrijf je deze naar een CSV-bestand met behulp van streams.
Maak en open writeCSV.js
in je editor:
Voeg in je writeCSV.js
bestand de volgende regels toe om de fs
en csv-stringify
modules en het database verbindingsobject van db.js
te importeren:
De csv-stringify
module transformeert gegevens van een object of array naar een CSV-tekstformaat.
Voeg vervolgens de volgende regels toe om een variabele te definiëren die de naam van het CSV-bestand bevat waarnaar je gegevens wilt schrijven en een beschrijfbare stream waarnaar je gegevens zult schrijven:
De methode createWriteStream
neemt een argument van de bestandsnaam waarnaar je je gegevensstroom wilt schrijven, dat is de bestandsnaam saved_from_db.csv
opgeslagen in de variabele filename
.
In de vierde regel definieer je een variabele columns
, die een array bevat met de namen van de headers voor de CSV-gegevens. Deze headers worden geschreven in de eerste regel van het CSV-bestand wanneer je begint met het schrijven van de gegevens naar het bestand.
Nog steeds in je bestand writeCSV.js
, voeg de volgende regels toe om gegevens uit de database op te halen en elke rij in het CSV-bestand te schrijven:
Eerst roep je de methode stringify
aan met een object als argument, dat een transform stream creëert. De transform stream zet de data om van een object naar CSV-tekst. Het object dat wordt doorgegeven aan de methode stringify()
heeft twee eigenschappen:
header
accepteert een booleaanse waarde en genereert een header als de booleaanse waarde is ingesteld optrue
.columns
neemt een array aan met de namen van de kolommen die in de eerste regel van het CSV-bestand zullen worden geschreven als de optieheader
is ingesteld optrue
.
Vervolgens roept u de each()
-methode aan vanuit het db
-verbindingsobject met twee argumenten. Het eerste argument is de SQL-instructie select * from migration
die de rijen één voor één uit de database haalt. Het tweede argument is een callback die telkens wordt aangeroepen wanneer een rij uit de database wordt opgehaald. De callback heeft twee parameters: een error
-object en een row
-object met gegevens die uit een enkele rij in de database zijn opgehaald. Binnen de callback controleert u of het error
-object is ingesteld in de if
-verklaring. Als de voorwaarde evalueert naar true
, wordt een foutmelding gelogd in de console met behulp van de console.log()
-methode. Als er geen fout is, roept u de write()
-methode aan op stringifier
, die de gegevens in de stringifier
-transformatiestroom schrijft.
Wanneer de each()
-methode klaar is met itereren, begint de pipe()
-methode op de stringifier
-stroom gegevens in brokken te verzenden en deze in de writableStream
te schrijven. De schrijfbare stroom zal elk gegevensbrok in het bestand saved_from_db.csv
opslaan. Zodra alle gegevens naar het bestand zijn geschreven, zal console.log()
een succesbericht registreren.
Het volledige bestand ziet er nu als volgt uit:
Sla uw bestand op en sluit het, en voer vervolgens het bestand writeCSV.js
uit in de terminal:
U ontvangt de volgende uitvoer:
OutputFinished writing data
Om te bevestigen dat de gegevens zijn geschreven, inspecteert u de inhoud van het bestand met behulp van het cat
-commando:
cat
zal alle rijen teruggeven die in het bestand zijn geschreven (aangepast voor beknopte weergave):
Outputyear_month,month_of_release,passenger_type,direction,sex,age,estimate
2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,344
2001-01,2020-09,Long-term migrant,Arrivals,Male,0-4 years,341
2001-01,2020-09,Long-term migrant,Arrivals,Female,10-14 years,
...
Je kunt nu gegevens uit de database ophalen en elke rij in een CSV-bestand schrijven met behulp van streams.
Conclusie
In dit artikel heb je een CSV-bestand gelezen en de gegevens ervan ingevoegd in een database met behulp van de modules node-csv
en node-sqlite3
. Vervolgens heb je gegevens uit de database opgehaald en deze naar een ander CSV-bestand geschreven.
Je kunt nu CSV-bestanden lezen en schrijven. Als volgende stap kun je nu werken met grote CSV-datasets met dezelfde implementatie met geheugenefficiënte streams, of je kunt kijken naar een pakket zoals event-stream
dat werken met streams veel gemakkelijker maakt.
Om meer te ontdekken over node-csv
, bezoek hun documentatie CSV Project – Node.js CSV-pakket. Om meer te leren over node-sqlite3
, bezoek hun Github-documentatie. Om je Node.js-vaardigheden verder te ontwikkelen, bekijk de How To Code in Node.js-serie.