一連の記事では、TwitterAPI の情報を GAS 経由で取得し、Google スプレッドシートに録り溜めていく方法を、ステップバイステップで説明していきます。
前回 は、OAuth1 ライブラリを用いて、GAS から TwitterAPI の呼び出しを行う最小のコードを示しました。
今回は、GAS から Google スプレッドシートに書かれたキーワードを参照し、そのキーワードを使って TwitterAPI の Search を呼び出し、得られたツイートデータを Google スプレッドシートに書き戻す方法について説明します。
しかしこの辺りは、TwitterAPI へのアクセスを除いて、ごくごく一般的な GAS の処理だと思うので、日々 GAS を書いてらっしゃる人からすると、新しい発見はこの記事にないかと思います。
…という点を踏まえて読んでいただけると助かります。
Google スプレッドシートの準備
GAS は、Google スプレッドシートに内包されるものではなく、前回用意した、スタンドアローンのものを拡張して使います。
ここでいう「内包されるもの」とは、Google スプレッドシート上で [Tools] ⇒ [Script Editor] で書き始められる GAS のことを言っています。
ちなみに、私は、次のように GAS を使い分けています。(完全に好みの問題です)
- 既存の Google スプレッドシート上にあるデータで、お手軽に、秒でなんらかの簡単な処理をさせてしまいたい場合は、内包する GAS
- 特定の Google スプレッドシートに依存しない、ある程度汎用性のある処理を作る場合はスタンドアローンの GAS
そういうわけで、Google スプレッドシートは別で用意し、ID を控えておきます。
ちなみに、ID は、Google スプレッドシートの URL の下記の場所になります。
https://docs.google.com/spreadsheets/d/{Google Spreadsheet ID}/edit#gid=0
Google スプレッドシートの仕様決め
仕様はいくらでも複雑にできますが、一連の記事ではシンプルさを重視して、次のように決めます。
- A1 セルに検索用キーワードが書かれている
- A2 セルに前回のキーワード保存される
- 3 行目以降の行末に Search API で取得したツイートの情報が保存されていく
- GAS が走ったとき、A1 ≠ A2 なら、新しいキーワードが指定されたものとして 3 行目以降をクリアする
蛇足: Google スプレッドシートの読み込みと書き込みについて
C/C++ や C# で Excel にアクセスするプログラムを作ったことのある人なら、メモリの使用量を気にして 1 セルずつ処理することを試みたことがあると思います。しかしこの方法だと恐ろしく処理時間がかかり、使い物になりません。
そうではなく、読み込み/書き込みするのに必要な複数のセルを Range でがばっとメモリ上に取り、メモリ上で必要な処理をした後、Range で Excel に書き戻すようにすると、使用に耐えられる処理速度が出てきます。
Google スプレッドシートの処理も、Excel のそれと似ており、getRange() で範囲指定した後、getValues() で必要なデータを配列に取り込み、配列を処理した後、setValues() でシートに書き戻します。
Range は、左上のセル (A1) から、データが書かれている最大行(getLastRow())、最大列(getLastColumn())まで取ることが多いですが、Google スプレッドシートでは、getDataRange() でそれを一発で取得できるなど、便利な関数が多数用意されています。
コード
前回の最後のコードに、Google スプレッドシートの読み込み/書き込み処理を付け足します。
シートへ記載する Tweet データは、ユーザー名、Tweet した日付け、Tweet 内容の 3 つを、それぞれ、A, B, C 列に書き込むこととしました。
let VAL_CONSUMER_API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
let VAL_CONSUMER_API_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
function logOAuthURL() {
var twitterService = getTwitterService();
Logger.log(twitterService.authorize());
}
function getTwitterService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth1.createService('twitter')
// Set the endpoint URLs.
.setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
.setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
.setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
// Set the consumer key and secret.
.setConsumerKey(VAL_CONSUMER_API_KEY)
.setConsumerSecret(VAL_CONSUMER_API_SECRET)
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties());
}
function resetTwitterService() {
var twitterService = getTwitterService();
twitterService.reset();
}
function authCallback(request) {
var twitterService = getTwitterService();
var isAuthorized = twitterService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
function twitterSearch(keyword, maxCount) {
let encodedKeyword = encodeURIComponent(keyword);
try {
var twitterService = getTwitterService();
if (!twitterService.hasAccess()) {
Logger.log(twitterService.getLastError());
return null;
}
var url = 'https://api.twitter.com/1.1/search/tweets.json?q='
+ encodedKeyword
+ '&result_type=recent&lang=ja&locale=ja&count='
+ maxCount;
var response = twitterService.fetch(url, { method: "GET" });
var json = JSON.parse(response);
Logger.log(json);
return json;
}
catch (ex) {
Logger.log(ex)
return null;
}
}
// 古い main() はコメントアウト
//function main() {
// twitterSearch("天気",50);
//}
// ⇓ 今回付け足したコード ⇓
function updateSheet(sheet) {
let keywordNew = ''; // 今回のキーワード
let keywordOld = ''; // 前回処理時のキーワード
// セル A1 から、2 行 1 列分の範囲を取得
let range = sheet.getRange(1, 1, 2, 1);
// 取得した範囲の値を取得
let values = range.getValues();
// セル A1 にある今回のキーワードの取得
keywordNew = values[0][0];
// セル A2 にある前回のキーワードの取得
keywordOld = values[1][0];
// セル A2 にある前回のキーワードを今回のキーワードで更新しておく
values[1][0] = keywordNew;
// 2 行を一旦書き込む
range.setValues(values);
// 今回のキーワードが空、または、新規キーワードなら、3 行目以降の全セルをクリア
if ( sheet.getLastRow() > 3 && (keywordNew == '' || keywordNew != keywordOld ) ) {
range = sheet.getRange(3, 1, sheet.getLastRow()-2, sheet.getLastColumn());
values = range.getValues();
for ( let row=0; row<values.length; row++) {
for ( let col=0; col<values[row].length; col++) {
values[row][col] = '';
}
}
// 一旦書き込む
range.setValues(values);
}
// keyword が空でなければ Tweet データを取得してシートの更新
if ( keywordNew != '') {
let json = twitterSearch(keywordNew, 50);
let countTweets = json.statuses.length;
// 更新をする最初の行の決定
let rowUpdateStart = ( keywordNew != keywordOld )? 2 : sheet.getLastRow();
// Tweet 記入用のレンジの取得
range = sheet.getRange(rowUpdateStart+1, 1, countTweets, 3);
values = range.getValues();
let row = 0, col = 0;
json.statuses.forEach( tweet => {
values[row][col++] = tweet.user.name;
values[row][col++] = tweet.created_at;
values[row][col] = tweet.text;
row++; col=0;
});
range.setValues(values);
}
}
let VAL_ID_TARGET_BOOK = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'; // Google スプレッドシートの ID
function main() {
// Google スプレッドシートの取得
let book = SpreadsheetApp.openById(VAL_ID_TARGET_BOOK);
// 含まれる全シートの取得
let sheets = book.getSheets();
// 全シートについて updateSheet() を呼んで処理をする
sheets.forEach(function (sheet) {
updateSheet( sheet );
});
}
これで、Google スプレッドシートのセル A1 に「天気」などと記入し、main() を実行すると、シートに結果が記入されます。
updateSheet() 関数で、検索キーワードの取得、前回のキーワードとの比較、データ消去、更新とうしているので、やや冗長で読みにくくなっているかもしれませんが、基本的には getRange() ⇒ getValues() でシートのデータを取得して、setValues() で書き戻すことをしています。
後はこれを用途に合わせて発展させていけば、ニーズに合った仕様のツールを得られると思います。
以上で一連の記事を締めくくりたいと思います。