はじめに
企業Twitterを運用しており、毎月のパフォーマンスを投稿数やインプレッション、エンゲージメント1等を指標にして記録しています。
毎月死んだ魚の目でTwitterアナリティクスから転記することに飽きてきたので、Twitter APIで自動化できないか調べてみました。
Twitterには、その名もずばりTwitter Engagement APIというAPIが用意されていますが、これは法人向け。今回は、すぐに申請・利用可能なTwitter API v2でインプレッションとエンゲージメントを取得してみます。
目標
自己アカウントの月間ツイートのインプレッション、エンゲージメントをTwitter API v2で取得し、スプレッドシートに記録する。
法人契約(Enterprise Level)が必要なTwitter Engagement APIは用いず、アクセスレベル "Essential" で利用可能なAPIのみを使う。
Twitter APIのアクセスレベルについて
Twitter APIには利用機能枠の異なる Essential, Elevated, Academic Research のアクセスレベルがある。さらに別APIとして、法人向けにEnterprise APIsが用意されている。(公式ドキュメント)
- 日本語の記事では「【2022年 Twitter API】ツイート収集したい人向けのTwitter APIの種類と使い分け」がわかりやすい。
完成図
取得したいもの
- 月間ツイート数
- 月間インプレッション
- 月間エンゲージメント
集計対象は過去30日の投稿したツイート。それ以前のツイートは対象外。理由は後述。
使うもの
- Google Apps Script
- スプレッドシートへの転記が容易なため、今回はこれにします。
-
OAuth1 for Apps Script
- Google Apps ScriptでOauth1を使うためのライブラリ
- Twitter API v2
- GET /2/users/me
- GET /2/users/:id/tweets
目次
- TwitterのAPI Key, API Key Secretを取得
- Google Apps ScriptでのOAuth1認証
- Twitter API v2からデータを取得する
- スプレッドシートへのデータの転記
1. TwitterのAPI Key, API Key Secretを取得(Twitter Developer Portal)
Twitter Developer Portalから利用申請、アプリの作成、各種シークレットの取得をします。手順はこちらの記事などをご参照ください。
以前は英作文の試験があったりとハードルが高かった2ようですが、今では即日で利用申請ができるようになりました。
シークレットは、API Key, API Key Secret, Bearer Tokenの三種類ありますが、今回は、API Key, API Key Secretの2種類を使用します。3
2. Google Apps ScriptでのOAuth1認証
なぜOAuth1認証?
Twitter APIはBearer Token認証など、いくつか認証方式がありますが、Twitterのインプレッション、エンゲージメントは公開指標ではないので、今回はOAuth1認証が必要になります。
Since these fields [non-public metrics, organic metrics] are private (not available to view on Twitter.com), OAuth 1.0a User Context authorization is required for the request.
(https://developer.twitter.com/en/docs/twitter-api/metrics)
これらのフィールド(non public, orgnic metrics)はプライベートな(Twitter.comで閲覧できない)ため、リクエストにはOAuth 1.0a User Context認証が必要です。
取得できるツイートは過去30日分まで
非公開のプライベート指標を取得できるのは、過去30日分のツイートのみ。
Non-public, organic, and promoted metrics are only available for Tweets that have been created within the last 30 days.
(https://developer.twitter.com/en/docs/twitter-api/metrics)
対して上述のEngagement APIだと90日分が対象になります。
(https://developer.twitter.com/en/docs/twitter-api/enterprise/engagement-api/overview)
ライブラリ(OAuth1 for Apps Script)の導入
OAuth1認証にあたり、今回はGoogle Apps Script向けのライブラリ、OAuth1 for Apps Scriptを使用します。
Google Apps Scriptのプロジェクトを作成
後々スプレッドシートに記録するのに都合がよいので、standaloneではなく、スプレッドシートを作成しエディタを開きます。
スクリプトプロパティにTwitterのAPI Key, API Key Secretを登録
環境変数を登録するGoogle Apps Scriptのスクリプトプロパティ。新エディタになってGUIでの登録が廃止されたのですが、アップデートで復活しました。
https://qiita.com/Tyamamoto1007/items/c12af331eb62fb6e3051
せっかくなのでGUIで登録します。歯車アイコンの「プロジェクトの設定」からTwitter_API_KEYとTwitter_API_KEY_SECRETに、先ほど取得した値をセットします。
OAuth1 for Apps ScriptのスクリプトIDを入力
続いてライブラリの登録です。
ライブラリのREADME.mdに記載の通り、スクリプトID 1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s
を入力します。
Callback URLの設定(Twitter Developer Portal)
READMEに記載の通り、OAuth1 for Apps ScriptではCallback URLは常に以下を指定します。{SCRIPT ID}をGoogle Apps ScriptのIDに書き換えてください。
https://script.google.com/macros/d/{SCRIPT ID}/usercallback
IDはスクリプトプロパティと同じ「プロジェクトの設定」から確認できます。
再び、Twitter Developer Portalから、PROJECT APPを開きます。
歯車アイコンを押し…
Editに進み…
Oauth1.0aをアクティベートし…
先述のCallback URLを設定します。
https://script.google.com/macros/d/{SCRIPT ID}/usercallback
以上でTwitter Developer Portalで行う作業は完了です。
認証用のGoogle Apps Scriptを書く
折よくもOAuth1 for Apps ScriptのREADMEにTwitter用のサンプルコードが掲載されているので拝借します。(Source)
サンプルから書き換えるのは
.setConsumerKey()
.setConsumerSecret()
の部分。引数にスクリプトプロパティに保存した値を渡します。
Serviceクラスのインスタンスを作成
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(PropertiesService.getScriptProperties().getProperty('Twitter_API_KEY'))
.setConsumerSecret(PropertiesService.getScriptProperties().getProperty('Twitter_API_KEY_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());
}
認証URLを取得・表示
Google Apps Scriptはリダイレクトができないので、認証URLをコンソールに表示させます。(※サンプルではサイドバーへ表示させていますが、ここではコンソールに表示させています。)
認証URLはService.authorize()で生成されます。
function showOauthLink() {
const twitterService = getTwitterService();
if (!twitterService.hasAccess()) {
const authorizationUrl = twitterService.authorize();
Logger.log(`For Authorization, Please Click ${authorizationUrl}`);
} else {
Logger.log("You Are Already Authorized")
}
}
コールバックのハンドラ関数
認証ページからのコールバックの処理用関数。こちらもREADMEのサンプル通り。
function authCallback(request) {
const twitterService = getTwitterService();
const 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');
}
}
showOauthLink()を実行
エディタのバーでshowOauthLink()」を指定し、実行します。
ログのURLにアクセスし、
認証します。authCallback(request)が実行され、成功すると
と表示されます。OauthTokenはGoogle Apps ScrptのUserPropertyに登録され、Serviceインスタンスにも保存されています。
これでGoogle Apps ScriptでのTwitterの認証は完了、APIを実行する準備が整いました。
3. Twitter API v2からデータを取得する
ここから実際にTwitterのAPIを使用していきます。手始めに
GET /2/users/:id/tweets
で必要になる自己アカウントのidを取得します。
自己アカウントのidを取得する(GET /2/users/me)
自己のアカウント情報はGET /2/users/me
で取得できます。(公式ドキュメント)
以下の関数を実行します。
function findMyUserId() {
const twitterService = getTwitterService()
const response = twitterService.fetch('https://api.twitter.com/2/users/me')
const userId = JSON.parse(response).data.id
PropertiesService.getScriptProperties().setProperty('Twitter_MY_USER_ID',userId)
}
取得したidはスクリプトプロパティに保管しています。
ツイートのインプレッションとエンゲージメントを取得する(GET /2/users/:id/tweets)
いよいよインプレッションとエンゲージメントを取得していきます。使うエンドポイントはGET /2/users/:id/tweets
。(公式ドキュメント)
ドキュメント記載のクエリパラメータのうち、以下の二つを指定します。
クエリパラメータ | 内容 |
---|---|
tweet.fields=non_public_metrics | 'non_public_metrics'をレスポンス含める |
exclude=retweets | レスポンスからリツイートを除外する |
function getUserTweetsData() {
const twitterService = getTwitterService()
const twitterUserId = PropertiesService.getScriptProperties().getProperty('Twitter_MY_USER_ID')
const baseUrl = 'https://api.twitter.com'
const endpointUrl = `/2/users/${twitterUserId}/tweets`
const queryParam = `?tweet.fields=non_public_metrics&exclude=retweets`
let response = JSON.parse(twitterService.fetch(`${baseUrl}${endpointUrl}${queryParam}`))
Logger.log(JSON.stringify(response))
}
実行すると以下を含むレスポンスが得られます。
user_profile_clicks
impression_count
url_link_clicks
{
"data": [
{
"non_public_metrics": {
"impression_count": 1660,
"user_profile_clicks": 3,
"url_link_clicks": 19
},
"id": "15408367XXXXXXXXXXX",
"text": "◆◆ツイート内容1◆◆"
},
{
"non_public_metrics": {
"impression_count": 5120,
"user_profile_clicks": 8,
"url_link_clicks": 29
},
"id": "15390627XXXXXXXXXXX",
"text": "◆◆ツイート内容2◆◆"
},
(中略)
{
"non_public_metrics": {
"impression_count": 3535,
"user_profile_clicks": 12,
"url_link_clicks": 52
},
"id": "15311564XXXXXXXXXXX",
"text": "◆◆ツイート内容3◆◆"
}
],
"meta": {
"next_token": "7140dibdnow9c7btw421t2ewenfp071jxnenj8gjljfoy",
"result_count": 10,
"newest_id": "154083673XXXXXXXXXXX",
"oldest_id": "153115644XXXXXXXXXXX"
}
}
'start_time', 'end_time'の指定
"non_public_metrics"が取得可能なツイートは過去30日間のものに限られますが、'start_time', 'end_time'を含めるとさらに取得期間を限定でき、たとえば過去一週間分だけを取得することもできます。
形式はYYYY-MM-DDTHH:mm:ssZ (ISO 8601/RFC 3339)
。Google Apps Scriptの場合は、さらにencodeURIComponent
でエンコードする必要があります。
エンコードをしないと401エラーが返ります。(参考)
paginationを追加する
Twitter APIでは、クエリパラメータmax_results
(デフォルトは10。最大100)で指定した数を越えるツイートを取得する場合は、paginationの処理が必要になります。
先ほどのresponseに含まれていた、
"next_token": "7140dibdnow9c7btw421t2ewenfp071jxnenj8gjljfoy"
を、今度はリクエスト(GET /2/users/:id/tweets)のpagination_token
に追加することで、ページ送りができます。
https://api.twitter.com/2/users/${twitterUserId}/tweets?tweet.fields=non_public_metrics&exclude=retweets&pagination_token=7140dibdnow9c7btw421t2ewenfp071jxnenj8gjljfoy
これを踏まえてpaginationの処理を加えたコード。エラー処理も加えました。また、tweet.field
にcreated_at
も追加しています。
function getUserTweetsData() {
const twitterService = getTwitterService()
const twitterUserId = PropertiesService.getScriptProperties().getProperty('Twitter_MY_USER_ID')
const baseUrl = 'https://api.twitter.com'
const endpointUrl = `/2/users/${twitterUserId}/tweets`
const queryParam = `?tweet.fields=non_public_metrics,created_at&exclude=retweets`
let response = JSON.parse(twitterService.fetch(`${baseUrl}${endpointUrl}${queryParam}`))
if (!response.data) {
Logger.log('No tweet found')
return
}
let tweetDataArr = response.data
let nextToken = response.meta.next_token
while (nextToken) {
response = JSON.parse(twitterService.fetch(`${baseUrl}${endpointUrl}${queryParam}&pagination_token=${nextToken}`))
if (!response.data) {
break
}
tweetDataArr.push(...response.data)
nextToken = response.meta.next_token
}
//ToDo: Twitter APIのデータ(tweetDataArr)をスプレッドシート用の二重配列に変換
//ToDo: その月の新しいシートを作成し、変換したデータをセットする
}
これで、ツイートのデータが入ったobjectがtweetDataArr
に配列で格納されました。スプレッド構文を使った.push
で破壊的に追加する点、注意です。(参考)
残るはスプレッドシートへの転記のみ。
参考:公式ドキュメント
4. スプレッドシートへのデータの転記
最後にスプレッドシート周りのGoogle Apps Scriptを書きます。
データの二次元配列への整形
スプレッドシートに値を入れる.setValues()は引数に二次元配列を取ります。4
そこでTwitter APIで取得したデータから必要なプロパティを抽出し、二次元配列に整えます。
response.data
の配列を引数に二次元配列を返す関数を以下のように定義します。
/**
*Twitter APIからのレスポンスを配列にして返す関数。
*@param {object[]}
*@return {(number | string)[][]}
*/
function formatTwitterResponse(tweetDataArr) {
return tweetDataArr.map((tweet) => {
//url_link_clicks, user_profile_clicksの合計をエンゲージメントとする。
const engagements = (url_link_clicks, user_profile_clicks) => {
if (!url_link_clicks) {
url_link_clicks = 0
}
else if (!user_profile_clicks) {
user_profile_clicks = 0
}
return url_link_clicks + user_profile_clicks
}
return [tweet.id, tweet.created_at, tweet.text, tweet.non_public_metrics.impression_count, engagements(tweet.non_public_metrics.url_link_clicks, tweet.non_public_metrics.user_profile_clicks)]
})
}
スプレッドシートにデータを落とし込む関数を作成
最後にスプレッドシートにデータを落とし込みます。以下の手順で作成します。
- テンプレートシートをコピー。集計対象の年月をシート名に指定。
- データを新しいシートにセットする。(setValues())
- 各指標の総和を追加
テンプレートシートを作成
Google Apps Scriptを書く
/**
*テンプレートからスプレッドシートを作成し、引数の値をセットする関数。
*@param {(number | string)[][]}
*/
function createMonthlySheeet(data) {
const monthlyTwitterSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
const templateSheet = monthlyTwitterSpreadsheet.getSheetByName('template')
const newSheet = templateSheet.copyTo(monthlyTwitterSpreadsheet)
const now = new Date()
const yesterday = new Date(now.setDate(now.getDate() - 1))
newSheet.setName(Utilities.formatDate(yesterday, 'JST', "YYYY/MM"))
newSheet.getRange(2, 1, data.length, data[0].length).setValues(data)
//総和を追加
const lastRow = newSheet.getLastRow()
newSheet.getRange(2,6,1,3).setFormulas([[`=COUNTA(A2:A${lastRow})`,`=Sum(D2:D${lastRow})`,`=Sum(E2:E${lastRow})`]])
}
まとめ
これらを先ほどのコードに追加し、完成です。
function getUserTweetsData() {
const twitterService = getTwitterService()
const twitterUserId = PropertiesService.getScriptProperties().getProperty('Twitter_MY_USER_ID')
const baseUrl = 'https://api.twitter.com'
const endpointUrl = `/2/users/${twitterUserId}/tweets`
const queryParam = `?tweet.fields=non_public_metrics,created_at&exclude=retweets`
let response = JSON.parse(twitterService.fetch(`${baseUrl}${endpointUrl}${queryParam}`))
if (!response.data) {
Logger.log('No tweet found')
return
}
let tweetDataArr = response.data
let nextToken = response.meta.next_token
while (nextToken) {
response = JSON.parse(twitterService.fetch(`${baseUrl}${endpointUrl}${queryParam}&pagination_token=${nextToken}`))
if (!response.data) {
break
}
tweetDataArr.push(...response.data)
nextToken = response.meta.next_token
}
const spreadsheetData = formatTwitterResponse(tweetDataArr)
createMonthlySheeet(spreadsheetData)
}
/**
*Twitter APIからのレスポンスを配列にして返す関数。
*@param {object[]}
*@return {(number | string)[][]}
*/
function formatTwitterResponse(tweetDataArr) {
return tweetDataArr.map((tweet) => {
//url_link_clicks, user_profile_clicksの合計をエンゲージメントとする。
const engagements = (url_link_clicks, user_profile_clicks) => {
if (!url_link_clicks) {
url_link_clicks = 0
}
else if (!user_profile_clicks) {
user_profile_clicks = 0
}
return url_link_clicks + user_profile_clicks
}
return [tweet.id, tweet.created_at, tweet.text, tweet.non_public_metrics.impression_count, engagements(tweet.non_public_metrics.url_link_clicks, tweet.non_public_metrics.user_profile_clicks)]
})
}
/**
*テンプレートからスプレッドシートを作成し、引数の値をセットする関数。
*@param {(number | string)[][]}
*/
function createMonthlySheeet(data) {
const monthlyTwitterSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
const templateSheet = monthlyTwitterSpreadsheet.getSheetByName('template')
const newSheet = templateSheet.copyTo(monthlyTwitterSpreadsheet)
const now = new Date()
const yesterday = new Date(now.setDate(now.getDate() - 1))
newSheet.setName(Utilities.formatDate(yesterday, 'JST', "YYYY/MM"))
newSheet.getRange(2, 1, data.length, data[0].length).setValues(data)
//総和を追加
const lastRow = newSheet.getLastRow()
newSheet.getRange(2,6,1,3).setFormulas([[`=COUNTA(A2:A${lastRow})`,`=Sum(D2:D${lastRow})`,`=Sum(E2:E${lastRow})`]])
}
これを毎月1日、00:00~にトリガ実行をするように設定すれば、前月のツイート情報の自動取得ができます。
注意点としては31日の月は1日のツイートが取得できないこと、過去一ヶ月のツイートのみが取得対象となるため、それ以前のツイートのデータは月間パフォーマンスに含まれないこと。もしアカウントの全てのツイートの過去1か月のパフォーマンスを集計したい場合は、先述のEngagement APIが必要です。
また、集計は1日時点のデータになるので、指標が月初のツイートに偏重します。(31日のツイートは1日分のデータのみになる。)これはたとえば、クエリパラメータに'start_time', 'end_time'を変数で指定し、30日前のツイートを取得、トリガの実行間隔を1日単位にすることで対処できそうです。
さいごに
本記事ではインプレッション、エンゲージメントの取得を題材にTwitter APIへの申請からGoogle Apps ScriptでのOAuth1認証、Twitter APIからのデータの取得、データの整形、そしてスプレッドシートへの落とし込みまで一通り行いました。
non-public metrics
でインプレッション、エンゲージメントが取れることに触れた日本語の記事が乏しく、記事にしました。いつかどなたかの参考になれば幸いです。
-
インプレッション:
「利用者のタイムラインまたは検索結果にツイートが表示された回数」
エンゲージメント:
「利用者がツイートに反応した合計回数。ツイートの任意の場所(リツイート、返信、フォロー、いいね、リンク、カード、ハッシュタグ、埋め込みメディア、ユーザー名、プロフィール画像、ツイートの詳細表示など)のクリック数」
(https://help.twitter.com/ja/managing-your-account/using-the-tweet-activity-dashboard)
本記事では、狭義的に「リンクのクリック数」と「プロフィールのクリック数」のみをエンゲージメントとして扱っています。 ↩ -
Qiitaに残された先人たちの奮闘の記録。
Twitter Developerのアプリ審査に通った文例
Twitter APIの申請が通らない!!!! ↩ -
TwitterのAPI Keyの呼称は様々で、API KeyはConsumer KeyやApp Key、API Secret KeyはConsumer SecretやApp Key Secretと同義。公式ドキュメント の"Terminology clarification"を参考のこと。 ↩
-
.setValue()よりも.setValues()の方がAPIの呼び出し回数が少なく処理が軽いので、.setValues()を用います。 ↩