Node-CSVを使用してNode.jsでCSVファイルを読み書きする方法

提供:Dev Guides
移動先:案内検索

著者は、 Society of Women Engineers を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

CSVは、表形式のデータを保存するためのプレーンテキストファイル形式です。 CSVファイルは、コンマ区切り文字を使用してテーブルセルの値を区切り、新しい行で行の開始位置と終了位置を示します。 ほとんどのスプレッドシートプログラムとデータベースは、CSVファイルをエクスポートおよびインポートできます。 CSVはプレーンテキストファイルであるため、どのプログラミング言語でもCSVファイルを解析して書き込むことができます。 Node.js には、 node-csvfast-csvpapaparse など、CSVファイルを処理できる多くのモジュールがあります。

このチュートリアルでは、node-csvモジュールを使用して、Node.jsストリームを使用してCSVファイルを読み取ります。これにより、大量のメモリを消費することなく、大きなデータセットを読み取ることができます。 プログラムを変更して、解析されたデータをCSVファイルからSQLiteデータベースに移動します。 また、データベースからデータを取得し、node-csvで解析し、Node.jsストリームを使用してCSVファイルにチャンクで書き込みます。

前提条件

このチュートリアルに従うには、次のものが必要です。

  • ローカル環境またはサーバー環境にインストールされたNode.js。 Node.jsのインストール方法とローカル開発環境の作成に従って、Node.jsをインストールします。
  • ローカル環境またはサーバー環境にインストールされたSQLite。Ubuntu20.04にSQLiteをインストールして使用する方法の手順1に従ってインストールできます。 SQLiteの使用方法に関する知識は役に立ち、インストールガイドのステップ2〜7で学ぶことができます。
  • Node.jsプログラムの作成に精通していること。 Node.jsで最初のプログラムを作成して実行する方法を参照してください。
  • Node.jsストリームに精通していること。 Node.jsでストリームを使用してファイルを操作する方法を参照してください。

ステップ1—プロジェクトディレクトリの設定

このセクションでは、プロジェクトディレクトリを作成し、アプリケーションのパッケージをダウンロードします。 また、 Stats NZ からCSVデータセットをダウンロードします。これには、ニュージーランドの国際的な移行データが含まれています。

開始するには、csv_demoというディレクトリを作成し、次のディレクトリに移動します。

mkdir csv_demo
cd csv_demo

次に、npm initコマンドを使用して、ディレクトリをnpmプロジェクトとして初期化します。

npm init -y

-yオプションは、npm initに、すべてのプロンプトに「はい」と言うように通知します。 このコマンドは、いつでも変更できるデフォルト値でpackage.jsonを作成します。

ディレクトリがnpmプロジェクトとして初期化されたので、必要な依存関係node-csvおよびnode-sqlite3をインストールできます。

次のコマンドを入力して、node-csvをインストールします。

npm install csv

node-csvモジュールは、データを解析してCSVファイルに書き込むことができるモジュールのコレクションです。 このコマンドは、node-csvパッケージの一部であるcsv-generatecsv-parsecsv-stringify、およびstream-transformの4つのモジュールすべてをインストールします。 csv-parseモジュールを使用してCSVファイルを解析し、csv-stringifyモジュールを使用してCSVファイルにデータを書き込みます。

次に、node-sqlite3モジュールをインストールします。

npm install sqlite3

node-sqlite3モジュールを使用すると、アプリでSQLiteデータベースを操作できます。

プロジェクトにパッケージをインストールした後、wgetコマンドを使用してニュージーランド移行CSVファイルをダウンロードします。

wget https://www.stats.govt.nz/assets/Uploads/International-migration/International-migration-September-2021-Infoshare-tables/Download-data/international-migration-September-2021-estimated-migration-by-age-and-sex-csv.csv

ダウンロードしたCSVファイルには長い名前が付いています。 作業を簡単にするために、mvコマンドを使用してファイル名を短い名前に変更します。

 mv international-migration-September-2021-estimated-migration-by-age-and-sex-csv.csv migration_data.csv

新しいCSVファイル名migration_data.csvは短く、操作が簡単です。

nanoまたはお気に入りのテキストエディタを使用して、ファイルを開きます。

nano migration_data.csv

開くと、次のようなコンテンツが表示されます。

demo_csv / migration_data.csv

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)があります。 ほとんどのモジュールではファイルの解析に区切り文字が必要なため、ファイルで使用されている区切り文字を知る必要があります。

ファイルを確認して区切り文字を特定したら、CTRL+Xを使用してmigration_data.csvファイルを終了します。

これで、プロジェクトに必要な依存関係がインストールされました。 次のセクションでは、CSVファイルを読みます。

ステップ2—CSVファイルの読み取り

このセクションでは、node-csvを使用してCSVファイルを読み取り、その内容をコンソールに記録します。 fsモジュールのcreateReadStream()メソッドを使用して、CSVファイルからデータを読み取り、読み取り可能なストリームを作成します。 次に、csv-parseモジュールで初期化された別のストリームにストリームをパイプ処理して、データのチャンクを解析します。 データのチャンクが解析されたら、コンソールに記録できます。

お好みのエディタでreadCSV.jsファイルを作成して開きます。

nano readCSV.js

readCSV.jsファイルで、次の行を追加してfsおよびcsv-parseモジュールをインポートします。

demo_csv / readCSV.js

const fs = require("fs");
const { parse } = require("csv-parse");

最初の行では、fs変数を定義し、Node.jsrequire()メソッドがモジュールをインポートするときに返すfsオブジェクトを割り当てます。

2行目では、破壊構文を使用して、require()メソッドによって返されたオブジェクトから[X11X]変数にparseメソッドを抽出します。

次の行を追加して、CSVファイルを読み取ります。

demo_csv / readCSV.js

...
fs.createReadStream("./migration_data.csv")
  .pipe(parse({ delimiter: ",", from_line: 2 }))
  .on("data", function (row) {
    console.log(row);
  })

fsモジュールのcreateReadStream()メソッドは、読み取りたいファイル名の引数(ここではmigration_data.csv)を受け入れます。 次に、読み取り可能な stream を作成します。これにより、大きなファイルが取得され、小さなチャンクに分割されます。 読み取り可能なストリームを使用すると、ストリームからのデータの読み取りのみが可能になり、書き込みはできなくなります。

読み取り可能なストリームを作成した後、Nodeのpipe()メソッドは、読み取り可能なストリームから別のストリームにデータのチャンクを転送します。 2番目のストリームは、csv-parseモジュールのparse()メソッドがpipe()メソッド内で呼び出されたときに作成されます。 csv-parseモジュールは、変換ストリーム(読み取りおよび書き込み可能なストリーム)を実装し、データチャンクを取得して別の形式に変換します。 たとえば、2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,344のようなチャンクを受信すると、parse()メソッドはそれを配列に変換します。

parse()メソッドは、プロパティを受け入れるオブジェクトを受け取ります。 次に、オブジェクトは、メソッドが解析するデータに関する詳細情報を構成および提供します。 オブジェクトは次のプロパティを取ります。

  • delimiterは、行の各フィールドを区切る文字を定義します。 値,は、コンマでフィールドを区切ることをパーサーに通知します。
  • from_lineは、パーサーが行の解析を開始する行を定義します。 値が2の場合、パーサーは1行目をスキップし、2行目から開始します。 後でデータベースにデータを挿入するため、このプロパティは、データベースの最初の行に列名を挿入しないようにするのに役立ちます。

次に、Node.json()メソッドを使用してストリーミングイベントをアタッチします。 ストリーミングイベントを使用すると、特定のイベントが発生した場合にメソッドがデータのチャンクを消費できます。 dataイベントは、parse()メソッドから変換されたデータを使用する準備ができたときにトリガーされます。 データにアクセスするには、on()メソッドにコールバックを渡します。このメソッドは、rowという名前のパラメーターを取ります。 rowパラメーターは、配列に変換されたデータチャンクです。 コールバック内で、console.log()メソッドを使用してコンソールにデータを記録します。

ファイルを実行する前に、ストリームイベントをさらに追加します。 これらのストリームイベントはエラーを処理し、CSVファイル内のすべてのデータが消費されたときにコンソールに成功メッセージを書き込みます。

readCSV.jsファイルに、強調表示されたコードを追加します。

demo_csv / readCSV.js

...
fs.createReadStream("./migration_data.csv")
  .pipe(parse({ delimiter: ",", from_line: 2 }))
  .on("data", function (row) {
    console.log(row);
  })
  .on("end", function () {
    console.log("finished");
  })
  .on("error", function (error) {
    console.log(error.message);
  });

endイベントは、CSVファイル内のすべてのデータが読み取られたときに発行されます。 これが発生すると、コールバックが呼び出され、終了したことを示すメッセージがログに記録されます。

CSVデータの読み取りと解析中にエラーが発生すると、errorイベントが発行され、コールバックが呼び出され、コンソールにエラーメッセージが記録されます。

これで、完全なファイルは次のようになります。

demo_csv / readCSV.js

const fs = require("fs");
const { parse } = require("csv-parse");

fs.createReadStream("./migration_data.csv")
  .pipe(parse({ delimiter: ",", from_line: 2 }))
  .on("data", function (row) {
    console.log(row);
  })
  .on("end", function () {
    console.log("finished");
  })
  .on("error", function (error) {
    console.log(error.message);
  });

CTRL+Xを使用して、readCSV.jsファイルを保存して終了します。

次に、nodeコマンドを使用してファイルを実行します。

node readCSV.js

出力は次のようになります(簡潔にするために編集)。

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—データベースへのデータの挿入

Node.jsを使用してCSVファイルからデータベースにデータを挿入すると、データベースに挿入する前にデータを処理、クリーンアップ、または拡張するために使用できるモジュールの膨大なライブラリにアクセスできます。

このセクションでは、node-sqlite3モジュールを使用してSQLiteデータベースとの接続を確立します。 次に、データベースにテーブルを作成し、readCSV.jsファイルをコピーして、CSVファイルから読み取ったすべてのデータをデータベースに挿入するように変更します。

エディターでdb.jsファイルを作成して開きます。

nano db.js

db.jsファイルに、次の行を追加して、fsおよびnode-sqlite3モジュールをインポートします。

demo_csv / db.js

const fs = require("fs");
const sqlite3 = require("sqlite3").verbose();
const filepath = "./population.db";
...

3行目では、SQLiteデータベースのパスを定義し、それを変数filepathに格納します。 データベースファイルはまだ存在していませんが、node-sqlite3がデータベースとの接続を確立するために必要になります。

同じファイルに、次の行を追加してNode.jsをSQLiteデータベースに接続します。

demo_csv / db.js

...
function connectToDatabase() {
  if (fs.existsSync(filepath)) {
    return new sqlite3.Database(filepath);
  } else {
    const db = new sqlite3.Database(filepath, (error) => {
      if (error) {
        return console.error(error.message);
      }
      console.log("Connected to the database successfully");
    });
    return db;
  }
}

ここでは、connectToDatabase()という名前の関数を定義して、データベースへの接続を確立します。 関数内で、fsモジュールのexistsSync()メソッドをifステートメントで呼び出します。このメソッドは、データベースファイルがプロジェクトディレクトリに存在するかどうかを確認します。 if条件がtrueと評価された場合、データベースファイルパスを使用してnode-sqlite3モジュールのSQLiteのDatabase()クラスをインスタンス化します。 接続が確立されると、関数は接続オブジェクトを返し、終了します。

ただし、ifステートメントがfalseと評価された場合(データベースファイルが存在しない場合)、実行はelseブロックにスキップされます。 elseブロックで、データベースファイルパスとコールバックの2つの引数を使用してDatabase()クラスをインスタンス化します。

最初の引数は、SQLiteデータベースファイルのパスである./population.dbです。 2番目の引数は、データベースとの接続が正常に確立されたとき、またはエラーが発生したときに自動的に呼び出されるコールバックです。 コールバックは、errorオブジェクトをパラメーターとして受け取ります。これは、接続が成功した場合はnullです。 コールバック内で、ifステートメントは、errorオブジェクトが設定されているかどうかを確認します。 trueと評価された場合、コールバックはエラーメッセージをログに記録して戻ります。 falseと評価された場合は、接続が確立されたことを確認する成功メッセージをログに記録します。

現在、ifおよびelseブロックは接続オブジェクトを確立します。 elseブロックのDatabaseクラスを呼び出してデータベースにテーブルを作成するときにコールバックを渡しますが、データベースファイルが存在しない場合に限ります。 データベースファイルが既に存在する場合、関数はifブロックを実行し、データベースに接続して、接続オブジェクトを返します。

データベースファイルが存在しない場合にテーブルを作成するには、強調表示されたコードを追加します。

demo_csv / db.js

const fs = require("fs");
const sqlite3 = require("sqlite3").verbose();
const filepath = "./population.db";

function connectToDatabase() {
  if (fs.existsSync(filepath)) {
    return new sqlite3.Database(filepath);
  } else {
    const db = new sqlite3.Database(filepath, (error) => {
      if (error) {
        return console.error(error.message);
      }
      createTable(db);
      console.log("Connected to the database successfully");
    });
    return db;
  }
}

function createTable(db) {
  db.exec(`
  CREATE TABLE migration
  (
    year_month       VARCHAR(10),
    month_of_release VARCHAR(10),
    passenger_type   VARCHAR(50),
    direction        VARCHAR(20),
    sex              VARCHAR(10),
    age              VARCHAR(50),
    estimate         INT
  )
`);
}

module.exports = connectToDatabase();

これで、connectToDatabase()createTable()関数を呼び出します。この関数は、db変数に格納されている接続オブジェクトを引数として受け入れます。

connectToDatabase()関数の外で、接続オブジェクトdbをパラメーターとして受け入れるcreateTable()関数を定義します。 SQLステートメントを引数として取るdb接続オブジェクトでexec()メソッドを呼び出します。 SQLステートメントは、7列のmigrationという名前のテーブルを作成します。 列名は、migration_data.csvファイルの見出しと一致します。

最後に、connectToDatabase()関数を呼び出し、関数によって返された接続オブジェクトをエクスポートして、他のファイルで再利用できるようにします。

db.jsファイルを保存して終了します。

データベース接続が確立されたら、readCSV.jsファイルをコピーして変更し、csv-parseモジュールが解析した行をデータベースに挿入します。

次のコマンドを使用して、ファイルをコピーしてinsertData.jsに名前を変更します。

cp readCSV.js insertData.js

エディタでinsertData.jsファイルを開きます。

nano insertData.js

強調表示されたコードを追加します。

demo_csv / insertData.js

const fs = require("fs");
const { parse } = require("csv-parse");
const db = require("./db");

fs.createReadStream("./migration_data.csv")
  .pipe(parse({ delimiter: ",", from_line: 2 }))
  .on("data", function (row) {
    db.serialize(function () {
      db.run(
        `INSERT INTO migration VALUES (?, ?, ? , ?, ?, ?, ?)`,
        [row[0], row[1], row[2], row[3], row[4], row[5], row[6]],
        function (error) {
          if (error) {
            return console.log(error.message);
          }
          console.log(`Inserted a row with the id: ${this.lastID}`);
        }
      );
    });
  });

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 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を作成して開きます。

nano writeCSV.js

writeCSV.jsファイルに次の行を追加して、fsおよびcsv-stringifyモジュールとデータベース接続オブジェクトをdb.jsからインポートします。

demo_csv / writeCSV.js

const fs = require("fs");
const { stringify } = require("csv-stringify");
const db = require("./db");

csv-stringifyモジュールは、オブジェクトまたは配列からのデータをCSVテキスト形式に変換します。

次に、次の行を追加して、データを書き込むCSVファイルの名前とデータを書き込む書き込み可能なストリームを含む変数を定義します。

demo_csv / writeCSV.js

...
const filename = "saved_from_db.csv";
const writableStream = fs.createWriteStream(filename);

const columns = [
  "year_month",
  "month_of_release",
  "passenger_type",
  "direction",
  "sex",
  "age",
  "estimate",
];

createWriteStreamメソッドは、データのストリームを書き込むファイル名の引数を取ります。これは、filename変数に格納されているsaved_from_db.csvファイル名です。

4行目では、columns変数を定義します。この変数は、CSVデータのヘッダーの名前を含む配列を格納します。 これらのヘッダーは、ファイルへのデータの書き込みを開始すると、CSVファイルの最初の行に書き込まれます。

writeCSV.jsファイルに、次の行を追加してデータベースからデータを取得し、CSVファイルに各行を書き込みます。

demo_csv / writeCSV.js

...
const stringifier = stringify({ header: true, columns: columns });
db.each(`select * from migration`, (error, row) => {
  if (error) {
    return console.log(error.message);
  }
  stringifier.write(row);
});
stringifier.pipe(writableStream);
console.log("Finished writing data");

まず、オブジェクトを引数としてstringifyメソッドを呼び出し、変換ストリームを作成します。 変換ストリームは、オブジェクトからのデータをCSVテキストに変換します。 stringify()メソッドに渡されるオブジェクトには、次の2つのプロパティがあります。

  • headerはブール値を受け入れ、ブール値がtrueに設定されている場合、ヘッダーを生成します。
  • columnsは、headerオプションがtrueに設定されている場合に、CSVファイルの最初の行に書き込まれる列の名前を含む配列を取ります。

次に、2つの引数を使用してdb接続オブジェクトからeach()メソッドを呼び出します。 最初の引数は、データベース内の行を1つずつ取得するSQLステートメントselect * from migrationです。 2番目の引数は、データベースから行が取得されるたびに呼び出されるコールバックです。 コールバックは2つのパラメーターを取ります。データベースの単一の行から取得されたデータを含むerrorオブジェクトとrowオブジェクトです。 コールバック内で、errorオブジェクトがifステートメントで設定されているかどうかを確認します。 状態がtrueと評価された場合、console.log()メソッドを使用してエラーメッセージがコンソールに記録されます。 エラーがない場合は、stringifierwrite()メソッドを呼び出します。これにより、データがstringifier変換ストリームに書き込まれます。

each()メソッドの反復が終了すると、stringifierストリームのpipe()メソッドは、データをチャンクで送信し、writableStreamに書き込み始めます。 書き込み可能なストリームは、データの各チャンクをsaved_from_db.csvファイルに保存します。 すべてのデータがファイルに書き込まれると、console.log()は成功メッセージをログに記録します。

完全なファイルは次のようになります。

demo_csv / writeCSV.js

const fs = require("fs");
const { stringify } = require("csv-stringify");
const db = require("./db");
const filename = "saved_from_db.csv";
const writableStream = fs.createWriteStream(filename);

const columns = [
  "year_month",
  "month_of_release",
  "passenger_type",
  "direction",
  "sex",
  "age",
  "estimate",
];

const stringifier = stringify({ header: true, columns: columns });
db.each(`select * from migration`, (error, row) => {
  if (error) {
    return console.log(error.message);
  }
  stringifier.write(row);
});
stringifier.pipe(writableStream);
console.log("Finished writing data");

ファイルを保存して閉じてから、ターミナルでwriteCSV.jsファイルを実行します。

node writeCSV.js

次の出力が表示されます。

OutputFinished writing data

データが書き込まれたことを確認するには、catコマンドを使用してファイルの内容を調べます。

cat saved_from_db.csv

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 Project-Node.js CSVpackageにアクセスしてください。 node-sqlite3の詳細については、Githubドキュメントにアクセスしてください。 Node.jsスキルを継続的に向上させるには、Node.jsシリーズのコーディング方法を参照してください。