著者は、女性技術者協会(Society of Women Engineers)を、寄付のための執筆プログラムの一環として選択しました。
導入
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
モジュールを使用して、Node.jsストリームを介してCSVファイルを読み取ります。これにより、大規模なデータセットをメモリを大量に消費することなく読み取ることができます。プログラムを変更して、CSVファイルから解析されたデータをSQLiteデータベースに移動します。また、データベースからデータを取得し、node-csv
で解析し、Node.jsストリームを使用してデータをチャンクごとにCSVファイルに書き込みます。
DigitalOcean App Platformを使用してGitHubからNodeアプリケーションをデプロイします。DigitalOceanにアプリのスケーリングを任せましょう。
前提条件
このチュートリアルには、以下が必要です:
-
ローカルまたはサーバー環境にNode.jsがインストールされていること。Node.jsのインストールとローカル開発環境の作成に従ってNode.jsをインストールしてください。
-
SQLiteをローカルまたはサーバー環境にインストールしてください。これは、Ubuntu 20.04でSQLiteをインストールして使用する方法の手順1に従ってインストールできます。SQLiteの使用方法を学ぶためには、インストールガイドの手順2〜7を参照してください。
-
Node.jsプログラムの作成に精通してください。最初のプログラムを作成して実行する方法については、Node.jsで最初のプログラムを書いて実行する方法を参照してください。
-
Node.jsのストリームに精通してください。ファイルをストリームを使用して操作する方法については、Node.jsでストリームを使用してファイルを操作する方法を参照してください。
ステップ1 — プロジェクトディレクトリの設定
このセクションでは、プロジェクトディレクトリを作成し、アプリケーション用のパッケージをダウンロードします。また、ニュージーランドの国際移民データを含む CSV データセットを Stats NZ からダウンロードします。
まず、csv_demo
というディレクトリを作成し、そのディレクトリに移動します:
次に、npm init
コマンドを使用して、ディレクトリを npm プロジェクトとして初期化します:
-y
オプションは、すべてのプロンプトに「はい」と答えるように npm init
に通知します。このコマンドは、いつでも変更できるデフォルト値を持つ package.json
を作成します。
npm プロジェクトとしてディレクトリが初期化されたら、必要な依存関係をインストールできます: node-csv
と node-sqlite3
。
node-csv
をインストールするには、次のコマンドを入力します:
node-csv
モジュールは、CSVファイルのデータを解析および書き込みするためのモジュールのコレクションです。以下のコマンドは、node-csv
パッケージの一部である4つのモジュールをすべてインストールします:csv-generate
、csv-parse
、csv-stringify
、およびstream-transform
。CSVファイルを解析するためにcsv-parse
モジュールを使用し、データをCSVファイルに書き込むためにcsv-stringify
モジュールを使用します。
次に、node-sqlite3
モジュールをインストールしてください:
node-sqlite3
モジュールを使用すると、アプリがSQLiteデータベースとやり取りできます。
プロジェクトにパッケージをインストールした後、wget
コマンドを使用してニュージーランドの移住CSVファイルをダウンロードしてください:
ダウンロードした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
...
最初の行には列名が含まれており、すべての後続の行には各列に対応するデータが含まれています。データの各部分はコンマで区切られています。この文字をデリミタと呼びます。ファイルを解析するために大部分のモジュールがデリミタを必要とするので、ファイルでどのデリミタが使用されているかを知る必要があります。制限されることはありません。他の一般的なデリミタには、コロン(:
)、セミコロン(;
)、およびタブ(\t
)が含まれます。
ファイルを確認し、区切り文字を識別した後、migration_data.csv
ファイルを終了するには、CTRL+X
を使用します。
必要な依存関係をインストールしました。次のセクションでは、CSV ファイルを読み込みます。
ステップ 2 — CSV ファイルの読み込み
このセクションでは、node-csv
を使用して CSV ファイルを読み込み、その内容をコンソールにログに出力します。 fs
モジュールの createReadStream()
メソッドを使用して CSV ファイルからデータを読み取り、読み取り可能なストリームを作成します。 次に、データのチャンクを解析する csv-parse
モジュールで初期化された別のストリームにストリームをパイプします。 データのチャンクが解析されたら、それらをコンソールにログに出力できます。
好みのエディタで readCSV.js
ファイルを作成し、開きます:
readCSV.js
ファイルに、次の行を追加して fs
および csv-parse
モジュールをインポートします:
最初の行では、fs
変数を定義し、Node.js の require()
メソッドがモジュールをインポートしたときに返す fs
オブジェクトを代入します。
第二行では、require()
メソッドから返されたオブジェクトからparse
メソッドをdestructuring syntax
を使用してparse
変数に抽出します。
次の行を追加して、CSVファイルを読み取ります:
fs
モジュールのcreateReadStream()
メソッドは、ここでmigration_data.csv
というファイル名の引数を受け取ります。次に、大きなファイルを取り、それを小さなチャンクに分割する読み取り可能なstreamを作成します。読み取り可能なストリームでは、データを読み取るだけで書き込むことはできません。
読み取り可能なストリームを作成した後、Nodeのpipe()
メソッドは、読み取り可能なストリームからデータのチャンクを別のストリームに転送します。2番目のストリームは、pipe()
メソッドの内部でcsv-parse
モジュールのparse()
メソッドが呼び出されるときに作成されます。csv-parse
モジュールは、データのチャンクを取り、別の形式に変換するトランスフォームストリーム(読み取り可能で書き込み可能なストリーム)を実装しています。たとえば、2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,344
のようなチャンクを受け取ると、parse()
メソッドはそれを配列に変換します。
parse()
メソッドは、プロパティを受け入れるオブジェクトを取ります。その後、メソッドが解析するデータに関する追加の情報を設定および提供します。オブジェクトは次のプロパティを受け入れます:
-
delimiter
は、行内の各フィールドを区切る文字を定義します。値,
は、カンマがフィールドを区切ることを解析器に伝えます。 -
from_line
は、解析器が行を解析し始める行を定義します。値2
では、解析器は1行目をスキップして2行目から開始します。後でデータをデータベースに挿入するため、このプロパティはデータベースの最初の行に列名を挿入しないようにします。
次に、Node.jsのon()
メソッドを使用してストリーミングイベントをアタッチします。ストリーミングイベントは、特定のイベントが発生した場合にメソッドがデータのチャンクを消費できるようにします。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-parse
変換ストリームを使用して配列に変換されました。ストリームからチャンクが受信されるたびにログが記録されるため、データは一度にすべて表示されるのではなく、ダウンロードされているかのように表示されます。
このステップでは、CSVファイル内のデータを読み取り、配列に変換しました。次に、データベースにCSVファイルからデータを挿入します。
ステップ3 — データをデータベースに挿入する
Node.jsを使用してCSVファイルからデータをデータベースに挿入すると、データを挿入する前に処理、クリーニング、またはデータの強化に使用できる豊富なモジュールライブラリにアクセスできます。
このセクションでは、node-sqlite3
モジュールを使用してSQLiteデータベースとの接続を確立します。次に、データベース内のテーブルを作成し、readCSV.js
ファイルをコピーして、CSVファイルから読み取ったすべてのデータをデータベースに挿入するように修正します。
エディターでdb.js
ファイルを作成し、開きます:
db.js
ファイルに、次の行を追加してfs
とnode-sqlite3
モジュールをインポートします:
3行目では、SQLiteデータベースのパスを定義し、変数filepath
に格納します。データベースファイルはまだ存在しませんが、node-sqlite3
がデータベースとの接続を確立するために必要です。
同じファイルに、次の行を追加してNode.jsをSQLiteデータベースに接続します:
ここでは、connectToDatabase()
という関数を定義して、データベースへの接続を確立しています。この関数内では、if
文でfs
モジュールの existsSync()
メソッドを呼び出し、プロジェクトディレクトリ内にデータベースファイルが存在するかどうかをチェックしています。もしif
の条件がtrue
に評価される場合は、SQLite の Database()
クラスをnode-sqlite3
モジュールでデータベースファイルパスとともにインスタンス化します。接続が確立されると、関数は接続オブジェクトを返して終了します。
ただし、if
文がfalse
に評価される場合(データベースファイルが存在しない場合)、実行はelse
ブロックに移ります。else
ブロックでは、2 つの引数を指定して Database()
クラスをインスタンス化します。これらの引数は、SQLite データベースファイルのパスとコールバックです。
最初の引数は SQLite データベースファイルのパスであり、./population.db
です。2 番目の引数は、データベースとの接続が正常に確立された場合、またはエラーが発生した場合に自動的に呼び出されるコールバックです。コールバックは error
オブジェクトをパラメータとして受け取ります。このオブジェクトは、接続が成功した場合には null
です。コールバック内では、if
文で error
オブジェクトが設定されているかどうかをチェックします。もしtrue
に評価される場合は、エラーメッセージをログに記録して関数を返します。false
に評価される場合は、接続が確立されたことを確認する成功メッセージをログに記録します。
現在、if
およびelse
ブロックが接続オブジェクトを確立します。 else
ブロックでDatabase
クラスを呼び出すときにコールバックを渡し、データベース内にテーブルを作成しますが、データベースファイルが存在しない場合にのみ行われます。 データベースファイルがすでに存在する場合、関数はif
ブロックを実行し、データベースと接続し、接続オブジェクトを返します。
データベースファイルが存在しない場合にテーブルを作成するには、次のコードを追加してください:
これで、connectToDatabase()
がcreateTable()
関数を呼び出し、db
変数に格納された接続オブジェクトを引数として受け取ります。
connectToDatabase()
関数の外側で、createTable()
関数を定義し、接続オブジェクトdb
をパラメーターとして受け取ります。 db
接続オブジェクトでexec()
メソッドを呼び出し、SQLステートメントを引数として渡します。 SQLステートメントは、7つの列を持つmigration
という名前のテーブルを作成します。 列名はmigration_data.csv
ファイルの見出しと一致します。
最後に、connectToDatabase()
関数を呼び出し、関数によって返された接続オブジェクトをエクスポートして、他のファイルで再利用できるようにします。
db.js
ファイルを保存して終了します。
データベース接続が確立されたので、csv-parse
モジュールによって解析された行をデータベースに挿入するためにreadCSV.js
ファイルをコピーして変更します。
次のコマンドでファイルをコピーして名前を変更します:insertData.js
エディターでinsertData.js
ファイルを開きます:
ハイライトされたコードを追加します:
3行目で、db.js
ファイルから接続オブジェクトをインポートし、変数db
に格納します。
fs
モジュールストリームにアタッチされたdata
イベントコールバック内で、接続オブジェクトにserialize()
メソッドを呼び出します。このメソッドは、SQLステートメントが実行を終えるまで次のステートメントの実行を待機させるため、データベースの競合操作を防ぐのに役立ちます。
serialize()
メソッドはコールバックを取ります。このコールバック内で、db
接続オブジェクトにrun
メソッドを呼び出します。このメソッドは3つの引数を受け取ります:
-
最初の引数はSQLiteデータベースで実行されるSQLステートメントです。
run()
メソッドは結果を返さないSQLステートメントのみを受け付けます。INSERT INTO migration VALUES (?, ..., ?
ステートメントは、migration
テーブルに行を挿入し、?
は後でrun()
メソッドの2番目の引数で置換されるプレースホルダです。 -
第2引数は配列
[row[0], ... row[5], row[6]]
です。前のセクションでは、parse()
メソッドが読み込み可能なストリームからデータの塊を受け取り、配列に変換します。データが配列として受信されるため、各フィールド値を取得するには、[row[1], ..., row[6]]
などのように配列インデックスを使用する必要があります。 -
第3引数は、データが挿入された場合またはエラーが発生した場合に実行されるコールバックです。 コールバックはエラーが発生したかどうかを確認し、エラーメッセージをログに記録します。 エラーがない場合、関数は
console.log()
メソッドを使用してコンソールに成功メッセージをログに記録し、行が挿入されたこととIDが示されます。
最後に、ファイルからend
とerror
イベントを削除します。 node-sqlite3
メソッドの非同期性のため、end
とerror
イベントはデータがデータベースに挿入される前に実行されるため、これらはもはや必要ありません。
ファイルを保存して終了します。
node
を使用してinsertData.js
ファイルを実行します:
システムによっては、時間がかかる場合がありますが、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
特にidを含むメッセージは、CSVファイルからの行がデータベースに保存されたことを証明しています。
今、CSVファイルを読み取り、その内容をデータベースに挿入できます。次に、CSVファイルを書き込みます。
ステップ4 — CSVファイルの書き込み
このセクションでは、データベースからデータを取得し、ストリームを使用してCSVファイルに書き込みます。
エディタでwriteCSV.js
を作成して開きます:
writeCSV.js
ファイルで、以下の行を追加してfs
モジュールとcsv-stringify
モジュール、およびdb.js
からのデータベース接続オブジェクトをインポートします:
csv-stringify
モジュールは、オブジェクトや配列からデータをCSVテキスト形式に変換します。
次に、以下の行を追加して、データを書き込むCSVファイルの名前を含む変数と、データを書き込む書き込み可能なストリームを定義します:
createWriteStream
メソッドは、データを書き込むファイルのファイル名を引数に取ります。これは filename
変数に格納されている saved_from_db.csv
ファイル名です。
4行目では、columns
変数を定義し、CSVデータのヘッダー名を含む配列を格納します。これらのヘッダーは、ファイルにデータを書き込み始めるときにCSVファイルの最初の行に書き込まれます。
依然として、writeCSV.js
ファイルで、次の行を追加してデータベースからデータを取得し、CSVファイルの各行を書き込みます:
まず、オブジェクトを引数として渡して stringify
メソッドを呼び出します。これにより、変換ストリームが作成されます。変換ストリームは、データをオブジェクトからCSVテキストに変換します。 stringify()
メソッドに渡されるオブジェクトには2つのプロパティがあります:
header
はブール値を受け取り、ブール値がtrue
に設定されている場合はヘッダーを生成します。columns
は、header
オプションがtrue
に設定されている場合に、CSVファイルの最初の行に書き込まれる列名を含む配列を取ります。
次に、db
接続オブジェクトからeach()
メソッドを2つの引数で呼び出します。最初の引数はSQLステートメントselect * from migration
であり、データベースから1行ずつ行を取得します。2番目の引数は、データベースから行が取得されるたびに呼び出されるコールバックです。コールバックは2つのパラメータを取ります:エラーオブジェクトとデータベースの単一の行から取得されたrow
オブジェクト。コールバック内で、error
オブジェクトがif
ステートメントで設定されているかどうかをチェックします。条件がtrue
に評価される場合、console.log()
メソッドを使用してコンソールにエラーメッセージをログに記録します。エラーがない場合は、stringifier
上でwrite()
メソッドを呼び出し、データをstringifier
変換ストリームに書き込みます。
each()
メソッドの反復が完了すると、stringifier
ストリーム上のpipe()
メソッドがデータをチャンクで送信し、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ファイルに書き込むことができます。
結論
この記事では、node-csv
とnode-sqlite3
モジュールを使用してCSVファイルを読み取り、そのデータをデータベースに挿入しました。その後、データベースからデータを取得して別のCSVファイルに書き込みました。
CSVファイルの読み書きができるようになりました。次のステップとしては、メモリ効率の良いストリームを使用して大規模なCSVデータセットを扱うか、またはevent-stream
のようなパッケージを検討して、ストリームの操作を簡素化することができます。
node-csv
についてさらに詳しく知りたい場合は、彼らのドキュメントCSVプロジェクト – Node.js CSVパッケージをご覧ください。また、node-sqlite3
について詳しく知りたい場合は、Githubのドキュメントをご覧ください。Node.jsのスキルをさらに向上させるには、How To Code in Node.jsシリーズを参照してください。