GAS(Google Apps Script)からQiita APIにアクセスして、Google Spreadsheetに取得した情報書き込む

  • 52
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

タイトルのまんまのことをやります。
まず、動作イメージです。

※ GAS(Google Apps Script)ってどうやって始めればいい? って感じの場合:


動作イメージ

  1. 事前準備:QiitaアカウントをSpreadsheetに定義しておく
  2. GAS:Qiitaアカウント名を取得する
  3. GAS:取得したアカウント名をキーにして、Qiita APIからアカウント情報を取得する
  4. GAS:取得したアカウント情報をSpreadsheetに書き出す

このGASをトリガー(スケジューラ)に、1日1回任意の時間に動かすとかの設定をしておきます。

スクリーンショット_2014-02-10_13_28_27.png

Point

この実装で必要なGASのポイントは主に以下2点です。

  • Spreadsheetの読み込み・書き出し
  • Qiita APIに、GASのUrlFetchApp.fetch(url, urlFetchOption)でアクセス
    • 今回の実装ではQiita認証はしてません(ちなみに、1時間に150リクエストまでしか送れません)。
    • GAS:UrlFetchApp のAPIリファレンスはコチラ

参考情報:Qiita APIの仕様

ココに仕様(制限も)が書いてあります。
本当は、Contributionの数も取得したかったのですが、取れないようです。。。

API利用制限部分だけ抜粋しときます。

APIの利用制限

上限: 150リクエスト/1時間
認証リクエストでは認証ユーザーごと,認証していないリクエストではIPごとに制限される.
以下のようにレスポンスヘッダにリクエスト上限数と残りリクエスト可能回数が入っている.

$ curl -I https://qiita.com/api/v1/users/whoever.json
HTTP/1.1 200 OK
X-RateLimit-Limit: 150
X-RateLimit-Remaining: 140
またこれらの情報はGET /api/v1/rate_limitでも取得できる.

json
{
"remaining": 140,
"limit": 150
}

ソースコード

トリガー(スケジューラ)に仕込んでおくメソッドは、qiitaInfoWrite2SpreadSheetです。
エラーハンドリング等、一部適当な実装もありますがこのソースで普通に動きます。

処理の流れはこんな感じ

  1. メイン関数起動(スケジューラに仕込むやつ):qiitaInfoWrite2SpreadSheet
  2. スプレッドシートからQiitaアカウントをObject(Map)で取得:getQiitaAccountList
  3. Qiitaアカウント名をループさせて、そのアカウントの情報をQiita APIで取得:getUserQiitaInfo
    • → 取得した情報を引数に渡したQiitaアカウントObject(Map)に追加で情報を積めて返す
  4. Qiita APIをUrlFetchApp.fetch(url, urlFetchOption)で叩いて、取得したbody部をJSONパースして返す:requestQiita

GetQiitaAccountInfo
// @see Qiita API: http://qiita.com/docs

var ENDPOINT = "https://qiita.com/api/v1";

var ACCOUNT_LIST_ = {
  SHEET_NAME : "アカウント一覧",
  ROW_START : 3,  
  COL_ACCOUNT : 2
};


/**
 * Qiita情報をスプレッドシートに書く。
 */
function qiitaInfoWrite2SpreadSheet(){

  var accountList = getQiitaAccountList();

  var values = [];
  var cnt = 1;

  for each(var val in accountList){
    var uinflist = [];
    var userinfo = getUserQiitaInfo(val);

    // 列の順番でpushすること
    uinflist.push(cnt);
    uinflist.push(userinfo.account);
    uinflist.push(userinfo.status);
    uinflist.push(userinfo.items);
    uinflist.push(userinfo.follow_tags);
    uinflist.push(userinfo.following_users);
    uinflist.push(userinfo.followers);
    uinflist.push(userinfo.organization);
    uinflist.push(userinfo.url);    

    values.push(uinflist);
    cnt++;
  }  

  // まとめてドーン!
  var sheet = SpreadsheetApp.getActive().getSheetByName(ACCOUNT_LIST_.SHEET_NAME);
  var srange = sheet.getRange(ACCOUNT_LIST_.ROW_START, 1, values.length, uinflist.length);

  srange.setValues(values);
}


/**
 * アカウントリストを返す。
 */
function getQiitaAccountList(){

  var spApp = SpreadsheetApp.getActive();
  var sheet = spApp.getSheetByName(ACCOUNT_LIST_.SHEET_NAME);
  var values = sheet.getDataRange().getValues();

  var accountListMap = {};

  for(var i = ACCOUNT_LIST_.ROW_START-1; i < values.length; i++){    
    var val = values[i];

    var account = val[ACCOUNT_LIST_.COL_ACCOUNT-1];            
    if(!account){
      continue;
    }

    if(accountListMap[account]){
      // 気づいてね。取り敢えず手で直して。
      accountListMap[account].status = 'err:duplicate'
      continue;
    }

    accountListMap[account] = {
      account : account,  
      status : ''
    };    
  }

  return accountListMap;
}  


/**
 * 指定したQiitaアカウントの情報を返す。
 */
function getUserQiitaInfo(accountMap) {

  // 特定ユーザーの情報取得
  // GET /api/v1/users/:url_name
  var result = requestQiita('/users/' + accountMap.account , 'GET');

  if(result.parseError){
    accountMap.isError = true;
    accountMap.status += 'parseError'
    return accountMap;
  }

  if (result.body.error){
    accountMap.isError = true;
    accountMap.status += result.body.error;
    return accountMap;
  }

  // 特定ユーザーのフォローしているタグ取得
  //GET /api/v1/users/:url_name/following_tags
  var resultf = requestQiita('/users/' + accountMap.account + '/following_tags', 'GET');  
  var rbody = resultf.body;

  var follow_tags = '';
  for each(var val in rbody){
    var follow_tags = follow_tags + ',' + val.name;
  }  
  follow_tags = follow_tags.replace(/^,/,'');  

  accountMap.status += ',normal'; //適当
  accountMap.items  = result.body.items;
  accountMap.followers = result.body.followers;
  accountMap.following_users = result.body.following_users;
  accountMap.url = result.body.url;
  accountMap.organization = result.body.organization;
  accountMap.follow_tags = follow_tags;

  return accountMap;
}


/**
 * Qiitaにリクエストを送信して結果を返す。
 */
function requestQiita(path, method) {

  var url = ENDPOINT + path;

  var urlFetchOption = {
    'method' : (method || 'get'),    
    'contentType' : 'application/json; charset=utf-8',
    'muteHttpExceptions' : true
  };

  var response = UrlFetchApp.fetch(url, urlFetchOption);
  try {
    return {
      responseCode : response.getResponseCode(),
      rateLimit : {
        limit : response.getHeaders()['X-RateLimit-Limit'],
        remaining : response.getHeaders()['X-RateLimit-Remaining'],
      },
      parseError : false,
      body : JSON.parse(response.getContentText()),
      bodyText : response.getContentText()
    };
  } catch(e) {
    return {
      responseCode : response.getResponseCode(),
      rateLimit : {
        limit : response.getHeaders()['X-RateLimit-Limit'],
        remaining : response.getHeaders()['X-RateLimit-Remaining'],
      },      
      // 何らかのエラーが発生した場合、parseError=trueにする
      parseError : true,
      // JSON.parse(response.getContent())で落ちる時があるので、そん時はbody=null返す
      // TODO:ただ、今は呼出元でnull返しの対処はしてない。。。
      body : null,
      bodyText : response.getContentText()
    };
  }

}

このソースで取得できる情報は?

このまんまのソースであれば、以下の赤枠のところが取得できます。

スクリーンショット_2014-02-10_13_54_47.png


最後に:なぜ、やりたかったのか?

社内のエンジニアの技術交流を活性化させるための一つの施策としてQiitaを利用させてもらってます。

ただ、なかなかアカウント登録が進まない & 登録しても投稿が一部でしか増えないので、まだやってない人にやってる感じを見せるのと、沈んでるところを簡単に見える化して、盛り上げ喚起しやすくするためにやりました。

勝負はコレからです。