GAミント至上主義

Web Monomaniacal Developer.

Googleスプレッドシートを一行ずつJSONのファイルにしてGoogleドライブに書き出す

前回↓につづきスプレッドシートの処理。
uyamazak.hatenablog.com

前回は1つのJSONだからメッセージに出してコピペで良かった。

でも今回は都道府県ごとに数百文字のテキストがあり、すべてを1ファイルにまとめてしまうと100KB近くいってしまい、Ajaxでの取得に時間がかかりそうなので、都道府県ごとにファイル分割することにしました。

最初はすべてをまとめたJSONを書き出して、ローカルPCでファイルごとに分割する処理をシェルスクリプトかなにかでやろうかと思いましたが、よく考えるとそれ、Googleドライブでできるのでは?と思い調べてみたら簡単そうなのでやりました。

ファイルの書き出し、文字コードの指定はこちらを参考にしました。
hrroct.hatenablog.com


スプレッドシート側のデータ構造はこんな感じ(まだテキストは入ってない)
f:id:uyamazak:20200821163409p:plain

これを1行ずつ都道府県コードをファイル名としてファイル保存にします。

コードはこんな感じ。

function createJsonFiles() {
  const sheet = SpreadsheetApp.getActiveSheet(); 
  const rows = sheet.getSheetValues(1, 1, sheet.getLastRow(), sheet.getLastColumn());  
  const parentFolder = DriveApp.getFolderById('{フォルダのID}');
  const folderName = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd_HH-mm-ss');
  const folder = DriveApp.createFolder(folderName).moveTo(parentFolder)
  const contentType = 'text/json';
  const charset = 'utf-8';
  rows.forEach(function(cols, index) {
    if (index === 0) {
      return
    }
    const prefCode = cols[0]
    const prefName = cols[1]
    const textAll = cols[2]
    const text50 = cols[3]
    const text60 = cols[4]
    const text70 = cols[5]
    const fileName = `${prefCode}.json`
    const content = {
      'name': prefName,
      'textAll': textAll,
      'text50': text50,
      'text60': text60,
      'text70': text70,
    }
    const blob = Utilities.newBlob('', contentType, fileName)
      .setDataFromString(JSON.stringify(content), charset);
    
    folder.createFile(blob);
  })
}

最初、一つのフォルダに書き出せば、再度実行した際に上書きされるかと思いましたが、Googleドライブは実際にはIDで管理しているので、実行するたびに同名のファイルができてしまいました。

そのため、指定のフォルダの下に実行日時を元にフォルダを作り、そこに入れることにしました

  const parentFolder = DriveApp.getFolderById('{フォルダのID}');
  const folderName = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd_HH-mm-ss');
  const folder = DriveApp.createFolder(folderName).moveTo(parentFolder)

DriveApp.createFolder(ファイル名)だけでは、他のフォルダに配置することができないので、作成した後に、moveTo(親フォルダ)が必要でした。
developers.google.com

実行すると日時のフォルダができて、こんな感じで出力されました。
f:id:uyamazak:20200821164313p:plain

更新頻度によってはCMS使ったり、専用アプリ作った方がいいけど、こんな雑なGASとG Suiteで十分な場面は結構あるなぁと思いました。

Googleスプレッドシートを使って政府統計のデータをChart.jsで使えるJSONに変換する

シニアジョブで、サイトのコンテンツとして年代別就業状況の統計データを使うためにいろいろやったので流れをメモ。

データは政府の政府統計ポータルサイトe-Statのものを使いました。リンクは都道府県別のもの。
www.e-stat.go.jp

利用の際は出典の明記が必要です。
利用規約 | 政府統計の総合窓口

いろいろ探してエクセル形式でダウンロードしてGoogleスプレッドシートに読み込み、必要なものをコピペしていきます。

ちなみに私のスプレッドシートやエクセルのスキルは実務経験でいうと3週間ぐらいです。

コンテンツとしてデータがどんなものがいいかは、数字の羅列を見てもわかりません。
まずはグラフにするため、必要そうなデータをどんどん列に追加していきます。

行は年と都道府県にして、列にシニア世代を50代、60代、70歳以上という3つに分けていろいろ追加していきました。

f:id:uyamazak:20200818152939p:plain

唯一知っていると言っても過言ではない機能、ピボットテーブルを使って、データを整理してグラフにします。
知っているだけで使い方はわからないので、いい感じになるまで設定をいじりまくります。
いくつかできたら、どれがいいかなというのを周りに相談しながら決めました。
最終的には全就業者数における、各シニア世代の就業者数の割合のパーセントにしました。

f:id:uyamazak:20200818153519p:plain

グラフとデータが決まったのでChart.jsに移ります。
いい感じにグラフを表示してくれます。
www.chartjs.org


今回はVueのコンポーネントとして実装するため、ラッパーであるvue-chartjsをインストールしておきます。
vue-chartjs.org

Chart.jsで使うためには、スプレッドシートのデータがこんな感じになると嬉しいです。

  {
    "labels":[1980,1985,1990,1995,2000,2005,2010,2015],
    "datasets":[
      {"label":"50代","data":[16.69,18.53,19.08,19.95,22.89,23.11,20.34,20.07]},
      {"label":"60代","data":[7.33,7.61,8.87,10.18,10.17,11.53,14.5,15.51]},
      {"label":"70歳以上","data":[2.33,2.55,2.63,3.27,3.78,4.53,4.97,5.99]}
    ]
  }

なるべくスプレッドシートはデータだけに集中させたいので、backgroundColorなど色なんかはあとでVue側でlabelを見てくっつけることにします。

スプレッドシートのメニューの「ツール」→「スクリプトエディタ」を開いてGASを書きます。
Null合体演算子が使えない、アロー関数でインデントが狂う、StringにreplaceAllがないなど最近のJavaScriptとの微妙な違いと戦いながら雑に書いていき、こんな感じになりました。
都道府県別のも書きましたが複雑なので単純な年代別のものだけ。

function ageJson() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const rows = sheet.getSheetValues(1, 1, sheet.getLastRow(), sheet.getLastColumn());
  const labels = [];
  const dataset = [];
  const data50 = [];
  const data60 = [];
  const data70 = [];
  const dataAll = [];
  
  rows.forEach(
    function(cols, index) 
    {
      if (index === 0){
        return;
      }
      // 0が全国
      if (cols[1] !== 0) {
        return;
      }
      // 列変えたら数字変える
      year = cols[0];
      worker50 = cols[6];
      worker60 = cols[7];
      worker70 = cols[8];
      workerAll = cols[12];
      labels.push(year);
      data50.push(worker50);
      data60.push(worker60);
      data70.push(worker70);
      dataAll.push(workerAll);
    }
  );
  function toPercent(val, index) {
    return round(val / dataAll[index]*100, 2);
  }
  const result = {
    labels: labels,
    datasets: [
      {label: '50代', data: data50.map(toPercent)},
      {label: '60代', data: data60.map(toPercent)},
      {label: '70歳以上', data: data70.map(toPercent)}
    ]
  };
  
  Browser.msgBox(JSON.stringify(result));
}

// https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/round
function round(number, precision) {
  var shift = function (number, precision, reverseShift) {
    if (reverseShift) {
      precision = -precision;
    }  
    var numArray = ("" + number).split("e");
    return +(numArray[0] + "e" + (numArray[1] ? (+numArray[1] + precision) : precision));
  };
  return shift(Math.round(shift(number, precision, false)), precision, true);
}

他でも使えるテンプレートとしては下記のような感じ。

function myFunction() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const rows = sheet.getSheetValues(1, 1, sheet.getLastRow(), sheet.getLastColumn());
  const result = {}
  rows.forEach(
    function(cols, index) {
      // いろいろやってresultにいれてく
    }
  );
  Browser.msgBox(JSON.stringify(result));

出力結果を他のシートの書き出す方法なんかもあるんですが、今回は手軽にBrowser.msgBoxを使いました。

f:id:uyamazak:20200818155441p:plain

実行してシートに戻るとこんな感じで出力されてるので、コピペして使います。

まだ途中だけどこんな感じでChart.jsで表示できました。
f:id:uyamazak:20200818160022p:plain

色パターンはデフォルトだとグレーなので、一旦こちらのを使いました。あとでデザイナーの方にいい感じにしてもらいます。
colorbrewer2.org


microCMSのリッチテキスト内のimgタグをamp-imgに変換する

7/1に公開したシニアタイムズでは、NuxtJSとamp-moduleを使ってAMP対応を行いました。

でもmicroCMSのリッチテキストで保存している記事本文は自分で対応する必要があります。

基本的に生成されるHTMLはきれいなのでほとんど問題ないのですが、imgタグだけはamp-imgに変換する必要がありました。

ドキュメント:<amp-img> - amp.dev

タグ名だけでよければ簡単ですが、難しいのが画像の高さを指定する必要があること。表示速度のためには仕方ない。

サムネイルに使うような画像でよければ大体横長で比率も固定しやすいですが、記事内なのでどんな画像が使われるかはわかりません。

今回はいろいろ試行錯誤の結果、画像APIで固定のサイズに変換し、余白は背景色で埋めるという対応をしました。

画像APIとは

HTMLで返ってくるので正規表現で置換するのが良さそうです。まずはimgタグを抽出し、中でsrcにパラメータをつける処理をしました。

で下記のように書いてみました。

ampページのvueファイル抜粋

<script lang="ts">
// 省略
  async asyncData({ $axios, error, params }) {
    // 省略
    // 記事データ取得
    const currentPost = await $axios.$get(API_URL + `magazines/${id}`)
    // imgをamp-imagに変換する heightの指定が必須なので200で固定する
    const ampBody = currentPost.body.replace(
      /<img([^>]*)>/gi,
      (_match, sub) => {
        sub = sub.replace(
          /src="(.+)"/,
          'src="$1?width=300&height=225&fit=fill&fill-color=fafafa"'
        )
        return `<amp-img ${sub} layout="intrinsic" width="300" height="225"></amp-img>`
      }
    return {
      ampBody,
    }
  },
</script>

layout属性については下記。
amp.dev


比率が違う画像を使うと余計な余白ができてしまうという暫定対応感は否めません。

きっちりやりたいのであれば画像APIでサイズなどのメタ情報を取得できるので、これを使って横幅を計算して入れれば良さそうです。
複雑さとAPIリクエスト数が増えるのが懸念事項でしょうか。

フォーマット