Автор выбрал Общество женщин-инженеров для получения пожертвования в рамках программы Написать за Пожертвования.
Введение
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
.
В этом руководстве вы будете использовать модуль node-csv
для чтения CSV-файла с использованием потоков Node.js, что позволяет читать большие наборы данных без большого расхода памяти. Вы измените программу, чтобы переместить данные, разобранные из CSV-файла, в базу данных SQLite. Вы также извлечете данные из базы данных, разберете их с помощью node-csv
и используете потоки Node.js для записи их в CSV-файл порциями.
Развертывайте ваши приложения Node из GitHub с использованием Платформы приложений DigitalOcean. Позвольте DigitalOcean заниматься масштабированием вашего приложения.
Предварительные требования
Чтобы следовать этому руководству, вам понадобится:
-
Установленный Node.js на вашем локальном или серверном окружении. Следуйте руководству Как установить Node.js и создать локальное среду разработки, чтобы установить Node.js.
-
SQLite установлен на вашем локальном или серверном окружении, который вы можете установить, следуя шагу 1 в Как установить и использовать SQLite в Ubuntu 20.04. Знание использования SQLite полезно и может быть изучено на шагах 2-7 инструкции по установке.
-
Знакомство с написанием программы на Node.js. Смотрите Как написать и запустить вашу первую программу на Node.js.
-
Знакомство с потоками Node.js. Смотрите Как работать с файлами с использованием потоков в Node.js.
Шаг 1 — Настройка каталога проекта
В этом разделе вы создадите каталог проекта и загрузите пакеты для вашего приложения. Вы также загрузите набор данных CSV от Stats NZ, который содержит данные о международной миграции в Новой Зеландии.
Чтобы начать, создайте каталог с именем csv_demo
и перейдите в него:
Затем инициализируйте каталог как проект npm, используя команду npm init
:
Опция -y
уведомляет npm init
ответить “да” на все запросы. Эта команда создает package.json
с значениями по умолчанию, которые вы можете изменить в любое время.
После инициализации каталога как проекта npm, теперь вы можете установить необходимые зависимости: node-csv
и node-sqlite3
.
Введите следующую команду для установки node-csv
:
Модуль node-csv
– это коллекция модулей, которая позволяет вам разбирать и записывать данные в файл CSV. Эта команда устанавливает все четыре модуля, которые входят в пакет node-csv
: csv-generate
, csv-parse
, csv-stringify
и stream-transform
. Вы будете использовать модуль csv-parse
для разбора CSV-файла и модуль csv-stringify
для записи данных в CSV-файл.
Затем установите модуль node-sqlite3
:
Модуль node-sqlite3
позволяет вашему приложению взаимодействовать с базой данных SQLite.
После установки пакетов в ваш проект загрузите файл миграции Новой Зеландии в формате CSV с помощью команды wget
:
Скачанный вами CSV-файл имеет длинное имя. Чтобы было удобнее с ним работать, переименуйте его в более короткое имя с помощью команды mv
:
Новое имя CSV-файла, migration_data.csv
, короче и удобнее для работы.
С помощью nano
или вашего любимого текстового редактора откройте файл:
После открытия вы увидите содержимое, подобное этому:
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
...
Первая строка содержит названия столбцов, а все последующие строки содержат данные, соответствующие каждому столбцу. Запятая разделяет каждый элемент данных. Этот символ известен как разделитель, потому что он отделяет поля. Вы не ограничены использованием запятых. Другие популярные разделители включают двоеточия (:
), точки с запятой (;
) и табуляции (\td
). Вам нужно знать, какой разделитель используется в файле, так как большинство модулей требуют его для разбора файлов.
После просмотра файла и определения разделителя закройте ваш файл migration_data.csv
, используя CTRL+X
.
Теперь у вас установлены необходимые зависимости для вашего проекта. В следующем разделе вы будете читать CSV-файл.
Шаг 2 — Чтение CSV-файлов
В этом разделе вы будете использовать node-csv
для чтения CSV-файла и записи его содержимого в консоль. Вы будете использовать метод createReadStream()
модуля fs
для чтения данных из CSV-файла и создания читаемого потока. Затем вы перенаправите поток в другой поток, инициализированный модулем csv-parse
, чтобы разбирать куски данных. После того как куски данных будут разобраны, вы можете вывести их в консоль.
Создайте и откройте файл readCSV.js
в вашем предпочтительном редакторе:
В вашем файле readCSV.js
импортируйте модули fs
и csv-parse
, добавив следующие строки:
В первой строке вы определяете переменную fs
и присваиваете ей объект fs
, который возвращает метод require()
Node.js при импорте модуля.
На второй строке вы извлекаете метод parse
из объекта, возвращаемого методом require()
, в переменную parse
, используя синтаксис деструктуризации.
Добавьте следующие строки для чтения CSV-файла:
Метод createReadStream()
из модуля fs
принимает аргумент с именем файла, который вы хотите прочитать, здесь это migration_data.csv
. Затем он создает читаемый поток, который берет большой файл и разбивает его на более мелкие куски. Читаемый поток позволяет вам только читать данные из него, а не записывать в него.
После создания читаемого потока метод pipe()
Node перенаправляет куски данных из читаемого потока в другой поток. Второй поток создается, когда внутри метода pipe()
вызывается метод parse()
модуля csv-parse
. Модуль csv-parse
реализует поток трансформации (читаемый и записываемый поток), который принимает кусок данных и преобразует его в другую форму. Например, когда он получает кусок данных, подобный 2001-01,2020-09,Мигрант долгосрочный,Прибытия,Женский,0-4 года,344
, метод parse()
преобразует его в массив.
Метод parse()
принимает объект, который принимает свойства. Затем этот объект конфигурирует и предоставляет дополнительную информацию о данных, которые метод будет разбирать. Объект принимает следующие свойства:
-
разделитель
определяет символ, который разделяет каждое поле в строке. Значение,
сообщает парсеру, что запятые отделяют поля. -
from_line
определяет строку, с которой парсер должен начать анализ строк. Со значением2
парсер пропустит строку 1 и начнет с строки 2. Поскольку вы будете вставлять данные в базу данных позже, эта функция помогает избежать вставки имен столбцов в первую строку базы данных.
Затем вы присоединяете событие потоковой передачи данных, используя метод on()
в Node.js. Событие потоковой передачи позволяет методу потреблять фрагмент данных, если возникает определенное событие. Событие data
срабатывает, когда данные, преобразованные из метода parse()
, готовы к потреблению. Чтобы получить доступ к данным, вы передаете обратный вызов методу on()
, который принимает параметр с именем row
. Параметр row
является фрагментом данных, преобразованным в массив. Внутри обратного вызова вы регистрируете данные в консоли, используя метод console.log()
.
Перед запуском файла вы добавите больше событий потока. Эти события потока обрабатывают ошибки и записывают сообщение об успешном выполнении в консоль, когда все данные в CSV-файле будут обработаны.
По-прежнему в вашем файле readCSV.js
добавьте выделенный код:
Событие end
генерируется, когда все данные в CSV-файле прочитаны. Когда это происходит, вызывается обратный вызов и регистрируется сообщение о завершении.
Если происходит ошибка при чтении и разборе данных CSV, генерируется событие error
, которое вызывает обратный вызов и регистрирует сообщение об ошибке в консоли.
Ваш полный файл теперь должен выглядеть следующим образом:
Сохраните и выйдите из файла readCSV.js
, используя CTRL+X
.
Затем запустите файл с помощью команды node
:
Вывод будет выглядеть примерно так (отредактировано для краткости):
Output[
'2001-01',
'2020-09',
'Long-term migrant',
'Arrivals',
'Female',
'0-4 years',
'344',
'0',
'Final'
]
...
[
'2021-09',
...
'70',
'Provisional'
]
finished
Все строки в CSV-файле были преобразованы в массивы с использованием потока преобразования csv-parse
. Поскольку регистрация происходит каждый раз, когда часть данных получена из потока, данные выглядят так, будто они загружаются, а не отображаются сразу.
На этом этапе вы читаете данные в CSV-файле и преобразуете их в массивы. Далее вы вставите данные из CSV-файла в базу данных.
Шаг 3 — Вставка данных в базу данных
Вставка данных из файла CSV в базу данных с использованием Node.js дает вам доступ к обширной библиотеке модулей, которые вы можете использовать для обработки, очистки или улучшения данных перед их вставкой в базу данных.
В этом разделе вы установите соединение с базой данных SQLite, используя модуль node-sqlite3
. Затем вы создадите таблицу в базе данных, скопируете файл readCSV.js
и измените его для вставки всех данных, считанных из файла CSV, в базу данных.
Создайте и откройте файл db.js
в вашем редакторе:
В вашем файле db.js
добавьте следующие строки для импорта модулей fs
и node-sqlite3
:
На третьей строке вы определяете путь к базе данных SQLite и сохраняете его в переменной filepath
. Файл базы данных пока не существует, но он будет нужен для установления соединения с базой данных с помощью node-sqlite3
.
В том же файле добавьте следующие строки для подключения Node.js к базе данных SQLite:
Здесь вы определяете функцию с именем connectToDatabase()
для установления соединения с базой данных. Внутри функции вы вызываете метод existsSync()
модуля fs
в операторе if
, который проверяет, существует ли файл базы данных в каталоге проекта. Если условие if
оценивается как true
, вы создаете экземпляр класса Database()
модуля SQLite из node-sqlite3
с путем к базе данных. После установления соединения функция возвращает объект соединения и завершает выполнение.
Однако, если условие if
оценивается как false
(если файл базы данных не существует), выполнение перейдет к блоку else
. В блоке else
вы создаете экземпляр класса Database()
с двумя аргументами: путем к файлу базы данных и функцией обратного вызова.
Первый аргумент – это путь к файлу базы данных SQLite, который равен ./population.db
. Второй аргумент – это функция обратного вызова, которая будет вызвана автоматически, когда соединение с базой данных будет установлено успешно или если произойдет ошибка. Функция обратного вызова принимает объект error
в качестве параметра, который равен null
, если соединение установлено успешно. Внутри обратного вызова оператор if
проверяет, установлен ли объект error
. Если это оценивается как true
, то функция обратного вызова записывает сообщение об ошибке и завершает выполнение. Если оценивается как false
, то вы записываете сообщение об успешном установлении соединения.
В данный момент блоки if
и else
устанавливают объект подключения. Вы передаете обратный вызов при вызове класса Database
в блоке else
, чтобы создать таблицу в базе данных, только если файл базы данных не существует. Если файл базы данных уже существует, функция выполнит блок if
, подключится к базе данных и вернет объект подключения.
Чтобы создать таблицу, если файл базы данных не существует, добавьте выделенный код:
Теперь функция connectToDatabase()
вызывает функцию createTable()
, которая принимает объект подключения, сохраненный в переменной db
, в качестве аргумента.
Вне функции connectToDatabase()
определите функцию createTable()
, которая принимает объект подключения db
в качестве параметра. Вызовите метод exec()
на объекте подключения db
, который принимает оператор SQL в качестве аргумента. Оператор SQL создает таблицу с именем migration
с 7 столбцами. Названия столбцов совпадают с заголовками в файле migration_data.csv
.
Наконец, вызовите функцию connectToDatabase()
и экспортируйте объект подключения, возвращенный функцией, чтобы его можно было использовать в других файлах.
Сохраните и закройте ваш файл db.js
.
После установления соединения с базой данных скопируйте и измените файл readCSV.js
, чтобы вставить строки, которые модуль csv-parse
разобрал, в базу данных.
Скопируйте и переименуйте файл следующей командой:
Откройте файл insertData.js
в вашем редакторе:
Добавьте выделенный код:
На третьей строке вы импортируете объект подключения из файла db.js
и сохраняете его в переменной db
.
Внутри обратного вызова события data
, присоединенного к потоку модуля fs
, вы вызываете метод serialize()
на объекте подключения. Метод обеспечивает завершение выполнения оператора SQL перед началом выполнения другого, что может помочь предотвратить гонку баз данных, когда система выполняет конкурирующие операции одновременно.
Метод serialize()
принимает обратный вызов. Внутри обратного вызова вы вызываете метод run
на объекте подключения db
. Метод принимает три аргумента:
-
Первый аргумент – это оператор SQL, который будет передан и выполнен в базе данных SQLite. Метод
run()
принимает только операторы SQL, которые не возвращают результатов. ОператорINSERT INTO migration VALUES (?, ..., ?)
вставляет строку в таблицуmigration
, а?
– это заполнители, которые позже заменяются значениями во втором аргументе методаrun()
. -
Второй аргумент представляет собой массив
[row[0], ... row[5], row[6]]
. В предыдущем разделе методparse()
получает фрагмент данных из потока чтения и преобразует его в массив. Поскольку данные получаются в виде массива, для получения значения каждого поля необходимо использовать индексы массива для доступа к ним, как[row[1], ..., row[6]]
и т. д. -
Третий аргумент представляет собой обратный вызов, который выполняется, когда данные были вставлены или если произошла ошибка. Обратный вызов проверяет, произошла ли ошибка, и регистрирует сообщение об ошибке. Если ошибок нет, функция регистрирует сообщение об успешном выполнении в консоли с использованием метода
console.log()
, уведомляя вас о том, что строка была вставлена вместе с идентификатором.
Наконец, удалите события end
и error
из вашего файла. В связи с асинхронным характером методов node-sqlite3
, события end
и error
выполняются до того, как данные будут вставлены в базу данных, поэтому они больше не требуются.
Сохраните и закройте ваш файл.
Запустите файл insertData.js
с помощью node
:
В зависимости от вашей системы это может занять некоторое время, но node
должен вернуть следующий вывод:
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
Это сообщение, особенно идентификаторы, подтверждает, что строка из CSV-файла была сохранена в базе данных.
Теперь вы можете читать CSV-файл и вставлять его содержимое в базу данных. Далее вы будете писать CSV-файл.
Шаг 4 — Запись CSV-файлов
В этом разделе вы извлечете данные из базы данных и запишете их в CSV-файл с использованием потоков.
Создайте и откройте в вашем редакторе файл writeCSV.js
:
В вашем файле writeCSV.js
добавьте следующие строки для импорта модулей fs
и csv-stringify
и объекта подключения к базе данных из db.js
:
Модуль csv-stringify
преобразует данные из объекта или массива в текстовый формат CSV.
Затем добавьте следующие строки для определения переменной, содержащей имя файла CSV, в который вы хотите записать данные, и записываемого потока, в который вы будете записывать данные:
Метод createWriteStream
принимает аргумент с именем файла, в который вы хотите записать поток данных, который является именем файла saved_from_db.csv
, хранящимся в переменной filename
.
На четвертой строке вы определяете переменную columns
, которая хранит массив, содержащий имена заголовков для данных CSV. Эти заголовки будут записаны в первой строке файла CSV, когда вы начнете запись данных в файл.
В вашем файле writeCSV.js
добавьте следующие строки для извлечения данных из базы данных и записи каждой строки в файл CSV:
Сначала вызовите метод stringify
с объектом в качестве аргумента, который создает поток преобразования. Поток преобразования преобразует данные из объекта в текст CSV. Объект, переданный в метод stringify()
, имеет два свойства:
header
принимает логическое значение и генерирует заголовок, если логическое значение установлено вtrue
.columns
принимает массив, содержащий имена столбцов, которые будут записаны в первой строке файла CSV, если опцияheader
установлена вtrue
.
Затем вы вызываете метод each()
из объекта подключения db
с двумя аргументами. Первый аргумент – это SQL-запрос select * from migration
, который извлекает строки по одной из базы данных. Второй аргумент – это обратный вызов, вызываемый каждый раз, когда из базы данных извлекается строка. Обратный вызов принимает два параметра: объект error
и объект row
, содержащий данные, извлеченные из одной строки в базе данных. Внутри обратного вызова вы проверяете, установлен ли объект error
в операторе if
. Если условие оценивается как true
, сообщение об ошибке записывается в консоль с помощью метода console.log()
. Если ошибки нет, вы вызываете метод write()
на stringifier
, который записывает данные в поток преобразования stringifier
.
Когда метод each()
завершает итерацию, метод pipe()
на потоке stringifier
начинает отправлять данные порциями и записывать их в writableStream
. Поток для записи будет сохранять каждую порцию данных в файле saved_from_db.csv
. После того как все данные будут записаны в файл, console.log()
выведет сообщение об успешном выполнении.
Теперь файл будет выглядеть следующим образом:
Сохраните и закройте файл, а затем выполните файл writeCSV.js
в терминале:
Вы получите следующий вывод:
OutputFinished writing data
Чтобы подтвердить, что данные были записаны, проверьте содержимое файла, используя команду cat
:
cat
вернет все строки, записанные в файле (отредактировано для краткости):
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,
...
Теперь вы можете извлекать данные из базы данных и записывать каждую строку в файл CSV, используя потоки.
Вывод
В этой статье вы читали файл CSV и вставляли его данные в базу данных, используя модули node-csv
и node-sqlite3
. Затем вы извлекали данные из базы данных и записывали их в другой файл CSV.
Теперь вы можете читать и записывать файлы CSV. В качестве следующего шага вы можете работать с большими наборами данных CSV, используя ту же реализацию с потоками, эффективными по памяти, или вы можете изучить пакет типа event-stream
, который делает работу с потоками намного проще.
Чтобы узнать больше о node-csv
, посетите их документацию Проект CSV – пакет CSV для Node.js. Чтобы узнать больше о node-sqlite3
, посетите их документацию на Github. Чтобы продолжить развивать свои навыки в Node.js, ознакомьтесь с серией How To Code in Node.js.