El autor seleccionó a Sociedad de Ingenieras para recibir una donación como parte del programa Escribe para Donaciones.
Introducción
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
.
En este tutorial, utilizarás el módulo node-csv
para leer un archivo CSV usando flujos de Node.js, lo que te permite leer grandes conjuntos de datos sin consumir mucha memoria. Modificarás el programa para mover los datos analizados del archivo CSV a una base de datos SQLite. También recuperarás datos de la base de datos, los analizarás con node-csv
y usarás flujos de Node.js para escribirlos en un archivo CSV por fragmentos.
Implementa tus aplicaciones de Node desde GitHub utilizando Plataforma de Aplicaciones de DigitalOcean. Deja que DigitalOcean se encargue de escalar tu aplicación.
Prerrequisitos
Para seguir este tutorial, necesitarás:
-
Node.js instalado en tu entorno local o de servidor. Sigue Cómo Instalar Node.js y Crear un Entorno de Desarrollo Local para instalar Node.js.
-
SQLite instalado en tu entorno local o de servidor, lo cual puedes instalar siguiendo el paso 1 en Cómo Instalar y Usar SQLite en Ubuntu 20.04. Conocer cómo usar SQLite es útil y se puede aprender en los pasos 2-7 de la guía de instalación.
-
Familiaridad con la escritura de un programa Node.js. Ver Cómo Escribir y Ejecutar Tu Primer Programa en Node.js.
-
Familiaridad con las corrientes (streams) de Node.js. Ver Cómo Trabajar con Archivos Usando Corrientes en Node.js.
Paso 1: Configurar el directorio del proyecto
En esta sección, crearás el directorio del proyecto y descargarás paquetes para tu aplicación. También descargarás un conjunto de datos CSV de Stats NZ, que contiene datos de migración internacional en Nueva Zelanda.
Para empezar, crea un directorio llamado csv_demo
y navega hasta él:
A continuación, inicializa el directorio como un proyecto npm utilizando el comando npm init
:
La opción -y
le indica a npm init
que responda “sí” a todas las solicitudes. Este comando crea un package.json
con valores predeterminados que puedes cambiar en cualquier momento.
Con el directorio inicializado como un proyecto npm, ahora puedes instalar las dependencias necesarias: node-csv
y node-sqlite3
.
Ingresa el siguiente comando para instalar node-csv
:
El módulo node-csv
es una colección de módulos que te permite analizar y escribir datos en un archivo CSV. El comando instala los cuatro módulos que forman parte del paquete node-csv
: csv-generate
, csv-parse
, csv-stringify
y stream-transform
. Utilizarás el módulo csv-parse
para analizar un archivo CSV y el módulo csv-stringify
para escribir datos en un archivo CSV.
A continuación, instala el módulo node-sqlite3
:
El módulo node-sqlite3
permite que tu aplicación interactúe con la base de datos SQLite.
Después de instalar los paquetes en tu proyecto, descarga el archivo CSV de migración de Nueva Zelanda con el comando wget
:
El archivo CSV que has descargado tiene un nombre largo. Para facilitar su trabajo, cambia el nombre del archivo a un nombre más corto usando el comando mv
:
El nuevo nombre del archivo CSV, migration_data.csv
, es más corto y más fácil de trabajar.
Usando nano
, o tu editor de texto favorito, abre el archivo:
Una vez abierto, verás un contenido similar a este:
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 primera línea contiene los nombres de las columnas, y todas las líneas subsiguientes tienen los datos correspondientes a cada columna. Una coma separa cada dato. Este carácter se conoce como delimitador porque delimita los campos. No estás limitado a usar comas. Otros delimitadores populares incluyen los dos puntos (:
), los puntos y comas (;
) y las tabulaciones (\t
). Necesitas 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 de su archivo migration_data.csv
usando CTRL+X
.
Ahora 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, usará node-csv
para leer un archivo CSV y registrar su contenido en la consola. Utilizará el método createReadStream()
del módulo fs
para leer los datos del archivo CSV y crear un flujo legible. Luego, enviará el flujo a otro flujo inicializado con el módulo csv-parse
para analizar los fragmentos de datos. Una vez que los fragmentos de datos hayan sido analizados, puede registrarlos en la consola.
Cree y abra un archivo readCSV.js
en su editor preferido:
En su archivo readCSV.js
, importe los módulos fs
y csv-parse
agregando las siguientes líneas:
En la primera línea, define la variable fs
y le asigna el objeto fs
que el método require()
de Node.js devuelve al importar el módulo.
En la segunda línea, extraes el método parse
del objeto devuelto por el método require()
en la variable parse
utilizando la sintaxis de desestructuración.
Agrega las siguientes líneas para leer el archivo CSV:
El método createReadStream()
del módulo fs
acepta un argumento con el nombre del archivo que deseas leer, que en este caso es migration_data.csv
. Luego, crea un flujo legible, que toma un archivo grande y lo divide en fragmentos más pequeños. Un flujo legible te permite solo leer datos de él y no escribir en él.
Después de crear el flujo legible, el método pipe()
de Node envía fragmentos de datos desde el flujo legible a otro flujo. El segundo flujo se crea cuando se invoca el método parse()
del módulo csv-parse
dentro del método pipe()
. El módulo csv-parse
implementa un flujo de transformación (un flujo legible y escribible), que toma un fragmento de datos y lo transforma en otra forma. Por ejemplo, cuando recibe un fragmento como 2001-01,2020-09,Migrante a largo plazo,Llegadas,Femenino,0-4 años,344
, el método parse()
lo transformará en un array.
El método parse()
recibe un objeto que acepta propiedades. Luego, el objeto configura y proporciona más información sobre los datos que el método analizará. El objeto toma las siguientes propiedades:
-
delimiter
define el carácter que separa cada campo en la fila. El valor,
indica al analizador que las comas delimitan los campos. -
from_line
define la línea donde 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. Como insertarás los datos en la base de datos más tarde, esta propiedad te ayuda a evitar la inserción de los nombres de columna en la primera fila de la base de datos.
A continuación, adjuntas un evento de transmisión utilizando el método on()
de Node.js. Un evento de transmisión permite que el método consuma un fragmento de datos si se emite cierto evento. El evento data
se desencadena cuando los datos transformados del método parse()
están listos para ser consumidos. Para acceder a los datos, pasas un callback al método on()
, que toma un parámetro llamado row
. El parámetro row
es un fragmento de datos transformado en un array. Dentro del callback, registras los datos en la consola utilizando el método console.log()
.
Antes de ejecutar el archivo, agregarás más eventos de flujo. Estos eventos de flujo manejan errores y escriben un mensaje de éxito en la consola cuando se haya consumido todos los datos en el archivo CSV.
Todavía en tu archivo readCSV.js
, agrega el código resaltado:
El evento end
se emite cuando todos los datos en el archivo CSV han sido leídos. Cuando esto sucede, se invoca al callback y se registra un mensaje que indica que ha terminado.
Si ocurre un error en cualquier parte mientras se lee y se analizan los datos CSV, se emite el evento error
, que invoca al callback y registra el mensaje de error en la consola.
El archivo completo debería lucir ahora como el siguiente:
Guarda y sal del archivo readCSV.js
usando CTRL+X
.
A continuación, ejecuta el archivo usando el comando node
:
La salida se verá similar a esto (editado por brevedad):
Output[
'2001-01',
'2020-09',
'Long-term migrant',
'Arrivals',
'Female',
'0-4 years',
'344',
'0',
'Final'
]
...
[
'2021-09',
...
'70',
'Provisional'
]
finished
Todas las filas en el archivo CSV se han transformado en arrays usando el flujo de transformación csv-parse
. Debido a que el registro ocurre cada vez que se recibe un fragmento del flujo, los datos parecen estar siendo descargados en lugar de mostrarse todos a la vez.
En este paso, leíste datos en un archivo CSV y los transformaste en arrays. A continuación, insertarás datos desde un archivo CSV en la base de datos.
Paso 3 — Inserción de datos en la base de datos
Insertar datos desde un archivo CSV en la base de datos utilizando Node.js te da acceso a una amplia biblioteca de módulos que puedes usar para procesar, limpiar o mejorar los datos antes de insertarlos en la base de datos.
En esta sección, establecerás una conexión con la base de datos SQLite utilizando el módulo node-sqlite3
. Luego crearás una tabla en la base de datos, copiarás el archivo readCSV.js
, y lo modificarás para insertar todos los datos leídos del archivo CSV en la base de datos.
Crea y abre un archivo db.js
en tu editor:
En tu archivo db.js
, agrega las siguientes líneas para importar los módulos fs
y node-sqlite3
:
En la tercera línea, defines la ruta de la base de datos SQLite y la almacenas en la variable filepath
. El archivo de la base de datos aún no existe, pero será necesario para que node-sqlite3
establezca una conexión con la base de datos.
En el mismo archivo, agrega las siguientes líneas para conectar Node.js a una base de datos SQLite:
Aquí defines una función llamada connectToDatabase()
para establecer una conexión a la base de datos. Dentro de la función, invocas el método existsSync()
del módulo fs
en una declaración if
, que verifica si el archivo de la base de datos existe en el directorio del proyecto. Si la condición del if
se evalúa como true
, instancias la clase Database()
de SQLite del módulo node-sqlite3
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.
Sin embargo, si la declaración if
se evalúa como false
(si el archivo de la base de datos no existe), la ejecución saltará al bloque else
. En el bloque else
, instancias la clase Database()
con dos argumentos: la ruta del archivo de la base de datos y un callback.
El primer argumento es la ruta del archivo de la base de datos SQLite, que es ./population.db
. El segundo argumento es un callback que se invocará automáticamente cuando la conexión con la base de datos se haya establecido correctamente o si ocurrió un error. El callback toma un objeto error
como parámetro, que es null
si la conexión es exitosa. Dentro del callback, la declaración if
verifica si el objeto error
está establecido. Si se evalúa como true
, el callback registra un mensaje de error y retorna. Si se evalúa como false
, registras un mensaje de éxito confirmando que se ha establecido la conexión.
Actualmente, los bloques if
y else
establecen el objeto de conexión. Pasas un callback al invocar la clase Database
en el bloque else
para crear una tabla en la base de datos, pero solo si el archivo de la base de datos no existe. Si el archivo de la base de datos ya existe, la función ejecutará el bloque if
, se conectará con la base de datos y devolverá el objeto de conexión.
Para crear una tabla si el archivo de la base de datos no existe, agrega el código resaltado:
Ahora, la función connectToDatabase()
invoca la función createTable()
, que acepta el objeto de conexión almacenado en la variable db
como argumento.
Fuera de la función connectToDatabase()
, defines la función createTable()
, que acepta el objeto de conexión db
como parámetro. Invocas el método exec()
en el objeto de conexión db
que toma una declaración SQL como argumento. La declaración SQL crea una tabla llamada migration
con 7 columnas. Los nombres de las columnas coinciden con los encabezados en el archivo migration_data.csv
.
Finalmente, invocas la función connectToDatabase()
y exportas el objeto de conexión devuelto por la función para que pueda ser reutilizado en otros archivos.
Guarda y cierra tu archivo db.js
.
Con la conexión a la base de datos establecida, ahora copiarás y modificarás el archivo readCSV.js
para insertar las filas que el módulo csv-parse
analizó en la base de datos.
Copia y renombra el archivo a insertData.js
con el siguiente comando:
Abre el archivo insertData.js
en tu editor:
Agrega el código resaltado:
En la tercera línea, importas el objeto de conexión desde el archivo db.js
y lo almacenas en la variable db
.
Dentro del callback del evento data
adjunto al flujo del módulo fs
, invocas el método serialize()
en el objeto de conexión. El método asegura que una declaración SQL finalice su ejecución antes de que comience otra, lo que puede ayudar a prevenir condiciones de carrera en la base de datos donde el sistema ejecuta operaciones competidoras simultáneamente.
El método serialize()
toma un callback. Dentro del callback, invocas el método run
en el objeto de conexión db
. El método acepta tres argumentos:
-
El primer argumento es una declaración SQL que se pasará y ejecutará en la base de datos SQLite. El método
run()
solo acepta declaraciones SQL que no devuelven resultados. La declaraciónINSERT INTO migration VALUES (?, ..., ?)
inserta una fila en la tablamigration
, y los?
son marcadores de posición que luego se sustituyen con los valores en el segundo argumento del métodorun()
. -
El segundo argumento es un array
[fila[0], ... fila[5], fila[6]]
. En la sección anterior, el métodoparse()
recibe un fragmento de datos del flujo legible y lo transforma en un array. Dado que los datos se reciben como un array, para obtener el valor de cada campo, debes usar los índices del array para acceder a ellos como[fila[1], ..., fila[6]]
, etc. -
El tercer argumento es una devolución de llamada que se ejecuta cuando los datos han sido insertados o si ocurrió un error. La devolución de llamada verifica si ocurrió un error y registra el mensaje de error. Si no hay errores, la función registra un mensaje de éxito en la consola utilizando el método
console.log()
, informándote que se ha insertado una fila junto con el id.
Finalmente, elimine los eventos end
y error
de su archivo. Debido a la naturaleza asincrónica de los métodos de node-sqlite3
, los eventos end
y error
se ejecutan antes de que los datos se inserten en la base de datos, por lo que ya no son necesarios.
Guarde y salga de su archivo.
Ejecute el archivo insertData.js
usando node
:
Dependiendo de su sistema, puede tomar algún tiempo, pero node
debería devolver la salida a continuación:
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
El mensaje, especialmente los ids, demuestra que la fila del archivo CSV ha sido guardada en la base de datos.
Ahora puede leer un archivo CSV e insertar su contenido en la base de datos. A continuación, escribirá un archivo CSV.
Paso 4 — Escribiendo Archivos CSV
En esta sección, recuperará datos de la base de datos y los escribirá en un archivo CSV utilizando streams.
Cree y abra writeCSV.js
en su editor:
En su archivo writeCSV.js
, agregue las siguientes líneas para importar los módulos fs
y csv-stringify
y el objeto de conexión a la base de datos desde db.js
:
El módulo csv-stringify
transforma datos de un objeto o array 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 al que desea escribir datos y un flujo de escritura al que escribirá datos:
El método createWriteStream
toma un argumento del nombre de archivo al que desea escribir su flujo de datos, que es el nombre de archivo saved_from_db.csv
almacenado en la variable filename
.
En la cuarta línea, se define una variable columns
, que almacena una matriz que contiene los nombres de los encabezados para 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.
Todavía en su archivo writeCSV.js
, agregue las siguientes líneas para recuperar datos de la base de datos y escribir cada fila en el archivo CSV:
Primero, se invoca el método stringify
con un objeto como argumento, que crea un flujo de transformación. El flujo de transformación convierte los datos de un objeto en texto CSV. El objeto pasado al método stringify()
tiene dos propiedades:
header
acepta un valor booleano y genera un encabezado si el valor booleano está configurado 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 la opciónheader
está configurada entrue
.
A continuación, invocas el método each()
desde el objeto de conexión db
con dos argumentos. El primer argumento es la declaración SQL select * from migration
que recupera las filas una por una en la base de datos. El segundo argumento es un callback invocado cada vez que se recupera una fila de la base de datos. El callback toma dos parámetros: un objeto error
y un objeto row
que contiene datos recuperados de una sola fila en la base de datos. Dentro del callback, se verifica si el objeto error
está configurado en la declaración if
. Si la condición se evalúa como true
, se registra un mensaje de error en la consola usando el método console.log()
. Si no hay error, se invoca el método write()
en stringifier
, que escribe los datos en el flujo de transformación stringifier
.
Cuando el método each()
termina de iterar, el método pipe()
en el flujo stringifier
comienza a enviar datos en trozos y a escribirlo en el writableStream
. El flujo escribible guardará cada trozo de datos en el archivo saved_from_db.csv
. Una vez que todos los datos hayan sido escritos en el archivo, console.log()
registrará un mensaje de éxito.
El archivo completo ahora se verá así:
Guarda y cierra tu archivo, luego ejecuta el archivo writeCSV.js
en la terminal:
Recibirás la siguiente salida:
OutputFinished writing data
Para confirmar que los datos han sido escritos, inspecciona el contenido del archivo usando el comando cat
:
cat
devolverá todas las filas escritas en el archivo (editado por brevedad):
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,
...
Ahora puedes recuperar datos de la base de datos y escribir cada fila en un archivo CSV utilizando flujos.
Conclusión
En este artículo, leíste un archivo CSV e insertaste sus datos en una base de datos utilizando los módulos node-csv
y node-sqlite3
. Luego recuperaste datos de la base de datos y los escribiste en otro archivo CSV.
Ahora puedes leer y escribir archivos CSV. Como próximo paso, ahora puedes trabajar con grandes conjuntos de datos CSV utilizando la misma implementación con flujos eficientes en memoria, o podrías investigar un paquete como event-stream
que facilita mucho trabajar con flujos.
Para explorar más sobre node-csv
, visita su documentación Proyecto CSV – Paquete CSV de Node.js. Para aprender más sobre node-sqlite3
, visita su documentación en Github. Para seguir desarrollando tus habilidades en Node.js, consulta la serie Cómo Codificar en Node.js.