5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Twitter API v2】過去30日のツイートのインプレッション・エンゲージメントを取得する(Google Apps Script)

Last updated at Posted at 2022-06-28

はじめに

企業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が用意されている。

公式ドキュメント

完成図

image.png

取得したいもの

  • 月間ツイート数
  • 月間インプレッション
  • 月間エンゲージメント
    集計対象は過去30日の投稿したツイート。それ以前のツイートは対象外。理由は後述。

使うもの

  • Google Apps Script
    • スプレッドシートへの転記が容易なため、今回はこれにします。
  • OAuth1 for Apps Script
    • Google Apps ScriptでOauth1を使うためのライブラリ
  • Twitter API v2
    • GET /2/users/me
    • GET /2/users/:id/tweets

目次

  1. TwitterのAPI Key, API Key Secretを取得
  2. Google Apps ScriptでのOAuth1認証
  3. Twitter API v2からデータを取得する
  4. スプレッドシートへのデータの転記

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ではなく、スプレッドシートを作成しエディタを開きます。
qiita_twitterapi_createProject.png

スクリプトプロパティにTwitterのAPI Key, API Key Secretを登録

環境変数を登録するGoogle Apps Scriptのスクリプトプロパティ。新エディタになってGUIでの登録が廃止されたのですが、アップデートで復活しました。
https://qiita.com/Tyamamoto1007/items/c12af331eb62fb6e3051

せっかくなのでGUIで登録します。歯車アイコンの「プロジェクトの設定」からTwitter_API_KEYTwitter_API_KEY_SECRETに、先ほど取得した値をセットします。
image.png

OAuth1 for Apps ScriptのスクリプトIDを入力

続いてライブラリの登録です。
ライブラリのREADME.mdに記載の通り、スクリプトID 1CXDCY5sqT9ph64fFwSzVtXnbjpSfWdRymafDrtIZ7Z_hwysTY7IIhi7s
を入力します。
qiita_twitterapi_registerLibraryKey.png

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を開きます。
qiita_twitter_projectapp.png
歯車アイコンを押し…
qiita_twitter_user_authenticate.png
Editに進み…
image.png
Oauth1.0aをアクティベートし…
image.png
先述の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()」を指定し、実行します。
image.png
ログのURLにアクセスし、
image.png
認証します。authCallback(request)が実行され、成功すると
image.png
と表示されます。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
response.json
{
    "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に追加することで、ページ送りができます。

リクエストURLの例
https://api.twitter.com/2/users/${twitterUserId}/tweets?tweet.fields=non_public_metrics&exclude=retweets&pagination_token=7140dibdnow9c7btw421t2ewenfp071jxnenj8gjljfoy

これを踏まえてpaginationの処理を加えたコード。エラー処理も加えました。また、tweet.fieldcreated_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)]
  })
}

スプレッドシートにデータを落とし込む関数を作成

最後にスプレッドシートにデータを落とし込みます。以下の手順で作成します。

  1. テンプレートシートをコピー。集計対象の年月をシート名に指定。
  2. データを新しいシートにセットする。(setValues())
  3. 各指標の総和を追加

テンプレートシートを作成

シート名は 'template'とします。
image.png

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~にトリガ実行をするように設定すれば、前月のツイート情報の自動取得ができます。
image.png

注意点としては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でインプレッション、エンゲージメントが取れることに触れた日本語の記事が乏しく、記事にしました。いつかどなたかの参考になれば幸いです。

  1. インプレッション
    「利用者のタイムラインまたは検索結果にツイートが表示された回数」
    エンゲージメント
    「利用者がツイートに反応した合計回数。ツイートの任意の場所(リツイート、返信、フォロー、いいね、リンク、カード、ハッシュタグ、埋め込みメディア、ユーザー名、プロフィール画像、ツイートの詳細表示など)のクリック数」
    (https://help.twitter.com/ja/managing-your-account/using-the-tweet-activity-dashboard)
    本記事では、狭義的に「リンクのクリック数」と「プロフィールのクリック数」のみをエンゲージメントとして扱っています。

  2. Qiitaに残された先人たちの奮闘の記録。
    Twitter Developerのアプリ審査に通った文例
    Twitter APIの申請が通らない!!!!

  3. TwitterのAPI Keyの呼称は様々で、API KeyはConsumer KeyApp Key、API Secret KeyはConsumer SecretApp Key Secretと同義。公式ドキュメント の"Terminology clarification"を参考のこと。

  4. .setValue()よりも.setValues()の方がAPIの呼び出し回数が少なく処理が軽いので、.setValues()を用います。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?