キーワードに一致するツイートデータを毎日自動取得する方法~スプレッドシート × Google Apps Script × Twitter API V2~

SNS

こんにちは、せこしょーです。
今回のスプレッドシート活用術は、
GAS(Google Apps Script)を使って、Twitterデータを取得します。
トリガーを設定すると毎日決まった時間に検索キーワードを含むツイートを自動取得してくれます。
動画はこちら↓↓

ツイートデータを取得するTwitter API v2をGASで利用するために、APIの利用申請(EssentialもしくはElevated)が必要です。
詳細は動画を参考ください。

キーワードに一致する昨日分のツイートを取得するGASのコード↓ ↓

const BEARER_TOKEN = "{BEARER_TOKEN}"

/**
 * Twitterから検索キーワードに一致するツイートを取得し、スプレッドシートへの書き込みをする
 * */ 
const main = () => {
  const keyword = getValueOnSheet('B2');
  if (keyword == '') {
    Browser.msgBox(`キーワードを入力して下さい。`);
    return;
  }
  const searchWord = "("  + keyword + " OR %23" + keyword + ") -is:retweet"; // キーワードもしくはハッシュタグ(リツイートは除く)
  // const searchWord = keyword + " -is:retweet";
  // const searchWord = "("  + keyword + " OR %23" + keyword + ") -is:retweet -is:quote -is:reply";

  let next_token = undefined;
  do {
    const topics = fetchTopics(searchWord,next_token);
    console.log(topics);
    if (topics.meta.result_count == 0) {
      console.log("検索結果は0件です。")
      return;
    }
    next_token = topics.meta.next_token;
    const dataForSheet = makeData(topics);
    recordToSpreadsheet(dataForSheet);

  } while (next_token != undefined);
  
}

/**
 * 昨日の検索キーワードのツイートを取得する
 * */ 
const fetchTopics = (keyword,pageToken) => {
  const date = new Date(); // 現在の日時でDateオブジェクトを生成

  // Twitter API仕様にあわせて協定世界時の開始日、終了日を設定する
  // なお、日本標準時は協定世界時の+9時間
  date.setDate(date.getDate() - 2) 
  const startDate = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd')+"T15:00:00Z"; 
  date.setDate(date.getDate() + 1) 
  const endDate = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd')+"T14:59:59Z";

  let targetUrl = `https://api.twitter.com/2/tweets/search/recent?query=${keyword}&tweet.fields=created_at,text,public_metrics,entities&start_time=${startDate}&end_time=${endDate}&expansions=author_id&max_results=100`;
  if (pageToken != undefined) {
    targetUrl = targetUrl + `&next_token=${pageToken}`;
  }

  const options = {
    'method': 'get',
    'headers': {
      'Content-Type': 'application/json',
      'authorization': 'Bearer ' + BEARER_TOKEN,
    },
  };

  const response = JSON.parse(UrlFetchApp.fetch(targetUrl, options));
  return response;
}

/**
 * ツイート取得内容を記載用に分解し、2次元配列を返す
 * */ 
const makeData = (topics) => {
  const users = topics.includes.users;
  console.log(users);
  let usernames = [];
  for (let i = 0; i < users.length; i++) {
    usernames.push(users[i].username)
  }
  formatted_user_names = usernames.join();
  console.log(formatted_user_names);

  // ユーザー名(100件)のユーザ情報をAPIで取得する
  const usersData = fetchUsersInfo(formatted_user_names).data;
  console.log(usersData);

  let rows = [];
  for (let i = 0; i < topics.data.length; i++) {
    const data = topics.data[i];
    const tweet = data.text
    // リツイートは除外する
    // if (tweet.slice(0,4).indexOf("RT @") != -1) {
    //   continue;
    // }
    const userInfo = getUserInfo(data.author_id, usersData);
    const name = userInfo[0];
    const username = userInfo[1];
    const following_count = userInfo[2];
    const followers_count = userInfo[3];

    const url = "https://twitter.com/"+username+"/status/"+data.id;

    const jstDate = formatDate(new Date(data.created_at), 'yyyy-MM-dd HH:mm:ss');
    rows.push([jstDate, name, following_count,followers_count,tweet, data.public_metrics.like_count, data.public_metrics.retweet_count, data.public_metrics.quote_count, data.public_metrics.reply_count,url]);
  }
  return rows;
}

/**
 * スプレッドシートに対象ツイートを記載する
 * */ 
const recordToSpreadsheet = (rows) => {
  const sheet = getSheet();
  const lastRowNumber = sheet.getLastRow();
  sheet.getRange(lastRowNumber+1,1,rows.length,10).setValues(rows);
}

/**
 * ユーザー情報を取得する(API)
 * */ 
const fetchUsersInfo = (usernames) => {
  const targetUrl = `https://api.twitter.com/2/users/by/?usernames=${usernames}&user.fields=public_metrics`;
  const options = {
    'method': 'get',
    'headers': {
      'Content-Type': 'application/json',
      'authorization': 'Bearer ' + BEARER_TOKEN,
    },
  };
  const response = JSON.parse(UrlFetchApp.fetch(targetUrl, options));
  return response;
}

/**
 * ユーザーを特定し、ユーザー情報を取得する
 * */ 
const getUserInfo = (authorId, users) => {
  for (let i = 0; i < users.length; i++) {
    if (authorId == users[i].id) {
      return [users[i].name,users[i].username,users[i].public_metrics.following_count,users[i].public_metrics.followers_count];
    }
  }
  return undefined;
}

/**
 * 日付のフォーマット
 * */ 
const formatDate = (date, format) => {
  date.setDate(date.getDate());
  return Utilities.formatDate(date, 'JST', format);
}

/**
 * データ書き込み対象のシートを取得する
 * */ 
const getSheet = () => {
  return SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Twitter');
}

/**
 * スプレッドシートから検索キーワードを取得する
 * */ 
const getValueOnSheet = (cellPosition) => {
  const sheet = getSheet();
  return sheet.getRange(cellPosition).getValue();
}

コメント