L’autore ha selezionato la Società degli Ingegneri Donne per ricevere una donazione come parte del programma Scrivi per le Donazioni.
Introduzione
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 questo tutorial, utilizzerai il modulo node-csv
per leggere un file CSV utilizzando gli stream di Node.js, che ti consente di leggere grandi set di dati senza consumare molta memoria. Modificherai il programma per spostare i dati analizzati dal file CSV in un database SQLite. Recupererai anche i dati dal database, li analizzerai con node-csv
, e utilizzerai gli stream di Node.js per scriverli in un file CSV a blocchi.
Effettua il deploy delle tue applicazioni Node da GitHub utilizzando la Piattaforma App di DigitalOcean. Lascia che DigitalOcean si concentri sulla scalabilità della tua app.
Prerequisiti
Per seguire questo tutorial, avrai bisogno di:
-
Node.js installato nel tuo ambiente locale o server. Segui Come Installare Node.js e Creare un Ambiente di Sviluppo Locale per installare Node.js.
-
SQLite installato sul tuo ambiente locale o server, che puoi installare seguendo il passaggio 1 in Come Installare e Utilizzare SQLite su Ubuntu 20.04. La conoscenza su come utilizzare SQLite è utile e può essere appresa nei passaggi da 2 a 7 della guida all’installazione.
-
Familiarità con la scrittura di un programma Node.js. Vedi Come Scrivere ed Eseguire il Tuo Primo Programma in Node.js.
-
Familiarità con gli stream Node.js. Vedi Come Lavorare con i File Utilizzando gli Stream in Node.js.
Passaggio 1 — Configurazione della directory del progetto
In questa sezione, creerai la directory del progetto e scaricherai i pacchetti per la tua applicazione. Scaricherai anche un set di dati CSV da Stats NZ, che contiene dati sulla migrazione internazionale in Nuova Zelanda.
Per iniziare, crea una directory chiamata csv_demo
e naviga nella directory:
Successivamente, inizializza la directory come un progetto npm utilizzando il comando npm init
:
L’opzione -y
notifica a npm init
di rispondere “sì” a tutte le richieste. Questo comando crea un package.json
con valori predefiniti che puoi modificare in qualsiasi momento.
Con la directory inizializzata come progetto npm, puoi ora installare le dipendenze necessarie: node-csv
e node-sqlite3
.
Inserisci il seguente comando per installare node-csv
:
Il modulo node-csv
è una raccolta di moduli che ti consente di analizzare e scrivere dati su un file CSV. Il comando installa tutti e quattro i moduli che fanno parte del pacchetto node-csv
: csv-generate
, csv-parse
, csv-stringify
e stream-transform
. Utilizzerai il modulo csv-parse
per analizzare un file CSV e il modulo csv-stringify
per scrivere dati su un file CSV.
Successivamente, installa il modulo node-sqlite3
:
Il modulo node-sqlite3
consente alla tua app di interagire con il database SQLite.
Dopo aver installato i pacchetti nel tuo progetto, scarica il file CSV sulle migrazioni in Nuova Zelanda con il comando wget
:
Il file CSV che hai scaricato ha un nome lungo. Per rendere più facile lavorarci, rinomina il file con un nome più corto usando il comando mv
:
Il nuovo nome del file CSV, migration_data.csv
, è più corto e più facile da gestire.
Utilizzando nano
, o il tuo editor di testo preferito, apri il file:
Una volta aperto, vedrai un contenuto simile a questo:
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
...
La prima riga contiene i nomi delle colonne e tutte le righe successive contengono i dati corrispondenti a ciascuna colonna. Una virgola separa ciascun dato. Questo carattere è conosciuto come delimitatore perché delimita i campi. Non sei limitato all’uso delle virgole. Altri delimitatori popolari includono i due punti (:
), i punti e virgola (;
) e le tabulazioni (\td
). Devi sapere quale delimitatore viene utilizzato nel file poiché la maggior parte dei moduli lo richiede per analizzare i file.
Dopo aver revisionato il file e identificato il delimitatore, esci dal tuo file migration_data.csv
utilizzando CTRL+X
.
Hai ora installato le dipendenze necessarie per il tuo progetto. Nella prossima sezione, leggerai un file CSV.
Passaggio 2 — Lettura dei File CSV
In questa sezione, utilizzerai node-csv
per leggere un file CSV e registrare il suo contenuto nella console. Utilizzerai il metodo createReadStream()
del modulo fs
per leggere i dati dal file CSV e creare uno stream leggibile. Successivamente, instraderai lo stream in un altro stream inizializzato con il modulo csv-parse
per analizzare i frammenti di dati. Una volta analizzati i frammenti di dati, puoi registrarli nella console.
Crea e apri un file readCSV.js
nel tuo editor preferito:
Nel tuo file readCSV.js
, importa i moduli fs
e csv-parse
aggiungendo le seguenti righe:
Nella prima riga, definisci la variabile fs
e assegnale l’oggetto fs
che il metodo require()
di Node.js restituisce quando importa il modulo.
Nella seconda riga, si estrae il metodo parse
dall’oggetto restituito dal metodo require()
nella variabile parse
utilizzando la sintassi di destrutturazione.
Aggiungi le seguenti righe per leggere il file CSV:
Il metodo createReadStream()
del modulo fs
accetta un argomento con il nome del file che si desidera leggere, che in questo caso è migration_data.csv
. Quindi, crea uno stream leggibile, che prende un file grande e lo suddivide in chunk più piccoli. Uno stream leggibile consente di leggere solo i dati da esso e non scriverci.
Dopo aver creato lo stream leggibile, il metodo pipe()
di Node inoltra i chunk di dati dallo stream leggibile a un altro stream. Il secondo stream viene creato quando il metodo parse()
del modulo csv-parse
viene invocato all’interno del metodo pipe()
. Il modulo csv-parse
implementa uno stream di trasformazione (uno stream leggibile e scrivibile), prendendo un chunk di dati e trasformandolo in un’altra forma. Ad esempio, quando riceve un chunk come 2001-01,2020-09,Emigrante a lungo termine,Arrivi,Femmina,0-4 anni,344
, il metodo parse()
lo trasformerà in un array.
Il metodo parse()
accetta un oggetto che accetta proprietà. L’oggetto quindi configura e fornisce ulteriori informazioni sui dati che il metodo analizzerà. L’oggetto accetta le seguenti proprietà:
-
delimiter
definisce il carattere che separa ogni campo nella riga. Il valore,
indica al parser che le virgole delimitano i campi. -
from_line
definisce la riga da cui il parser dovrebbe iniziare a analizzare le righe. Con il valore2
, il parser salterà la riga 1 e inizierà dalla riga 2. Poiché inserirai i dati nel database in seguito, questa proprietà ti aiuta a evitare di inserire i nomi delle colonne nella prima riga del database.
In seguito, si allega un evento di streaming utilizzando il metodo on()
di Node.js. Un evento di streaming consente al metodo di consumare un pezzo di dati se viene emesso un determinato evento. L’evento data
viene attivato quando i dati trasformati dal metodo parse()
sono pronti per essere consumati. Per accedere ai dati, si passa una callback al metodo on()
, che prende un parametro chiamato row
. Il parametro row
è un pezzo di dati trasformato in un array. All’interno della callback, si registrano i dati nella console utilizzando il metodo console.log()
.
Prima di eseguire il file, aggiungerai ulteriori eventi di flusso. Questi eventi di flusso gestiscono gli errori e scrivono un messaggio di successo sulla console quando tutti i dati nel file CSV sono stati consumati.
Ancora nel tuo file readCSV.js
, aggiungi il codice evidenziato:
L’evento end
viene emesso quando tutti i dati nel file CSV sono stati letti. Quando ciò accade, viene richiamata la callback e viene registrato un messaggio che indica che ha terminato.
Se si verifica un errore durante la lettura e il parsing dei dati CSV, viene emesso l’evento error
, che richiama la callback e registra il messaggio di errore sulla console.
Il tuo file completo dovrebbe ora assomigliare al seguente:
Salva ed esci dal tuo file readCSV.js
usando CTRL+X
.
Successivamente, esegui il file usando il comando node
:
L’output sarà simile a questo (modificato per brevità):
Output[
'2001-01',
'2020-09',
'Long-term migrant',
'Arrivals',
'Female',
'0-4 years',
'344',
'0',
'Final'
]
...
[
'2021-09',
...
'70',
'Provisional'
]
finished
Tutte le righe nel file CSV sono state trasformate in array usando il flusso di trasformazione csv-parse
. Poiché la registrazione avviene ogni volta che viene ricevuto un pezzo dal flusso, i dati sembrano essere scaricati anziché essere visualizzati tutti in una volta sola.
In questo passaggio, hai letto i dati in un file CSV e li hai trasformati in array. Successivamente, inserirai i dati da un file CSV nel database.
Passaggio 3 — Inserimento dei Dati nel Database
Insertare i dati da un file CSV nel database usando Node.js ti dà accesso a una vasta libreria di moduli che puoi utilizzare per elaborare, pulire o migliorare i dati prima di inserirli nel database.
In questa sezione, stabilirai una connessione con il database SQLite utilizzando il modulo node-sqlite3
. Successivamente, creerai una tabella nel database, copierai il file readCSV.js
e lo modificherai per inserire tutti i dati letti dal file CSV nel database.
Crea e apri un file db.js
nel tuo editor:
Nel tuo file db.js
, aggiungi le seguenti righe per importare i moduli fs
e node-sqlite3
:
Nella terza riga, definisci il percorso del database SQLite e lo memorizzi nella variabile filepath
. Il file del database non esiste ancora, ma sarà necessario per node-sqlite3
per stabilire una connessione con il database.
Nello stesso file, aggiungi le seguenti righe per connettere Node.js a un database SQLite:
Ecco, si definisce una funzione chiamata connectToDatabase()
per stabilire una connessione al database. All’interno della funzione, si invoca il metodo existsSync()
del modulo fs
in un’istruzione if
, che controlla se il file del database esiste nella directory del progetto. Se la condizione dell’if
si valuta a true
, si istanzia la classe Database()
di SQLite del modulo node-sqlite3
con il percorso del database. Una volta stabilita la connessione, la funzione restituisce l’oggetto di connessione ed esce.
Tuttavia, se l’istruzione if
si valuta a false
(se il file del database non esiste), l’esecuzione salterà al blocco else
. Nel blocco else
, si istanzia la classe Database()
con due argomenti: il percorso del file del database e un callback.
Il primo argomento è il percorso del file del database SQLite, che è ./population.db
. Il secondo argomento è un callback che verrà invocato automaticamente quando la connessione al database è stata stabilita con successo o se si è verificato un errore. Il callback prende come parametro un oggetto error
, che è null
se la connessione ha avuto successo. All’interno del callback, l’istruzione if
controlla se l’oggetto error
è impostato. Se si valuta a true
, il callback registra un messaggio di errore e ritorna. Se si valuta a false
, si registra un messaggio di successo confermando che la connessione è stata stabilita.
Attualmente, i blocchi if
e else
stabiliscono l’oggetto di connessione. Passi una callback quando si invoca la classe Database
nel blocco else
per creare una tabella nel database, ma solo se il file del database non esiste. Se il file del database esiste già, la funzione eseguirà il blocco if
, si connetterà al database e restituirà l’oggetto di connessione.
Per creare una tabella se il file del database non esiste, aggiungi il codice evidenziato:
Adesso la funzione connectToDatabase()
invoca la funzione createTable()
, che accetta l’oggetto di connessione memorizzato nella variabile db
come argomento.
All’esterno della funzione connectToDatabase()
, definisci la funzione createTable()
, che accetta l’oggetto di connessione db
come parametro. Invochi il metodo exec()
sull’oggetto di connessione db
che prende come argomento un’istruzione SQL. L’istruzione SQL crea una tabella chiamata migration
con 7 colonne. I nomi delle colonne corrispondono agli header nel file migration_data.csv
.
Infine, invochi la funzione connectToDatabase()
ed esporti l’oggetto di connessione restituito dalla funzione in modo che possa essere riutilizzato in altri file.
Salva ed esci dal tuo file db.js
.
Con la connessione al database stabilita, ora copierai e modificherai il file readCSV.js
per inserire le righe che il modulo csv-parse
ha analizzato nel database.
Copia e rinomina il file in insertData.js
con il seguente comando:
Apri il file insertData.js
nel tuo editor:
Aggiungi il codice evidenziato:
Nella terza riga, importi l’oggetto di connessione dal file db.js
e lo salvi nella variabile db
.
All’interno del callback dell’evento data
collegato allo stream del modulo fs
, invochi il metodo serialize()
sull’oggetto di connessione. Il metodo assicura che una dichiarazione SQL termini l’esecuzione prima che un’altra inizi, il che può aiutare a prevenire le condizioni di gara del database in cui il sistema esegue operazioni concorrenti contemporaneamente.
Il metodo serialize()
richiede un callback. All’interno del callback, invochi il metodo run
sull’oggetto di connessione db
. Il metodo accetta tre argomenti:
-
Il primo argomento è una dichiarazione SQL che verrà passata ed eseguita nel database SQLite. Il metodo
run()
accetta solo dichiarazioni SQL che non restituiscono risultati. La dichiarazioneINSERT INTO migration VALUES (?, ..., ?)
inserisce una riga nella tabellamigration
, e i?
sono segnaposto che vengono successivamente sostituiti con i valori nel secondo argomento del metodorun()
. -
Il secondo argomento è un array
[riga[0], ... riga[5], riga[6]]
. Nella sezione precedente, il metodoparse()
riceve un blocco di dati dal flusso leggibile e lo trasforma in un array. Poiché i dati vengono ricevuti come un array, per ottenere il valore di ciascun campo, è necessario utilizzare gli indici dell’array per accedervi come[riga[1], ..., riga[6]]
, ecc. -
Il terzo argomento è una callback che viene eseguita quando i dati sono stati inseriti o se si è verificato un errore. La callback verifica se si è verificato un errore e registra il messaggio di errore. Se non ci sono errori, la funzione registra un messaggio di successo nella console utilizzando il metodo
console.log()
, informandoti che è stata inserita una riga insieme all’id.
Infine, rimuovi gli eventi end
e error
dal tuo file. A causa della natura asincrona dei metodi di node-sqlite3
, gli eventi end
e error
vengono eseguiti prima che i dati vengano inseriti nel database, quindi non sono più necessari.
Salva ed esci dal tuo file.
Esegui il file insertData.js
utilizzando node
:
A seconda del tuo sistema, potrebbe richiedere del tempo, ma node
dovrebbe restituire l’output qui sotto:
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
Il messaggio, specialmente gli ID, dimostra che la riga dal file CSV è stata salvata nel database.
Ora puoi leggere un file CSV e inserire il suo contenuto nel database. Successivamente, scriverai un file CSV.
Passaggio 4 — Scrittura di File CSV
In questa sezione, recupererai i dati dal database e li scriverai in un file CSV utilizzando gli stream.
Crea e apri writeCSV.js
nel tuo editor:
Nel tuo file writeCSV.js
, aggiungi le seguenti righe per importare i moduli fs
e csv-stringify
e l’oggetto di connessione al database da db.js
:
Il modulo csv-stringify
trasforma i dati da un oggetto o un array in un formato testuale CSV.
Successivo, aggiungi le seguenti linee per definire una variabile che contiene il nome del file CSV in cui desideri scrivere i dati e uno stream scrivibile su cui scriverai i dati:
Il metodo createWriteStream
richiede un argomento del nome del file in cui desideri scrivere il tuo flusso di dati, che è il nome del file saved_from_db.csv
memorizzato nella variabile filename
.
Nella quarta riga, definisci una variabile columns
, che memorizza un array contenente i nomi degli header per i dati CSV. Questi header saranno scritti nella prima riga del file CSV quando inizierai a scrivere i dati nel file.
Ancora nel tuo file writeCSV.js
, aggiungi le seguenti linee per recuperare i dati dal database e scrivere ogni riga nel file CSV:
Prima, invochi il metodo stringify
con un oggetto come argomento, che crea uno stream di trasformazione. Lo stream di trasformazione converte i dati da un oggetto in testo CSV. L’oggetto passato al metodo stringify()
ha due proprietà:
header
accetta un valore booleano e genera un’intestazione se il valore booleano è impostato sutrue
.columns
prende un array contenente i nomi delle colonne che saranno scritti nella prima riga del file CSV se l’opzioneheader
è impostata sutrue
.
Successivo, si invoca il metodo each()
dall’oggetto di connessione db
con due argomenti. Il primo argomento è l’istruzione SQL select * from migration
che recupera le righe una per una nel database. Il secondo argomento è un callback invocato ogni volta che viene recuperata una riga dal database. Il callback prende due parametri: un oggetto error
e un oggetto row
contenente i dati recuperati da una singola riga nel database. All’interno del callback, si controlla se l’oggetto error
è impostato nell’istruzione if
. Se la condizione viene valutata come true
, viene registrato un messaggio di errore nella console utilizzando il metodo console.log()
. Se non ci sono errori, si invoca il metodo write()
su stringifier
, che scrive i dati nel flusso di trasformazione stringifier
.
Quando il metodo each()
finisce di iterare, il metodo pipe()
sul flusso stringifier
inizia a inviare dati a pezzi e a scriverli nel writableStream
. Il flusso scrivibile salverà ogni pezzo di dati nel file saved_from_db.csv
. Una volta che tutti i dati sono stati scritti nel file, console.log()
registrerà un messaggio di successo.
Il file completo avrà ora il seguente aspetto:
Salvare e chiudere il file, quindi eseguire il file writeCSV.js
nel terminale:
Riceverai il seguente output:
OutputFinished writing data
Per confermare che i dati siano stati scritti, ispeziona i contenuti nel file utilizzando il comando cat
:
Il comando cat
restituirà tutte le righe scritte nel file (modificate per brevità):
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,
...
Ora puoi recuperare i dati dal database e scrivere ogni riga in un file CSV utilizzando gli stream.
Conclusione
In questo articolo, hai letto un file CSV e inserito i suoi dati in un database utilizzando i moduli node-csv
e node-sqlite3
. Successivamente hai recuperato i dati dal database e li hai scritti in un altro file CSV.
Ora puoi leggere e scrivere file CSV. Come prossimo passo, puoi ora lavorare con grandi set di dati CSV utilizzando la stessa implementazione con stream efficienti in memoria, oppure potresti esaminare un pacchetto come event-stream
che rende molto più semplice lavorare con gli stream.
Per esplorare di più su node-csv
, visita la loro documentazione Progetto CSV – Pacchetto Node.js CSV. Per saperne di più su node-sqlite3
, visita la loro documentazione su Github. Per continuare a sviluppare le tue competenze in Node.js, consulta la serie How To Code in Node.js.