CSVファイルを取り込みたい

Google SpreadsheetにはCSVデータの取り込み機能が備わっていますが、1つ2つのCSVデータの取り込みならば標準で備わっている読み込みの機能を使用すれば良いでしょう。しかし、定期的にドライブにあるCSVのファイルを読み込むであったり、大量にあるCSVデータを一気に取り込むなどはスクリプトで作成しなければなりません。Google DriveにあるCSVファイルを取り込むためには、結構面倒な様々なAPIを利用しなければいけませんので、敷居は高いですが、既に多くの方が挑戦し、利用できるスクリプトがあります。

今回は、そんなスクリプトをベースにしつつ、自分にとって使いやすい形で少々いじったものを使ってみました。

今回想定しているケースですが以下の通りです。

  1. 特定のフォルダ内にCSVファイルがずらーっと入っている(およそ80個くらい)。
  2. それら全てを一括で取り込んで一括で書き込みをする。
  3. CSVファイルの文字コードはShift_JISである。
  4. 今回はCSV取り込みに当って、Googleにあるチュートリアルのコードを修正して使ってます。

ソースコード

//CSVインポートする為のメインの関数

function importFromCSV() {
  
  //事前準備
  var sheet = SpreadsheetApp.getActiveSpreadsheet();

//シート上に記述してあるCSVが入っているフォルダのIDを取得

  var csvFolderkey = sheet.getSheetByName('startup').getRange('A11').getValue();
  
  if(csvFolderkey == ""){
     Browser.msgBox("CSVファイルの解凍先フォルダのIDが記入されていません");
     return 0;
  }
  
  //特定のフォルダ内のCSVファイルを取得
  var files = DriveApp.searchFiles("'"+csvFolderkey+"' in parents");
  var csvFile = "";
  
  while (files.hasNext()) {
    //Shift_JISにてCSVをそのままで取り込む
    csvFile = csvFile + files.next().getBlob().getDataAsString("Shift_JIS"); 
  }
  
  //CSVToArrayにて成形してシートに記述(書き込み先シートはtestという名称)
  var csvData = CSVToArray(csvFile, ",");
  var sheetman = sheet.getSheetByName('test');
  

//CSVToArrayで配列となったデータをsetValuesで一気に書き込み

  for (var i = 0; i < csvData.length; i++) {
    sheetman.getRange(i+1, 1, 1, csvData[i].length).setValues(new Array(csvData[i]));
  }
  
  Browser.msgBox("CSVデータのインポートが完了しました。");
}
//CSVデータを整形して配列で返す関数(特に変更していません)
function CSVToArray( strData, strDelimiter ){
  // Check to see if the delimiter is defined. If not,
  // then default to comma.
  strDelimiter = (strDelimiter || ",");
  // Create a regular expression to parse the CSV values.
  var objPattern = new RegExp(
    (
      // Delimiters.
      "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
      // Quoted fields.
      "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
      // Standard fields.
      "([^\"\\" + strDelimiter + "\\r\\n]*))"
    ),
    "gi"
  );
  // Create an array to hold our data. Give the array
  // a default empty first row.
  var arrData = [[]];
  // Create an array to hold our individual pattern
  // matching groups.
  var arrMatches = null;
  // Keep looping over the regular expression matches
  // until we can no longer find a match.
  while (arrMatches = objPattern.exec( strData )){
    // Get the delimiter that was found.
    var strMatchedDelimiter = arrMatches[ 1 ];
    // Check to see if the given delimiter has a length
    // (is not the start of string) and if it matches
    // field delimiter. If id does not, then we know
    // that this delimiter is a row delimiter.
    if (
      strMatchedDelimiter.length &&
      (strMatchedDelimiter != strDelimiter)
    ){
      // Since we have reached a new row of data,
      // add an empty row to our data array.
      arrData.push( [] );
    }
    // Now that we have our delimiter out of the way,
    // let's check to see which kind of value we
    // captured (quoted or unquoted).
    if (arrMatches[ 2 ]){
      // We found a quoted value. When we capture
      // this value, unescape any double quotes.
      var strMatchedValue = arrMatches[ 2 ].replace(
        new RegExp( "\"\"", "g" ),
        "\""
      );
    } else {
      // We found a non-quoted value.
      var strMatchedValue = arrMatches[ 3 ];
    }
    // Now that we have our value string, let's add
    // it to the data array.
    arrData[ arrData.length - 1 ].push( strMatchedValue );
  }
  // Return the parsed data.
  return( arrData );
}

ポイント

Googleのサービスは基本的に「UTF-8」の文字コードを使うのが基本になっているようで、現実日本社会に於いて用いられている多くのCSVをそのままGoogleのチュートリアルの通りインポートすると確実に文字化けします。

ということで今回のような現実の日本の事務の世界では(そうでなくても、UTF-8以外が主流ですが)以下のようなコードのポイントがあります。

  1. getContentAsString()は使用しない。UTF-8であるというならばそのまま使えば結構ですが。encode_utf8()にてエンコードしているような例もありますが、今回は使いません。
  2. Shift_JISなCSVの多い日本では、getDataAsString("Shift_JIS")を使用するのが良いでしょう。
  3. DriveAppを今回は使用しているので、帰ってきた値は変数filesで受けて、files.next().getBlob()にて取得すること。
  4. 今回はサブ関数としてチュートリアルに乗っているそのままのCSVToArray関数の結果を受け取って書き込みをしている。setValuesで書き込む時にはそのまま配列で渡せるので便利。
  5. 80個の平均5KBのCSV(合計2,800レコード/31列)読み込み完了まで、50秒程度でした。結構早いですね。
  6. 書き込み自体は1行ずつ書き込みをしている。csvFileには全CSVデータを連結する形で一気に流し込んでいます。
  7. DriveApp.searchFilesでは、CSVのMIMEタイプがオプションにないため、必ずこのコードの場合対象のフォルダにはCSVファイル以外は入っていない状態であることが必要です。あえて言えば、その後、後ろ3文字を見て、csvではないものはスクリプト上でスルーする処理が必要です。

今回のコードは各CSVデータにカラム行があったとしてもベタで読み込み、ベタで書き込みをしています。そのため、本来ならば精査しなければならないカラム行の精査もしていないため、カラム行があってもそのまんま続けてデータが書き込まれてしまいます。カラム行がなければそれでも良いのですが、そうでない場合には、CSVデータの精査を行う・配列データを加工するルーチンを用意する必要性があります。

参考リンク