O autor selecionou Sociedade de Engenheiras para receber uma doação como parte do programa Write for Donations.
Introdução
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
.
Neste tutorial, você usará o módulo node-csv
para ler um arquivo CSV usando streams do Node.js, o que permite ler grandes conjuntos de dados sem consumir muita memória. Você modificará o programa para mover os dados analisados do arquivo CSV para um banco de dados SQLite. Você também recuperará dados do banco de dados, os analisará com node-csv
, e usará streams do Node.js para escrevê-los em um arquivo CSV em partes.
Implante suas aplicações Node.js do GitHub usando Plataforma de Aplicativos DigitalOcean. Deixe a DigitalOcean focar em dimensionar sua aplicação.
Pré-requisitos
Para seguir este tutorial, você precisará de:
-
Node.js instalado em seu ambiente local ou de servidor. Siga Como Instalar o Node.js e Criar um Ambiente de Desenvolvimento Local para instalar o Node.js.
-
O SQLite está instalado no seu ambiente local ou de servidor, o qual você pode instalar seguindo o passo 1 em Como Instalar e Usar o SQLite no Ubuntu 20.04. Conhecimento sobre como usar o SQLite é útil e pode ser aprendido nos passos 2-7 do guia de instalação.
-
Familiaridade com a escrita de um programa Node.js. Veja Como Escrever e Executar Seu Primeiro Programa em Node.js.
-
Familiaridade com streams Node.js. Veja Como Trabalhar com Arquivos Usando Streams em Node.js.
Passo 1 — Configurando o Diretório do Projeto
Nesta seção, você irá criar o diretório do projeto e baixar pacotes para sua aplicação. Você também irá baixar um conjunto de dados CSV do Stats NZ, que contém dados de migração internacional na Nova Zelândia.
Para começar, crie um diretório chamado csv_demo
e navegue até o diretório:
Em seguida, inicialize o diretório como um projeto npm usando o comando npm init
:
A opção -y
notifica o npm init
para responder “sim” a todas as solicitações. Este comando cria um package.json
com valores padrão que você pode alterar a qualquer momento.
Com o diretório inicializado como um projeto npm, agora você pode instalar as dependências necessárias: node-csv
e node-sqlite3
.
Insira o seguinte comando para instalar o node-csv
:
O módulo node-csv
é uma coleção de módulos que permite analisar e escrever dados em um arquivo CSV. O comando instala os quatro módulos que fazem parte do pacote node-csv
: csv-generate
, csv-parse
, csv-stringify
e stream-transform
. Você utilizará o módulo csv-parse
para analisar um arquivo CSV e o módulo csv-stringify
para escrever dados em um arquivo CSV.
Em seguida, instale o módulo node-sqlite3
:
O módulo node-sqlite3
permite que seu aplicativo interaja com o banco de dados SQLite.
Após instalar os pacotes em seu projeto, baixe o arquivo CSV de migração da Nova Zelândia com o comando wget
:
O arquivo CSV que você baixou tem um nome longo. Para facilitar o trabalho, renomeie o nome do arquivo para um nome mais curto usando o comando mv
:
O novo nome do arquivo CSV, migration_data.csv
, é mais curto e mais fácil de trabalhar.
Usando o nano
, ou seu editor de texto favorito, abra o arquivo:
Depois de aberto, você verá conteúdos semelhantes a estes:
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
...
A primeira linha contém os nomes das colunas, e todas as linhas subsequentes têm os dados correspondentes a cada coluna. Uma vírgula separa cada pedaço de dados. Esse caractere é conhecido como um delimitador porque delimita os campos. Você não está limitado a usar vírgulas. Outros delimitadores populares incluem dois pontos (:
), ponto e vírgula (;
) e guias (\t
). Você precisa saber qual delimitador é usado no arquivo, já que a maioria dos módulos requer isso para analisar os arquivos.
Depois de revisar o arquivo e identificar o delimitador, saia do seu arquivo migration_data.csv
usando CTRL+X
.
Você agora instalou as dependências necessárias para o seu projeto. Na próxima seção, você irá ler um arquivo CSV.
Passo 2 — Leitura de Arquivos CSV
Nesta seção, você usará node-csv
para ler um arquivo CSV e registrar seu conteúdo no console. Você usará o método createReadStream()
do módulo fs
para ler os dados do arquivo CSV e criar um fluxo legível. Em seguida, você encaminhará o fluxo para outro fluxo inicializado com o módulo csv-parse
para analisar os blocos de dados. Uma vez que os blocos de dados tenham sido analisados, você pode registrá-los no console.
Crie e abra um arquivo readCSV.js
no seu editor preferido:
No seu arquivo readCSV.js
, importe os módulos fs
e csv-parse
adicionando as seguintes linhas:
Na primeira linha, você define a variável fs
e atribui a ela o objeto fs
que o método require()
do Node.js retorna quando importa o módulo.
Na segunda linha, você extrai o método parse
do objeto retornado pelo método require()
para a variável parse
usando a sintaxe de desestruturação.
Adicione as seguintes linhas para ler o arquivo CSV:
O método createReadStream()
do módulo fs
aceita um argumento com o nome do arquivo que você deseja ler, que aqui é migration_data.csv
. Em seguida, ele cria um fluxo legível, que divide um arquivo grande em partes menores. Um fluxo legível permite apenas a leitura de dados e não a escrita.
Após criar o fluxo legível, o método pipe()
do Node encaminha partes de dados do fluxo legível para outro fluxo. O segundo fluxo é criado quando o método parse()
do módulo csv-parse
é invocado dentro do método pipe()
. O módulo csv-parse
implementa um fluxo de transformação (um fluxo legível e gravável), que recebe uma parte de dados e a transforma em outra forma. Por exemplo, quando ele recebe uma parte de dados como 2001-01,2020-09,Migrante de longo prazo,Chegadas,Feminino,0-4 anos,344
, o método parse()
irá transformá-lo em um array.
O método parse()
recebe um objeto que aceita propriedades. O objeto então configura e fornece mais informações sobre os dados que o método irá analisar. O objeto aceita as seguintes propriedades:
-
delimitador
define o caractere que separa cada campo na linha. O valor,
indica ao analisador que vírgulas demarcam os campos. -
from_line
define a linha onde o analisador deve começar a analisar as linhas. Com o valor2
, o analisador irá pular a linha 1 e começar na linha 2. Como você irá inserir os dados no banco de dados posteriormente, essa propriedade ajuda a evitar a inserção dos nomes das colunas na primeira linha do banco de dados.
Em seguida, você anexa um evento de streaming usando o método on()
do Node.js. Um evento de streaming permite que o método consuma um pedaço de dados se um determinado evento for emitido. O evento data
é acionado quando os dados transformados do método parse()
estão prontos para serem consumidos. Para acessar os dados, você passa um retorno de chamada para o método on()
, que recebe um parâmetro chamado row
. O parâmetro row
é um pedaço de dados transformado em uma matriz. Dentro do retorno de chamada, você registra os dados no console usando o método console.log()
.
Antes de executar o arquivo, você adicionará mais eventos de fluxo. Esses eventos de fluxo lidam com erros e escrevem uma mensagem de sucesso no console quando todos os dados no arquivo CSV foram consumidos.
Ainda no seu arquivo readCSV.js
, adicione o código destacado:
O evento end
é emitido quando todos os dados no arquivo CSV foram lidos. Quando isso acontece, a função de retorno de chamada é invocada e registra uma mensagem dizendo que terminou.
Se ocorrer um erro em qualquer lugar durante a leitura e análise dos dados CSV, o evento error
é emitido, o que invoca a função de retorno de chamada e registra a mensagem de erro no console.
Seu arquivo completo agora deve se parecer com o seguinte:
Salve e saia do seu arquivo readCSV.js
usando CTRL+X
.
Em seguida, execute o arquivo usando o comando node
:
A saída será semelhante a isto (editada para brevidade):
Output[
'2001-01',
'2020-09',
'Long-term migrant',
'Arrivals',
'Female',
'0-4 years',
'344',
'0',
'Final'
]
...
[
'2021-09',
...
'70',
'Provisional'
]
finished
Todas as linhas no arquivo CSV foram transformadas em arrays usando o fluxo de transformação csv-parse
. Como o registro acontece cada vez que um fragmento é recebido do fluxo, os dados parecem estar sendo baixados em vez de serem exibidos de uma vez.
Neste passo, você leu dados em um arquivo CSV e os transformou em arrays. Em seguida, você inserirá dados de um arquivo CSV no banco de dados.
Passo 3 — Inserindo Dados no Banco de Dados
Inserir dados de um arquivo CSV no banco de dados usando o Node.js dá acesso a uma vasta biblioteca de módulos que você pode usar para processar, limpar ou melhorar os dados antes de inseri-los no banco de dados.
Nesta seção, você estabelecerá uma conexão com o banco de dados SQLite usando o módulo node-sqlite3
. Em seguida, você criará uma tabela no banco de dados, copiará o arquivo readCSV.js
e o modificará para inserir todos os dados lidos do arquivo CSV no banco de dados.
Crie e abra um arquivo db.js
no seu editor:
No seu arquivo db.js
, adicione as seguintes linhas para importar os módulos fs
e node-sqlite3
:
Na terceira linha, você define o caminho do banco de dados SQLite e o armazena na variável filepath
. O arquivo do banco de dados ainda não existe, mas será necessário para que o node-sqlite3
estabeleça uma conexão com o banco de dados.
No mesmo arquivo, adicione as seguintes linhas para conectar o Node.js a um banco de dados SQLite:
Aqui, você define uma função chamada connectToDatabase()
para estabelecer uma conexão com o banco de dados. Dentro da função, você invoca o método existsSync()
do módulo fs
em um bloco if
, que verifica se o arquivo do banco de dados existe no diretório do projeto. Se a condição do if
for avaliada como true
, você instancia a classe Database()
do SQLite do módulo node-sqlite3
com o caminho do banco de dados. Uma vez que a conexão é estabelecida, a função retorna o objeto de conexão e sai.
No entanto, se o bloco if
for avaliado como false
(se o arquivo do banco de dados não existir), a execução será direcionada para o bloco else
. No bloco else
, você instancia a classe Database()
com dois argumentos: o caminho do arquivo do banco de dados e um retorno de chamada.
O primeiro argumento é o caminho do arquivo do banco de dados SQLite, que é ./population.db
. O segundo argumento é um retorno de chamada que será invocado automaticamente quando a conexão com o banco de dados for estabelecida com sucesso ou se ocorrer um erro. O retorno de chamada recebe um objeto de error
como parâmetro, que é null
se a conexão for bem-sucedida. Dentro do retorno de chamada, o bloco if
verifica se o objeto de error
está definido. Se for avaliado como true
, o retorno de chamada registra uma mensagem de erro e retorna. Se for avaliado como false
, você registra uma mensagem de sucesso confirmando que a conexão foi estabelecida.
Atualmente, os blocos if
e else
estabelecem o objeto de conexão. Você passa um retorno de chamada ao invocar a classe Database
no bloco else
para criar uma tabela no banco de dados, mas somente se o arquivo do banco de dados não existir. Se o arquivo do banco de dados já existir, a função executará o bloco if
, conectará-se ao banco de dados e retornará o objeto de conexão.
Para criar uma tabela se o arquivo do banco de dados não existir, adicione o código destacado:
Agora, o connectToDatabase()
invoca a função createTable()
, que aceita o objeto de conexão armazenado na variável db
como argumento.
Fora da função connectToDatabase()
, você define a função createTable()
, que aceita o objeto de conexão db
como parâmetro. Você invoca o método exec()
no objeto de conexão db
, que recebe uma instrução SQL como argumento. A instrução SQL cria uma tabela chamada migration
com 7 colunas. Os nomes das colunas correspondem aos cabeçalhos no arquivo migration_data.csv
.
Por fim, você invoca a função connectToDatabase()
e exporta o objeto de conexão retornado pela função para que ele possa ser reutilizado em outros arquivos.
Salve e saia do seu arquivo db.js
.
Com a conexão ao banco de dados estabelecida, você agora copiará e modificará o arquivo readCSV.js
para inserir as linhas que o módulo csv-parse
analisou no banco de dados.
Copie e renomeie o arquivo para insertData.js
com o seguinte comando:
Abra o arquivo insertData.js
no seu editor:
Adicione o código destacado:
Na terceira linha, você importa o objeto de conexão do arquivo db.js
e o armazena na variável db
.
Dentro do retorno de chamada do evento data
anexado ao fluxo do módulo fs
, você invoca o método serialize()
no objeto de conexão. O método garante que uma instrução SQL termine de executar antes que outra comece a ser executada, o que pode ajudar a evitar condições de corrida no banco de dados, onde o sistema executa operações concorrentes simultaneamente.
O método serialize()
recebe um retorno de chamada. Dentro do retorno de chamada, você invoca o método run
no objeto de conexão db
. O método aceita três argumentos:
-
O primeiro argumento é uma instrução SQL que será passada e executada no banco de dados SQLite. O método
run()
só aceita instruções SQL que não retornam resultados. A instruçãoINSERT INTO migration VALUES (?, ..., ?
insere uma linha na tabelamigration
, e os?
são espaços reservados que são posteriormente substituídos pelos valores no segundo argumento do métodorun()
. -
O segundo argumento é um array
[row[0], ... row[5], row[6]]
. Na seção anterior, o métodoparse()
recebe um bloco de dados do fluxo de leitura e o transforma em um array. Como os dados são recebidos como um array, para obter o valor de cada campo, você deve usar índices de array para acessá-los como[row[1], ..., row[6]]
, etc. -
O terceiro argumento é um retorno de chamada que é executado quando os dados foram inseridos ou se ocorreu um erro. O retorno de chamada verifica se ocorreu um erro e registra a mensagem de erro. Se não houver erros, a função registra uma mensagem de sucesso no console usando o método
console.log()
, informando que uma linha foi inserida junto com o id.
Finalmente, remova os eventos end
e error
do seu arquivo. Devido à natureza assíncrona dos métodos do node-sqlite3
, os eventos end
e error
são executados antes dos dados serem inseridos no banco de dados, então eles não são mais necessários.
Salve e saia do seu arquivo.
Execute o arquivo insertData.js
usando node
:
Dependendo do seu sistema, pode levar algum tempo, mas o node
deve retornar a saída abaixo:
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
A mensagem, especialmente os ids, prova que a linha do arquivo CSV foi salva no banco de dados.
Agora você pode ler um arquivo CSV e inserir seu conteúdo no banco de dados. Em seguida, você escreverá um arquivo CSV.
Passo 4 — Escrevendo Arquivos CSV
Nesta seção, você irá recuperar dados do banco de dados e escrevê-los em um arquivo CSV usando streams.
Crie e abra o arquivo writeCSV.js
no seu editor:
No seu arquivo writeCSV.js
, adicione as seguintes linhas para importar os módulos fs
e csv-stringify
e o objeto de conexão com o banco de dados de db.js
:
O módulo csv-stringify
transforma dados de um objeto ou array em um formato de texto CSV.
Em seguida, adicione as seguintes linhas para definir uma variável que contenha o nome do arquivo CSV no qual você deseja escrever os dados e um fluxo gravável no qual você escreverá os dados:
O método createWriteStream
recebe um argumento com o nome do arquivo no qual você deseja escrever seu fluxo de dados, que é o nome do arquivo saved_from_db.csv
armazenado na variável filename
.
Na quarta linha, você define uma variável columns
, que armazena uma matriz contendo os nomes dos cabeçalhos dos dados CSV. Esses cabeçalhos serão escritos na primeira linha do arquivo CSV quando você começar a escrever os dados no arquivo.
Ainda no seu arquivo writeCSV.js
, adicione as seguintes linhas para recuperar dados do banco de dados e escrever cada linha no arquivo CSV:
Primeiro, você invoca o método stringify
com um objeto como argumento, que cria um fluxo de transformação. O fluxo de transformação converte os dados de um objeto para texto CSV. O objeto passado para o método stringify()
possui duas propriedades:
header
aceita um valor booleano e gera um cabeçalho se o valor booleano estiver definido comotrue
.columns
recebe uma matriz contendo os nomes das colunas que serão escritas na primeira linha do arquivo CSV se a opçãoheader
estiver definida comotrue
.
Em seguida, você invoca o método each()
do objeto de conexão db
com dois argumentos. O primeiro argumento é a instrução SQL select * from migration
que recupera as linhas uma por uma no banco de dados. O segundo argumento é um retorno de chamada invocado cada vez que uma linha é recuperada do banco de dados. O retorno de chamada recebe dois parâmetros: um objeto de error
e um objeto de row
contendo dados recuperados de uma única linha no banco de dados. Dentro do retorno de chamada, você verifica se o objeto de error
está definido na instrução if
. Se a condição for avaliada como true
, uma mensagem de erro é registrada no console usando o método console.log()
. Se não houver erro, você invoca o método write()
em stringifier
, que escreve os dados no fluxo de transformação stringifier
.
Quando o método each()
termina de iterar, o método pipe()
no fluxo stringifier
começa a enviar dados em pedaços e escrevê-los no writableStream
. O fluxo gravável salvará cada pedaço de dados no arquivo saved_from_db.csv
. Assim que todos os dados forem escritos no arquivo, console.log()
registrará uma mensagem de sucesso.
O arquivo completo agora terá a seguinte aparência:
Salve e feche seu arquivo, e então execute o arquivo writeCSV.js
no terminal:
Você receberá a seguinte saída:
OutputFinished writing data
Para confirmar que os dados foram escritos, inspecione o conteúdo do arquivo usando o comando cat
:
cat
irá retornar todas as linhas escritas no arquivo (editado por brevidade):
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,
...
Agora você pode recuperar dados do banco de dados e escrever cada linha em um arquivo CSV usando streams.
Conclusão
Neste artigo, você leu um arquivo CSV e inseriu seus dados em um banco de dados usando os módulos node-csv
e node-sqlite3
. Em seguida, você recuperou dados do banco de dados e os escreveu em outro arquivo CSV.
Agora você pode ler e escrever arquivos CSV. Como próximo passo, você pode trabalhar com grandes conjuntos de dados CSV usando a mesma implementação com streams eficientes em memória, ou você pode procurar por um pacote como event-stream
que facilita muito o trabalho com streams.
Para explorar mais sobre node-csv
, visite sua documentação Projeto CSV – Pacote Node.js CSV. Para aprender mais sobre node-sqlite3
, visite sua documentação no Github. Para continuar a desenvolver suas habilidades em Node.js, veja a série Como Programar em Node.js.