Cómo leer y escribir archivos CSV en Node.js usando Node-CSV

La autora seleccionó a la Sociedad de Mujeres Ingenieras para recibir una donación como parte del programa Write for DOnations .
Introducción
Un CSV es un formato de archivo de texto sin formato para almacenar datos tabulares. El archivo CSV utiliza un delimitador de coma para separar los valores en las celdas de la tabla, y una nueva línea delinea dónde comienzan y terminan las filas. La mayoría de los programas de hojas de cálculo y bases de datos pueden exportar e importar archivos CSV. Debido a que CSV es un archivo de texto sin formato, cualquier lenguaje de programación puede analizar y escribir en un archivo CSV. Node.js tiene muchos módulos que pueden trabajar con archivos CSV, como node-csv
, fast-csv
y papaparse
.
En este tutorial, utilizará el node-csv
módulo para leer un archivo CSV mediante secuencias de Node.js, lo que le permite leer grandes conjuntos de datos sin consumir mucha memoria. Modificará el programa para mover los datos analizados del archivo CSV a una base de datos SQLite. También recuperará datos de la base de datos, los analizará con node-csv
y utilizará secuencias de Node.js para escribirlos en un archivo CSV en fragmentos.
Implemente sus aplicaciones Node desde GitHub con la plataforma de aplicaciones DigitalOcean . Deje que DigitalOcean se concentre en escalar su aplicación.
Prerrequisitos
Para seguir este tutorial, necesitarás:
-
Node.js instalado en su entorno local o de servidor. Siga Cómo instalar Node.js y crear un entorno de desarrollo local para instalar Node.js.
-
SQLite instalado en su entorno local o de servidor, que puede instalar siguiendo el paso 1 de Cómo instalar y usar SQLite en Ubuntu 20.04 . Es útil saber cómo usar SQLite y se puede aprender en los pasos 2 a 7 de la guía de instalación.
-
Familiaridad con la escritura de un programa Node.js. Consulte Cómo escribir y ejecutar su primer programa en Node.js.
-
Familiaridad con los flujos de Node.js. Consulte Cómo trabajar con archivos mediante flujos en Node.js.
Paso 1: Configuración del directorio del proyecto
En esta sección, creará el directorio del proyecto y descargará paquetes para su aplicación. También descargará un conjunto de datos CSV de Stats NZ , que contiene datos de migración internacional en Nueva Zelanda.
Para comenzar, cree un directorio llamado csv_demo
y navegue hasta el directorio:
- mkdir csv_demo
- cd csv_demo
A continuación, inicialice el directorio como un proyecto npm usando el npm init
comando:
- npm init -y
La -y
opción indica npm init
que se debe responder “sí” a todas las solicitudes. Este comando crea un archivo package.json
con valores predeterminados que puede cambiar en cualquier momento.
Con el directorio inicializado como un proyecto npm, ahora puedes instalar las dependencias necesarias: node-csv
y node-sqlite3
.
Introduzca el siguiente comando para instalar node-csv
:
- npm install csv
El node-csv
módulo es una colección de módulos que le permite analizar y escribir datos en un archivo CSV. El comando instala los cuatro módulos que forman parte del node-csv
paquete: csv-generate
, csv-parse
, csv-stringify
y stream-transform
. Utilizará el csv-parse
módulo para analizar un archivo CSV y el csv-stringify
módulo para escribir datos en un archivo CSV.
A continuación, instale el node-sqlite3
módulo:
- npm install sqlite3
El node-sqlite3
módulo permite que su aplicación interactúe con la base de datos SQLite.
Después de instalar los paquetes en su proyecto, descargue el archivo CSV de migración de Nueva Zelanda con el wget
comando:
- wget https://www.stats.govt.nz/assets/Uploads/International-migration/International-migration-September-2021-Infoshare-tables/Download-data/international-migration-September-2021-estimated-migration-by-age-and-sex-csv.csv
El archivo CSV que descargaste tiene un nombre largo. Para que sea más fácil trabajar con él, cambia el nombre del archivo por uno más corto usando el mv
comando:
- mv international-migration-September-2021-estimated-migration-by-age-and-sex-csv.csv migration_data.csv
El nuevo nombre de archivo CSV, migration_data.csv
, es más corto y más fácil de trabajar.
Usando nano
, o su editor de texto favorito, abra el archivo:
- nano migration_data.csv
Una vez abierto, verás un contenido similar a este:
demo_csv/datos_migracion.csv
year_month,month_of_release,passenger_type,direction,sex,age,estimate,standard_error,status2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,344,0,Final2001-01,2020-09,Long-term migrant,Arrivals,Male,0-4 years,341,0,Final...
La primera línea contiene los nombres de las columnas y todas las líneas subsiguientes contienen los datos correspondientes a cada columna. Una coma separa cada dato. Este carácter se conoce como delimitador porque delimita los campos. No está limitado al uso de comas. Otros delimitadores populares incluyen dos puntos( :
), punto y coma( ;
) y tabulaciones( td
). Debe saber qué delimitador se utiliza en el archivo, ya que la mayoría de los módulos lo requieren para analizar los archivos.
Después de revisar el archivo e identificar el delimitador, salga del migration_data.csv
archivo usando CTRL+X
.
Ya ha instalado las dependencias necesarias para su proyecto. En la siguiente sección, leerá un archivo CSV.
Paso 2: Lectura de archivos CSV
En esta sección, utilizará node-csv
el método fs
del módulo createReadStream()
para leer los datos del archivo CSV y crear una secuencia legible. Luego, conectará la secuencia a otra secuencia inicializada con el csv-parse
módulo para analizar los fragmentos de datos. Una vez que se hayan analizado los fragmentos de datos, podrá registrarlos en la consola.
Crea y abre un readCSV.js
archivo en tu editor preferido:
- nano readCSV.js
En su readCSV.js
archivo, importe los módulos fs
y csv-parse
agregando las siguientes líneas:
demo_csv/readCSV.js
const fs = require("fs");const { parse } = require("csv-parse");
En la primera línea, define la fs
variable y le asigna el fs
objeto que el require()
método Node.js devuelve cuando importa el módulo.
En la segunda línea, extrae el parse
método del objeto devuelto por el require()
método en la parse
variable utilizando la sintaxis de desestructuración .
Agregue las siguientes líneas para leer el archivo CSV:
demo_csv/readCSV.js
...fs.createReadStream("./migration_data.csv") .pipe(parse({ delimiter: ",", from_line: 2 })) .on("data", function (row) { console.log(row); })
El createReadStream()
método del fs
módulo acepta un argumento del nombre del archivo que desea leer, que se encuentra migration_data.csv
aquí. Luego, crea un flujo legible , que toma un archivo grande y lo divide en fragmentos más pequeños. Un flujo legible le permite solo leer datos de él y no escribir en él.
Después de crear el flujo legible, pipe()
el método de Node reenvía fragmentos de datos del flujo legible a otro flujo. El segundo flujo se crea cuando se invoca el método csv-parse
del módulo dentro del método. El módulo implementa un flujo de transformación (un flujo legible y escribible), que toma un fragmento de datos y lo transforma en otro formato. Por ejemplo, cuando recibe un fragmento como , el método lo transformará en una matriz.parse()
pipe()
csv-parse
2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,344
parse()
El parse()
método toma un objeto que acepta propiedades. Luego, el objeto configura y proporciona más información sobre los datos que analizará el método. El objeto toma las siguientes propiedades:
-
delimiter
define el carácter que separa cada campo de la fila. El valor,
indica al analizador que las comas delimitan los campos. -
from_line
define la línea en la que el analizador debe comenzar a analizar las filas. Con el valor2
, el analizador omitirá la línea 1 y comenzará en la línea 2. Debido a que insertará los datos en la base de datos más adelante, esta propiedad lo ayuda a evitar insertar los nombres de las columnas en la primera fila de la base de datos.
A continuación, adjunta un evento de transmisión mediante el on()
método Node.js. Un evento de transmisión permite que el método consuma un fragmento de datos si se emite un determinado evento. El data
evento se activa cuando los datos transformados desde el parse()
método están listos para ser consumidos. Para acceder a los datos, pasa una devolución de llamada al on()
método, que toma un parámetro llamado row
. El row
parámetro es un fragmento de datos transformado en una matriz. Dentro de la devolución de llamada, registra los datos en la consola mediante el console.log()
método.
Antes de ejecutar el archivo, deberá agregar más eventos de transmisión. Estos eventos de transmisión manejan errores y escriben un mensaje de éxito en la consola cuando se han consumido todos los datos del archivo CSV.
Aún en su readCSV.js
archivo, agregue el código resaltado:
demo_csv/readCSV.js
...fs.createReadStream("./migration_data.csv") .pipe(parse({ delimiter: ",", from_line: 2 })) .on("data", function (row) { console.log(row); }) .on("end", function () { console.log("finished"); }) .on("error", function (error) { console.log(error.message); });
El end
evento se emite cuando se han leído todos los datos del archivo CSV. Cuando esto sucede, se invoca la devolución de llamada y se registra un mensaje que indica que ha finalizado.
Si ocurre un error en cualquier lugar mientras se leen y analizan los datos CSV, error
se emite el evento, que invoca la devolución de llamada y registra el mensaje de error en la consola.
Su archivo completo ahora debería verse así:
demo_csv/readCSV.js
const fs = require("fs");const { parse } = require("csv-parse");fs.createReadStream("./migration_data.csv") .pipe(parse({ delimiter: ",", from_line: 2 })) .on("data", function (row) { console.log(row); }) .on("end", function () { console.log("finished"); }) .on("error", function (error) { console.log(error.message); });
Guarde y salga de su readCSV.js
archivo usando CTRL+X
.
A continuación, ejecute el archivo utilizando el node
comando:
- node readCSV.js
El resultado será similar a esto (editado para abreviar):
Output[ '2001-01', '2020-09', 'Long-term migrant', 'Arrivals', 'Female', '0-4 years', '344', '0', 'Final']...[ '2021-09', ... '70', 'Provisional']finished
Todas las filas del archivo CSV se han transformado en matrices mediante el csv-parse
flujo de transformación. Debido a que el registro se realiza cada vez que se recibe un fragmento del flujo, los datos aparecen como si se estuvieran descargando en lugar de mostrarse todos a la vez.
En este paso, leerá los datos de un archivo CSV y los transformará en matrices. A continuación, insertará los datos de un archivo CSV en la base de datos.
Paso 3: inserción de datos en la base de datos
Insertar datos de un archivo CSV en la base de datos usando Node.js le brinda acceso a una amplia biblioteca de módulos que puede usar para procesar, limpiar o mejorar los datos antes de insertarlos en la base de datos.
En esta sección, establecerá una conexión con la base de datos SQLite mediante el node-sqlite3
módulo. Luego, creará una tabla en la base de datos, copiará el readCSV.js
archivo y lo modificará para insertar todos los datos leídos del archivo CSV en la base de datos.
Crea y abre un db.js
archivo en tu editor:
- nano db.js
En su db.js
archivo, agregue las siguientes líneas para importar los módulos fs
y node-sqlite3
:
demo_csv/db.js
const fs = require("fs");const sqlite3 = require("sqlite3").verbose();const filepath = "./population.db";...
En la tercera línea, se define la ruta de la base de datos SQLite y se almacena en la variable filepath
. El archivo de la base de datos aún no existe, pero será necesario para node-sqlite3
establecer una conexión con la base de datos.
En el mismo archivo, agregue las siguientes líneas para conectar Node.js a una base de datos SQLite:
demo_csv/db.js
...function connectToDatabase() { if (fs.existsSync(filepath)) { return new sqlite3.Database(filepath); } else { const db = new sqlite3.Database(filepath, (error) = { if (error) { return console.error(error.message); } console.log("Connected to the database successfully"); }); return db; }}
Aquí, se define una función denominada connectToDatabase()
para establecer una conexión con la base de datos. Dentro de la función, se invoca el método fs
del módulo en una declaración, que verifica si el archivo de la base de datos existe en el directorio del proyecto. Si la condición se evalúa como , se crea una instancia de la clase SQLite del módulo con la ruta del archivo de la base de datos. Una vez que se establece la conexión, la función devuelve el objeto de conexión y sale.existsSync()
if
if
true
Database()
node-sqlite3
Sin embargo, si la if
instrucción se evalúa como false
(si el archivo de base de datos no existe), la ejecución saltará al else
bloque. En el else
bloque, crea una instancia de la Database()
clase con dos argumentos: la ruta del archivo de base de datos y una devolución de llamada.
El primer argumento es la ruta del archivo de base de datos SQLite, que es ./population.db
. El segundo argumento es una devolución de llamada que se invocará automáticamente cuando la conexión con la base de datos se haya establecido correctamente o si se produjo un error. La devolución de llamada toma un error
objeto como parámetro, que es null
si la conexión es exitosa. Dentro de la devolución de llamada, la if
declaración verifica si el error
objeto está configurado. Si evalúa como true
, la devolución de llamada registra un mensaje de error y regresa. Si evalúa como false
, registra un mensaje de éxito que confirma que se ha establecido la conexión.
Actualmente, los bloques if
y else
establecen el objeto de conexión. Se pasa una devolución de llamada al invocar la Database
clase en el else
bloque para crear una tabla en la base de datos, pero solo si el archivo de base de datos no existe. Si el archivo de base de datos ya existe, la función ejecutará el if
bloque, se conectará con la base de datos y devolverá el objeto de conexión.
Para crear una tabla si el archivo de base de datos no existe, agregue el código resaltado:
demo_csv/db.js
const fs = require("fs");const sqlite3 = require("sqlite3").verbose();const filepath = "./population.db";function connectToDatabase() { if (fs.existsSync(filepath)) { return new sqlite3.Database(filepath); } else { const db = new sqlite3.Database(filepath, (error) = { if (error) { return console.error(error.message); } createTable(db); console.log("Connected to the database successfully"); }); return db; }}function createTable(db) { db.exec(` CREATE TABLE migration ( year_month VARCHAR(10), month_of_release VARCHAR(10), passenger_type VARCHAR(50), direction VARCHAR(20), sex VARCHAR(10), age VARCHAR(50), estimate INT )`);}module.exports = connectToDatabase();
Ahora connectToDatabase()
invoca la createTable()
función, que acepta el objeto de conexión almacenado en la db
variable como argumento.
Fuera de la connectToDatabase()
función, se define la createTable()
función, que acepta el objeto de conexión db
como parámetro. Se invoca el exec()
método en el db
objeto de conexión que toma una sentencia SQL como argumento. La sentencia SQL crea una tabla con un nombre migration
y 7 columnas. Los nombres de las columnas coinciden con los encabezados del migration_data.csv
archivo.
Finalmente, invoca la connectToDatabase()
función y exporta el objeto de conexión devuelto por la función para que pueda reutilizarse en otros archivos.
Guarde y salga de su db.js
archivo.
Una vez establecida la conexión a la base de datos, ahora copiará y modificará el readCSV.js
archivo para insertar las filas que el csv-parse
módulo analizó en la base de datos.
Copie y cambie el nombre del archivo insertData.js
con el siguiente comando:
- cp readCSV.js insertData.js
Abra el insertData.js
archivo en su editor:
- nano insertData.js
Añade el código resaltado:
demo_csv/insertData.js
const fs = require("fs");const { parse } = require("csv-parse");const db = require("./db");fs.createReadStream("./migration_data.csv") .pipe(parse({ delimiter: ",", from_line: 2 })) .on("data", function (row) { db.serialize(function () { db.run( `INSERT INTO migration VALUES (?, ?, ? , ?, ?, ?, ?)`, [row[0], row[1], row[2], row[3], row[4], row[5], row[6]], function (error) { if (error) { return console.log(error.message); } console.log(`Inserted a row with the id: ${this.lastID}`); } ); }); });
En la tercera línea, importa el objeto de conexión desde el db.js
archivo y lo almacena en la variable db
.
Dentro de la data
devolución de llamada de evento adjunta a la fs
secuencia del módulo, se invoca el serialize()
método en el objeto de conexión. El método garantiza que una instrucción SQL termine de ejecutarse antes de que comience otra, lo que puede ayudar a evitar condiciones de competencia en la base de datos donde el sistema ejecuta operaciones que compiten entre sí simultáneamente.
El serialize()
método acepta una devolución de llamada. En la devolución de llamada, se invoca el run
método en el db
objeto de conexión. El método acepta tres argumentos:
-
El primer argumento es una sentencia SQL que se pasará y se ejecutará en la base de datos SQLite. El
run()
método solo acepta sentencias SQL que no devuelvan resultados. LaINSERT INTO migration VALUES (?, ..., ?
sentencia inserta una fila en la tablamigration
y?
son marcadores de posición que luego se sustituyen con los valores en elrun()
segundo argumento del método. -
El segundo argumento es una matriz
[row[0], ... row[5], row[6]]
. En la sección anterior, elparse()
método recibe un fragmento de datos del flujo legible y lo transforma en una matriz. Dado que los datos se reciben como una matriz, para obtener cada valor de campo, debe usar índices de matriz para acceder a ellos, como[row[1], ..., row[6]]
, etc. -
El tercer argumento es una devolución de llamada que se ejecuta cuando se han insertado los datos o si se ha producido un error. La devolución de llamada comprueba si se ha producido un error y registra el mensaje de error. Si no hay errores, la función registra un mensaje de éxito en la consola mediante el
console.log()
método, lo que permite saber que se ha insertado una fila junto con el ID.
Por último, elimine los eventos end
y error
del archivo. Debido a la naturaleza asincrónica de los node-sqlite3
métodos, los eventos end
y error
se ejecutan antes de que se inserten los datos en la base de datos, por lo que ya no son necesarios.
Guarde y salga de su archivo.
Ejecute el insertData.js
archivo usando node
:
- node insertData.js
Dependiendo de su sistema, puede tomar algún tiempo, pero node
debería devolver el siguiente resultado:
OutputConnected to the database successfullyInserted a row with the id: 1Inserted a row with the id: 2...Inserted a row with the id: 44308Inserted a row with the id: 44309Inserted a row with the id: 44310
El mensaje, especialmente los identificadores, demuestra que la fila del archivo CSV se ha guardado en la base de datos.
Ahora puedes leer un archivo CSV e insertar su contenido en la base de datos. A continuación, escribirás un archivo CSV.
Paso 4: Escritura de archivos CSV
En esta sección, recuperará datos de la base de datos y los escribirá en un archivo CSV mediante transmisiones.
Crea y abre writeCSV.js
en tu editor:
- nano writeCSV.js
En su writeCSV.js
archivo, agregue las siguientes líneas para importar los módulos fs
y csv-stringify
y el objeto de conexión de base de datos desde db.js
:
demo_csv/writeCSV.js
const fs = require("fs");const { stringify } = require("csv-stringify");const db = require("./db");
El csv-stringify
módulo transforma datos de un objeto o matriz en un formato de texto CSV.
A continuación, agregue las siguientes líneas para definir una variable que contenga el nombre del archivo CSV en el que desea escribir datos y una secuencia escribible en la que escribirá los datos:
demo_csv/writeCSV.js
...const filename = "saved_from_db.csv";const writableStream = fs.createWriteStream(filename);const columns = [ "year_month", "month_of_release", "passenger_type", "direction", "sex", "age", "estimate",];
El createWriteStream
método toma como argumento el nombre del archivo en el que desea escribir su flujo de datos, que es el saved_from_db.csv
nombre del archivo almacenado en la filename
variable.
En la cuarta línea, se define una columns
variable que almacena una matriz que contiene los nombres de los encabezados de los datos CSV. Estos encabezados se escribirán en la primera línea del archivo CSV cuando comience a escribir los datos en el archivo.
Aún en su writeCSV.js
archivo, agregue las siguientes líneas para recuperar datos de la base de datos y escribir cada fila en el archivo CSV:
demo_csv/writeCSV.js
...const stringifier = stringify({ header: true, columns: columns });db.each(`select * from migration`, (error, row) = { if (error) { return console.log(error.message); } stringifier.write(row);});stringifier.pipe(writableStream);console.log("Finished writing data");
En primer lugar, se invoca el stringify
método con un objeto como argumento, lo que crea un flujo de transformación. El flujo de transformación convierte los datos de un objeto en texto CSV. El objeto que se pasa al stringify()
método tiene dos propiedades:
header
acepta un valor booleano y genera un encabezado si el valor booleano se establece entrue
.columns
toma una matriz que contiene los nombres de las columnas que se escribirán en la primera línea del archivo CSV si laheader
opción está configurada entrue
.
A continuación, invoca el each()
método desde el db
objeto de conexión con dos argumentos. El primer argumento es la sentencia SQL select * from migration
que recupera las filas una por una en la base de datos. El segundo argumento es una devolución de llamada que se invoca cada vez que se recupera una fila de la base de datos. La devolución de llamada toma dos parámetros: un error
objeto y un row
objeto que contiene datos recuperados de una sola fila en la base de datos. Dentro de la devolución de llamada, verifica si el error
objeto está configurado en la if
sentencia. Si la condición se evalúa como true
, se registra un mensaje de error en la consola mediante el console.log()
método. Si no hay ningún error, invoca el write()
método on stringifier
, que escribe los datos en el stringifier
flujo de transformación.
Cuando el each()
método termina de iterar, el pipe()
método en el stringifier
flujo comienza a enviar datos en fragmentos y a escribirlos en el archivo writableStream
. El flujo escribible guardará cada fragmento de datos en el saved_from_db.csv
archivo. Una vez que todos los datos se hayan escrito en el archivo, console.log()
se registrará un mensaje de éxito.
El archivo completo ahora se verá así:
demo_csv/writeCSV.js
const fs = require("fs");const { stringify } = require("csv-stringify");const db = require("./db");const filename = "saved_from_db.csv";const writableStream = fs.createWriteStream(filename);const columns = [ "year_month", "month_of_release", "passenger_type", "direction", "sex", "age", "estimate",];const stringifier = stringify({ header: true, columns: columns });db.each(`select * from migration`, (error, row) = { if (error) { return console.log(error.message); } stringifier.write(row);});stringifier.pipe(writableStream);console.log("Finished writing data");
Guarde y cierre su archivo, luego ejecute el writeCSV.js
archivo en la terminal:
- node writeCSV.js
Recibirá el siguiente resultado:
OutputFinished writing data
Para confirmar que se han escrito los datos, inspeccione el contenido del archivo utilizando el cat
comando:
- cat saved_from_db.csv
cat
devolverá todas las filas escritas en el archivo (editado para abreviar):
Outputyear_month,month_of_release,passenger_type,direction,sex,age,estimate2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,3442001-01,2020-09,Long-term migrant,Arrivals,Male,0-4 years,3412001-01,2020-09,Long-term migrant,Arrivals,Female,10-14 years,...
Ahora puede recuperar datos de la base de datos y escribir cada fila en un archivo CSV usando secuencias.
Conclusión
En este artículo, leyó un archivo CSV e insertó sus datos en una base de datos mediante los módulos node-csv
y node-sqlite3
. Luego recuperó datos de la base de datos y los escribió en otro archivo CSV.
Ahora puede leer y escribir archivos CSV. Como próximo paso, puede trabajar con grandes conjuntos de datos CSV utilizando la misma implementación con flujos que hacen un uso eficiente de la memoria, o puede buscar un paquete event-stream
que facilite el trabajo con flujos.
Para explorar más sobre node-csv
, visita su documentación Proyecto CSV – Paquete CSV de Node.js . Para obtener más información sobre node-sqlite3
, visita su documentación de Github . Para seguir desarrollando tus habilidades en Node.js, consulta la serie Cómo codificar en Node.js.
Deja una respuesta