O autor selecionou a Sociedade de Engenheiras para receber uma doação como parte do programa Escreva para Doações.
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 fluxos no Node.js, o que permite ler grandes conjuntos de dados sem consumir muita memória. Você modificará o programa para mover dados analisados do arquivo CSV para um banco de dados SQLite. Você também recuperará dados do banco de dados, analisará com node-csv
e usará fluxos no Node.js para escrevê-los em um arquivo CSV em partes.
Implante suas aplicações Node.js do GitHub usando Plataforma de Aplicativos da DigitalOcean. Deixe a DigitalOcean cuidar da escalabilidade do seu aplicativo.
Pré-requisitos
Para seguir este tutorial, você precisará:
-
O Node.js instalado no 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. O 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. Consulte Como Escrever e Executar Seu Primeiro Programa em Node.js.
-
Familiaridade com os fluxos do Node.js. Consulte Como Trabalhar com Arquivos Usando Fluxos no 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
.
Digite 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 todos os quatro módulos que fazem parte do pacote node-csv
: csv-generate
, csv-parse
, csv-stringify
e stream-transform
. Você usará 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 com ele, 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 fácil de trabalhar.
Usando o nano
, ou seu editor de texto favorito, abra o arquivo:
Uma vez 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. Este caractere é conhecido como 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 tabulações(\t
). Você precisa saber qual delimitador é usado no arquivo, já que a maioria dos módulos o requer 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 — Lendo Arquivos CSV
Nesta seção, você usará o 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 de leitura. Em seguida, você encaminhará o fluxo para outro fluxo inicializado com o módulo csv-parse
para analisar os pedaços de dados. Assim que os pedaços de dados forem analisados, você poderá 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 ao importar 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 destruturação.
Adicione as seguintes linhas para ler o arquivo CSV:
O método createReadStream()
do módulo fs
aceita um argumento do nome do arquivo que você deseja ler, que é migration_data.csv
aqui. Em seguida, ele cria um fluxo legível, que divide um arquivo grande em pedaços menores. Um fluxo legível permite que você apenas leia dados dele e não escreva nele.
Após criar o fluxo legível, o método pipe()
do Node encaminha pedaços 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 um pedaço de dados e o transforma em outra forma. Por exemplo, quando ele recebe um pedaço como 2001-01,2020-09,Migrante de longo prazo,Chegadas,Feminino,0-4 anos,344
, o método parse()
o transformará 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 possui as seguintes propriedades:
-
delimiter
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, esta 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ê irá 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 forem 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 é invocada e registra uma mensagem que indica 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 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 por 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 ocorre 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ê irá 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 aprimorar 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 uma declaração if
, que verifica se o arquivo do banco de dados existe no diretório do projeto. Se a condição if
avaliar 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 a declaração if
avaliar como false
(se o arquivo do banco de dados não existir), a execução irá pular 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, a declaração if
verifica se o objeto de error
está definido. Se ele avaliar como true
, o retorno de chamada registra uma mensagem de erro e retorna. Se ele avaliar 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 apenas 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 possa ser reutilizado em outros arquivos.
Salve e saia do seu arquivo db.js
.
Com a conexão ao banco de dados estabelecida, agora você irá 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 ser executada 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()
aceita apenas 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 pedaço de dados do fluxo legível 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 os índices do 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 que os dados sejam 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, comprova 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 do banco de dados de db.js
:
O módulo csv-stringify
transforma dados de um objeto ou array em 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 gravar os dados e um fluxo gravável no qual você irá escrever os dados:
O método createWriteStream
recebe um argumento com o nome do arquivo no qual você deseja gravar o fluxo de dados, que é o nome do arquivo saved_from_db.csv
armazenado na variável filename
.
Na quarta linha, você define a variável columns
, que armazena um array contendo os nomes dos cabeçalhos para os 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 em 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 um array 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 error
e um objeto row
contendo dados recuperados de uma única linha no banco de dados. Dentro do retorno de chamada, você verifica se o objeto 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 de stringifier
começa a enviar dados em blocos e escrevê-los no writableStream
. O fluxo gravável salvará cada bloco de dados no arquivo saved_from_db.csv
. Uma vez que todos os dados tenham sido escritos no arquivo, console.log()
irá registrar uma mensagem de sucesso.
O arquivo completo agora terá o seguinte aspecto:
Salve e feche seu arquivo, em seguida, execute o arquivo writeCSV.js
no terminal:
Você receberá a seguinte saída:
OutputFinished writing data
Para confirmar que os dados foram gravados, inspecione o conteúdo no arquivo usando o comando cat
:
cat
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 a documentação deles Projeto CSV – Pacote CSV Node.js. Para aprender mais sobre node-sqlite3
, visite a documentação deles no Github. Para continuar aprimorando suas habilidades em Node.js, veja a série Como Codificar em Node.js.