Google Apps Scriptを触ってみるの巻

読んでもらうために書いたものではないので、読みにくいと思います。

その1 - 最初の一歩

とりあえずドットインストールを見た。

Google Apps Script入門 (全16回) - プログラミングならドットインストール

9回まで見た。あとは今のところ関係なさそうなのでパス。

逆引きリファレンスとASCIIの記事はちょろっと見ただけ。今のところ不要。

逆引きGoogle Apps Scriptリファレンス « bmoo.net

ASCII.jp:Web制作をちょっと便利にするGoogle Apps Script入門

その2 - サンプルを見る。

とりあえずは「スクレイピングスプレッドシートに書き込む」というのがしたいだけなので、「google apps script スクレイピング」でググる

Google Apps Scriptでスクレイピングする方法 - Qiita

コピペしたら動いた。

その3 - 書き込む。

セルの指定(ドットインストールのをそのまま使う)をして、setValueで書き込んだ。

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/");

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);

  var myRegexp = /<title>([\s\S]*?)<\/title>/i;
  var match = myRegexp.exec(response.getContentText());
  var title = match[1];
  title = title.replace(/(^\s+)|(\s+$)/g, "");

  range.setValue(title);
}

動いた。

その4 - 正規表現でマッチするものを抜き出す。

実際のデータはWikipedia article traffic statisticsからもらうわけですが、アクセスしまくると怒られそうなので自分のサイトにファイルを設置。

jsonなんだからそのまま使えるんじゃないのかと思いつつ(やり方はわからん)、整形してスプレッドシートに放り込むことにする。

正規表現は今動いているプログラムから持ってくることにする。

必要なのはファイルのうち、内側の波括弧の中だけ。とりあえずその3で使ったのを流用する。

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");

  
  var myRegexp = /([\s\S]*?)},\s\"project\"/i;
  var match = myRegexp.exec(response.getContentText());
  var result = match[1];
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  range.setValue(result);
  
}

ここのresultは「},\s\"project\"」(正規表現「波括弧、コンマ、半角スペース、ダブルクオーテーション、project、ダブルクオーテーション」)
よりも前の部分を抜き出したものになる。

ここで気がついたが、スクリプトエディタのデバッグ機能が便利。
ただしエラーがないと表示されないので、意図的にエラーを作っておくと良い。

Logger.log(うんこ); 

みたいなのを最終行に入れておくと最後に必ずエラーが発生するのでデバッグ画面が見れる。

その5 - 正規表現で置換する。

その4でexecというのを使っているがこれが何なのかわからなかった。
まずGoogle Apps Scriptのリファレンスの見方がはっきりしない。
Spreadsheet Service - Google Apps Script — Google Developers
とりあえずはJavaScriptのリファレンスを見ればいいということに気がついた。

ということでMDN
JavaScript リファレンス - JavaScript | MDN

replaceで置換ができるらしい。
String.prototype.replace() - JavaScript | MDN
↑のサンプルでは正規表現は以下のように書く

var str = "Twas the night before Xmas...";
var newstr = str.replace(/xmas/i, "Christmas");

alert(newstr); // "Twas the night before Christmas..." と出力されます。

なので、resultに対してreplaceすればいいので

result = result.replace(/.*{/g, "");

とすればresultの「{"daily_views": {」が消える。

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");

  
  var myRegexp = /([\s\S]*?)},\s\"project\"/i;
  var match = myRegexp.exec(response.getContentText());
  var result = match[1];
  result = result.replace(/.*{/g, "");
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  range.setValue(result);
  
}

動いた。

RegExp.prototype.exec()とString.prototype.replace()を混ぜて書く意味は無いのだが、続行する。あとで直す(直さないだろうなあ)。

消したいものは色々あるので
追加していく。

result = result.replace(/\"/g, "");
result = result.replace(/\s/g, "");

それぞれ、
ダブルクオーテーションを消す
半角スペースを消す
これを追加すると

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");

  
  var myRegexp = /([\s\S]*?)},\s\"project\"/i;
  var match = myRegexp.exec(response.getContentText());
  var result = match[1];
  result = result.replace(/.*{/g, "");
  result = result.replace(/\"/g, "");
  result = result.replace(/\s/g, "");
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  range.setValue(result);
  
}

動いた。

その6 - 配列って何?

このままだと取得したものが1つのセルに全部入力されてしまう。
置換で改行を入れてもセル内改行されるだけ
置換でタブを入れても無視される

配列というものがあるらしいことはなんとなく知っていたので、resultを配列にしてみることにした。
(ただし配列が何なのかはよくわからない)

String.prototype.split() - JavaScript | MDN
splitを使う。

result = result.split(",");

これを追加して

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");

  
  var myRegexp = /([\s\S]*?)},\s\"project\"/i;
  var match = myRegexp.exec(response.getContentText());
  var result = match[1];
  result = result.replace(/.*{/g, "");
  result = result.replace(/\"/g, "");
  result = result.replace(/\s/g, "");
  result = result.split(",");
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  range.setValue(result);
  
}

デバッグ画面を見ると、それぞれ配列に格納されているようだ。

この配列を1つ1つセルに入れているようにすればいいんじゃないか?と考えている。

その7 - jsonにダイレクトアタックできるかも

GAS(Google Apps Script)からATND APIにアクセスする。 - プログラマとSEのあいだ
なんかイケそうなので真似してみる。

  // ATND APIの結果をパース
  var eventSearchResult = Utilities.jsonParse(response.getContentText());

こんなオシャレなのがあるようだ。

  // スプレッドシート(データ)への書き込み
  for(var i=0; i<eventSearchResult.events.length; i++) {
    var event = eventSearchResult.events[i];
    
    // タイトル
    range.offset(i+1, 0).setValue(event.title);
    
    // ATNDのURL
    range.offset(i+1, 1).setValue(event.event_url);
  }

この辺は配列の書き方を指定しているんだろうと推測。
わからん。とりあえずjsonParseできるのか試してみる。

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");
  var result = Utilities.jsonParse(response.getContentText());
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  range.setValue(result);
}

できた。あっけない。

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");
  var result = Utilities.jsonParse(response.getContentText());
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  range.setValue(result.title);
}

こうすれば名前が出る。
じゃあこうすれば

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");
  var result = Utilities.jsonParse(response.getContentText());
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  range.setValue(result.daily_views.2013-02-04);
}

エラー。そういうことじゃないらしい。
JavaScriptの基本がわかっていないようだ。

どうやらstringはこれでいいがobjectはこれではアカンらしい。

その8 - 配列って何?

2回目。
オブジェクトっていうのは連想配列らしい。(←意味を理解せずに書いた)
jsonっていうのはkey:valueっていう構造になってるらしい。
ここで使ってるのでいうと日付:PVという形。
JavaScriptプログラミング講座【JSON について】
ならばと「javascript 連想配列 取り出し」でググる
連想配列の使用方法 [Java Script - 配列 - Tips]

// value1 と表示される
alert( myObj['key1'] ); 

これで取り出せるってことは

  var result = result.daily_views;
  //resultのdaily_viewsをresultとする
  var result_views = result['2013-02-04'];
  //resultのうち、keyが2013-02-04になっているものをresultとする

という風になるので、

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");
  var result = Utilities.jsonParse(response.getContentText());
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  var result = result.daily_views;
  var result_views = result['2013-02-04'];
  
  range.setValue(result_views);
}

これでセルA1に2013-02-04の数値である2704が表示される。

今日はここまで

あまり進まなかった。
keyを指定していけばそれぞれの値が取り出せることになる。
日付なのでループさせれば問題ないだろう。
続く。

その9 - 人が書いたのを読んでみる

ループの仕方を見せてもらう。
改めてその7で見たやつを見る。

  // スプレッドシート(データ)への書き込み
  for(var i=0; i<eventSearchResult.events.length; i++) {
    var event = eventSearchResult.events[i];
    
    // タイトル
    range.offset(i+1, 0).setValue(event.title);
    
    // ATNDのURL
    range.offset(i+1, 1).setValue(event.event_url);
  }





-----

i<eventSearchResult.events.length
何回ループするかをeventSearchResult.eventsの配列の要素の個数で測る

var event = eventSearchResult.events[i];
eventをeventsのi番目として定義

range.offset(i+1, 0).setValue(event.title);
eventのtitleをrange(1,1)からi+1だけずらした位置にsetVsetValue(書き込み)

だと思う。

素数の取得はObject.keys().lengthかfor-in
【JavaScript】連想配列で要素数の取得 - Qiita
美しくない?知らん。

とりあえずあまり考えないでサクッと書く

  // スプレッドシート(データ)への書き込み
  for(var i=0; i<Object.keys(result).length; i++) {
    
    // 日付
    range.offset(i+1, 0).setValue("2013-02-" + i);
    
    // データ
    range.offset(i+1, 1).setValue(result_views);
  }

これを追加するのかな?

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");
  var result = Utilities.jsonParse(response.getContentText());
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  var result = result.daily_views;
  var result_views = result['2013-02-04'];

  // スプレッドシート(データ)への書き込み
  for(var i=0; i<Object.keys(result).length; i++) {
    
    // 日付
    range.offset(i+1, 0).setValue("2013-02-" + i);
    
    // データ
    range.offset(i+1, 1).setValue(result_views);

  range.setValue(result_views);
  }
}

ちょっと考えなさすぎた。

・result_viewsの定義がおかしい。

var result_views = result['2013-02-' + i];


・日付のiは2桁にする必要がある。
[Javascript]1桁と2桁が混在する変数の数値を2桁にする。 | ITメモ

var day_number = ("0" + i).slice(-2);
var result_views = result['2013-02-' + day_number];

・iは1からでいい。

・offsetのi+1はサンプルの仕様なので変える。

・30日までしかない。

i<Object.keys(result).lengthじゃなくてi<=Object.keys(result).lengthにするべき


・Utilities.jsonParseはサポート終了するかもって脅されたので変える
GoogleAppsScript - Google Apps Script でjsonデータの読み込み - Qiita

var result = JSON.parse(response.getContentText());

こうかな。

function myFunction() {
  var response = UrlFetchApp.fetch("http://lisiograph.webcrow.jp/others/json/sampledata.json");
  var result = JSON.parse(response.getContentText());
  
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var range = sheet.getRange(1, 1);
  
  var result = result.daily_views;
  
  // スプレッドシート(データ)への書き込み
  for(var i=1; i<=Object.keys(result).length; i++) {
    var day_number = ("0" + i).slice(-2);
    var result_views = result['2013-02-' + day_number];
    
    // 日付
    range.offset(i, 0).setValue("2013-02-" + day_number);
    
    // データ
    range.offset(i, 1).setValue(result_views);
  }
}

その10 - とりあえず満足したので今後のことを考える。

わりとイケそうなので今後やるべきことを整理していく。

・とりあえずテストページ作って動かしてみる。
ページはそのまま流用できる。
過去のデータは手動で入力する。
isualization API で表示できるはず。
twproやニコニコ動画のデータは正規表現置換の方法(序盤にやってたやつ)で引っ張れるはず。
前日比を計算したやつを(特定のセルの計算結果を)表示しなければならない。
更新日もできれば表示できるようにしたい。

・一覧ページを作る
スプレッドシート側である程度整形しておけばtableに出力するのは無理ではなさそう。
ただ、常に動的生成になってしまう。過去データを静的ページとして保存しておく必要がある。


思いつくのはこんなところです。
ここで一区切り。