本文へスキップ

TwitterデータをGoogleスプレッドシートにエクスポートする方法

Googleスプレッドシートは単体ではTwitterのデータを取得できません。そのため、ツイートやフォロワーは、Apps Scriptからタイマーで呼び出すAPIを経由してシートに入ります。1つのスプレッドシートには最大2,000万セル、ツイートにして約200万件を保存できます。

要点: シートへ取り込むどの方法も、JSONを返すAPIの上で動きます。公式X APIは1投稿あたり$0.005、1フォロワーあたり$0.010の課金で、開発者アプリも必要です。Googleスプレッドシートには、Apps ScriptからSorsa APIを使うのが向いています。ApiKey ヘッダー1つで、1リクエストあたりツイート20件またはフォロワー200件を取得でき、フォロワーは1,000人あたり$0.007から、始めるための100リクエストは無料です。

エンドポイント
POST /search-tweets
1ページの件数
1リクエストあたりツイート20件
料金
1,000ツイートあたり$0.07から

#TwitterデータをGoogleスプレッドシートに取り込む仕組み

TwitterのデータをGoogleスプレッドシートに取り込む方法は4つあり、どれもJSONを返すデータソースが必要です。タイマーでAPIを呼び出すApps Script、シート内からヘッダーつきのURLを送るコネクタ系アドオン、Google Sheetsのステップを持つn8n、Make、Zapierなどの自動化ツール、そしてダウンロードしたCSVファイルをファイル > インポートで取り込む方法です。1回限りのファイルなら、無料のTwitterのツイートをCSVにエクスポートツールがCSVを作成します。

数式は5つ目の方法には数えません。IMPORTXML と IMPORTHTML が読み取れるのは1つのWebページだけです。x.comは、ログインしていない訪問者にフォロワーリストも検索結果も表示しないため、数式にはたどるページがなく、x.comのレイアウトが変わるたびに動かなくなります。

方法事前の準備自動更新結果のページ送り
Apps Script(本ガイド)スクリプトを1度貼り付けるだけあり(時間主導型トリガー)あり、何ページでも
コネクタ系アドオンアドオンのフォームにURLとヘッダーを入力あり(アドオンのスケジュール)アドオン次第
n8n、Make、ZapierGoogle Sheetsのステップを持つシナリオありあり(ループを使用)
CSVファイルダウンロードして、ファイル > インポートなしツールが実行
  • Apps Script(本ガイド)

    事前の準備
    スクリプトを1度貼り付けるだけ
    自動更新
    あり(時間主導型トリガー)
    結果のページ送り
    あり、何ページでも
  • コネクタ系アドオン

    事前の準備
    アドオンのフォームにURLとヘッダーを入力
    自動更新
    あり(アドオンのスケジュール)
    結果のページ送り
    アドオン次第
  • n8n、Make、Zapier

    事前の準備
    Google Sheetsのステップを持つシナリオ
    自動更新
    あり
    結果のページ送り
    あり(ループを使用)
  • CSVファイル

    事前の準備
    ダウンロードして、ファイル > インポート
    自動更新
    なし
    結果のページ送り
    ツールが実行

コストと手間を左右するのはデータソースです。公式X APIには、開発者アカウント、アプリ、OAuthが必要です。従量課金プランの料金は、Xの料金ページ(新しいタブで開きます)によると、投稿の読み取りが1件$0.005、フォロワーまたはプロフィールが1件$0.010で、投稿の読み取りは月300万件が上限です。2026年11月22日からは、投稿と一緒に返される投稿者のプロフィールにも$0.010が課金されるため、投稿とその投稿者で$0.015になります(Xの10月23日付の告知(新しいタブで開きます))。スプレッドシートに限らず、Xの公開データを取得するあらゆる方法は、Twitterをスクレイピングする方法のガイドで比較しています。

#Sorsa APIでツイートをGoogleスプレッドシートに取り込む

Twitter(X)API兼スクレイピングツールであるSorsa APIは、ApiKey ヘッダーが1つ付いたHTTPリクエストであれば応答します。これは、Apps Scriptの UrlFetchApp が必要とするすべてです。ツイート、フォロワー、プロフィールをGoogleスプレッドシートに取り込むなら、Sorsa APIが向いています。1リクエストでツイート20件またはフォロワー200件、2006年まで遡る検索、全プランで毎秒20リクエストに対応し、Xの開発者アカウントもOAuthアプリもXログインも不要です。

下のスクリプトは、スプレッドシートに「X data」メニューを追加します。検索に合致する新しい投稿の取り込み、アカウントのフォロワーリスト全体のエクスポート、=X_PROFILES() 数式の提供ができます。セットアップは6ステップです。

  1. APIキーを取得します。 サインアップして、ダッシュボードからキーをコピーします。最初の100リクエストは無料で、カードは不要、有効期限もありません。

  2. スクリプトエディタを開きます。 スプレッドシートで、拡張機能 > Apps Scriptを選びます。このシートに紐づいたプロジェクトが開きます。

  3. スクリプトを貼り付けます。 Code.gs の内容をすべて下のコードに置き換え、SEARCH に検索クエリを、ACCOUNT にフォロワーを取得したいハンドルを設定します。クエリにはXの検索演算子をすべて使えます。一覧はTwitter検索演算子にあります。

    Code.gs
    // Twitter (X) data to Google Sheets with Sorsa API.
    // Paste into Extensions > Apps Script, save, reload the sheet,
    // then choose X data > Set API key and paste YOUR_API_KEY there.
    
    const API = 'https://api.sorsa.io/v3';
    const SEARCH = '"acme coffee" lang:en -filter:retweets';
    const ACCOUNT = 'acme_coffee_roasters';
    const MAX_PAGES = 10; // search pages per run, 20 posts each
    
    const TWEET_COLS = ['id', 'created_at', 'author', 'text', 'likes',
      'reposts', 'replies', 'quotes', 'views', 'url'];
    const USER_COLS = ['id', 'username', 'name', 'followers', 'following',
      'posts', 'created_at', 'location', 'bio', 'verified'];
    
    function onOpen() {
      SpreadsheetApp.getUi().createMenu('X data')
        .addItem('Set API key', 'setApiKey')
        .addItem('Import new search results', 'importSearch')
        .addItem('Export followers', 'exportFollowers')
        .addToUi();
    }
    
    function setApiKey() {
      const ui = SpreadsheetApp.getUi();
      const answer = ui.prompt('Paste your Sorsa API key');
      if (answer.getSelectedButton() !== ui.Button.OK) return;
      PropertiesService.getUserProperties()
        .setProperty('SORSA_API_KEY', answer.getResponseText().trim());
    }
    
    // One request: GET params go in the URL, POST params in a JSON body.
    function sorsa_(method, path, params) {
      const key = PropertiesService.getUserProperties()
        .getProperty('SORSA_API_KEY');
      if (!key) throw new Error('Run X data > Set API key first.');
      const options = { method: method, headers: { ApiKey: key },
        muteHttpExceptions: true };
      let url = API + path;
      if (method === 'get') {
        url += '?' + Object.keys(params).map((k) => [].concat(params[k])
          .map((v) => k + '=' + encodeURIComponent(v)).join('&')).join('&');
      } else {
        options.contentType = 'application/json';
        options.payload = JSON.stringify(params);
      }
      for (let attempt = 1; attempt <= 3; attempt++) {
        const res = UrlFetchApp.fetch(url, options);
        const code = res.getResponseCode();
        if (code === 200) return JSON.parse(res.getContentText());
        if (code !== 429) throw new Error(code + ' ' + res.getContentText());
        Utilities.sleep(1000 * attempt); // over 20 requests a second
      }
      throw new Error('429 three times in a row');
    }
    
    function sheet_(name, cols) {
      const book = SpreadsheetApp.getActiveSpreadsheet();
      const sh = book.getSheetByName(name) || book.insertSheet(name);
      if (sh.getLastRow() === 0) {
        sh.getRange('A:A').setNumberFormat('@'); // 19-digit IDs stay text
        sh.appendRow(cols);
        sh.setFrozenRows(1);
      }
      return sh;
    }
    
    function append_(sh, rows) {
      if (!rows.length) return;
      sh.getRange(sh.getLastRow() + 1, 1, rows.length, rows[0].length)
        .setValues(rows);
    }
    
    // Text that starts with = + or - would be read as a formula.
    function safe_(s) {
      s = s || '';
      return /^[=+-]/.test(s) ? "'" + s : s;
    }
    
    function tweetRow_(t) {
      const u = t.user || {};
      return [t.id, new Date(t.created_at), '@' + u.username,
        safe_(t.full_text), t.likes_count || 0, t.retweet_count || 0,
        t.reply_count || 0, t.quote_count || 0, t.view_count || 0,
        'https://x.com/' + u.username + '/status/' + t.id];
    }
    
    function userRow_(u) {
      return [u.id, u.username, safe_(u.display_name), u.followers_count,
        u.followings_count, u.tweets_count, new Date(u.created_at),
        safe_(u.location), safe_(u.description), u.verified];
    }
    
    // New posts for SEARCH, newest first, until the first ID already in
    // the sheet. They are appended oldest first, so the sheet reads as a log.
    function importSearch() {
      const sh = sheet_('Search', TWEET_COLS);
      const last = sh.getLastRow();
      const seen = new Set(last > 1 ?
        sh.getRange(2, 1, last - 1, 1).getDisplayValues().flat() : []);
      const rows = [];
      let cursor = null;
      pages: for (let page = 0; page < MAX_PAGES; page++) {
        const body = { query: SEARCH, order: 'latest' };
        if (cursor) body.next_cursor = cursor;
        const res = sorsa_('post', '/search-tweets', body);
        for (const t of res.tweets || []) {
          if (seen.has(t.id)) break pages;
          seen.add(t.id);
          rows.push(tweetRow_(t));
        }
        cursor = res.next_cursor;
        if (!cursor) break;
      }
      append_(sh, rows.reverse());
    }
    
    // Every follower of ACCOUNT, 200 per request. Each run stops after five
    // minutes, under the six-minute limit, and the next run resumes from
    // the saved cursor. Delete FOLLOWERS_CURSOR to start a new export.
    function exportFollowers() {
      const props = PropertiesService.getUserProperties();
      let cursor = props.getProperty('FOLLOWERS_CURSOR');
      if (cursor === 'done') return;
      const sh = sheet_('Followers', USER_COLS);
      const stopAt = Date.now() + 5 * 60 * 1000;
      while (Date.now() < stopAt) {
        const params = { username: ACCOUNT };
        if (cursor) params.next_cursor = cursor;
        const res = sorsa_('get', '/followers', params);
        append_(sh, (res.users || []).map(userRow_));
        cursor = res.next_cursor;
        props.setProperty('FOLLOWERS_CURSOR', cursor || 'done');
        if (!cursor) return;
      }
    }
    
    /**
     * Profiles for up to 100 handles from one request: =X_PROFILES(A2:A101)
     * @param {string[][]} handles A column of X handles, with or without @.
     * @return {Array[]} One row per handle, in the same order.
     * @customfunction
     */
    function X_PROFILES(handles) {
      const names = [].concat(handles).flat()
        .map((h) => String(h).trim().replace(/^@/, '')).slice(0, 100);
      const ask = names.filter((h) => h);
      if (!ask.length) return '';
      const res = sorsa_('get', '/info-batch', { usernames: ask });
      const found = {};
      for (const u of res.users || []) found[u.username.toLowerCase()] = u;
      const blank = USER_COLS.map(() => '');
      return names.map((h) => found[h.toLowerCase()] ?
        userRow_(found[h.toLowerCase()]) : blank);
    }
    
    // Run once: search every hour, followers every 10 minutes until done.
    function createTriggers() {
      ScriptApp.getProjectTriggers().forEach((t) => ScriptApp.deleteTrigger(t));
      ScriptApp.newTrigger('importSearch').timeBased().everyHours(1).create();
      ScriptApp.newTrigger('exportFollowers').timeBased().everyMinutes(10)
        .create();
    }
    
  4. 保存して、シートを再読み込みします。 Ctrl+S(MacはCmd+S)を押してエディタを閉じ、スプレッドシートのタブを再読み込みします。ヘルプの隣に「X data」メニューが表示されます。

  5. キーを保存します。 X data > Set API keyを選び、キーを貼り付けます。スクリプトはキーをユーザープロパティに保存するので、セルにも、コードにも、共有するシートのコピーにも残りません。

  6. 最初のインポートを実行します。 X data > Import new search resultsを選び、Googleが求める2つの権限(このスプレッドシートへのアクセスと、外部サービスへの接続)を許可します。Searchタブが作られ、古い順に最大200件の投稿が入ります。

    トリガー 1時間ごと、または Xデータメニュー Apps Script UrlFetchApp ApiKeyヘッダー付き リクエスト JSON Sorsa API 1リクエストでツイート20件 1リクエストでフォロワー200件 1リクエストでプロフィール100件 Googleスプレッドシート 1ページ1回の書き込み、IDはテキストで保存、新しい行のみ
    ヘッダー1つ、ページごとにリクエスト1回、書き込み1回。パイプライン全体がスプレッドシートの中で完結します。

スクリプトの検索は、order を latest に設定して POST /search-tweets(新しいタブで開きます) を呼び出します。各レスポンスには最大20件の投稿が含まれ、どの投稿にも投稿者のプロフィール全体が付き、次ページ用の next_cursor も返ります。カーソルとは、次の結果ページを指すトークンのことで、最後のページではカーソルが返りません。検索ではなく、1つのアカウントの投稿でシートを埋めたい場合は、本文に username を入れて POST /user-tweets(新しいタブで開きます) を呼び出します。このエンドポイントはユーザーの全ツイートをダウンロードのガイドで詳しく解説しています。

レスポンス200 OK
{
  "tweets": [
    {
      "id": "2116630142857191424",
      "full_text": "Our Ethiopia natural roast is back on the menu.",
      "created_at": "2026-11-01T14:03:00Z",
      "lang": "en",
      "likes_count": 42,
      "retweet_count": 5,
      "reply_count": 3,
      "quote_count": 1,
      "view_count": 3810,
      "bookmark_count": 2,
      "user": {
        "id": "1500000000000000004",
        "username": "acme_coffee_roasters",
        "display_name": "Acme Coffee Roasters",
        "followers_count": 12840
      }
    }
  ],
  "next_cursor": "DAACCgACG3eLqJBAJxAKAAMbd4uokD-nGAgABAAAAAIAAA"
}

#各列に入るデータ

各投稿は10列の1行になり、そのうち3列は、JSONをそのままコピーしただけでは壊れてしまうため、注意が必要です。表は、各列とレスポンス内のフィールドの対応を示します。

列レスポンス内のフィールドスクリプトの処理
ididプレーンテキストに設定した列に書き込む
created_atcreated_atISO 8601の文字列を本物の日付に変換する
authoruser.username@を付ける
textfull_text先頭が =、+、- の場合は、前にアポストロフィを付ける
likes, reposts, replies, quotes, viewslikes_count, retweet_count, reply_count, quote_count, view_count値がない場合は0を書き込む
urluser.username と id から組み立てx.com上の投稿へのリンク
  • id

    レスポンス内のフィールド
    id
    スクリプトの処理
    プレーンテキストに設定した列に書き込む
  • created_at

    レスポンス内のフィールド
    created_at
    スクリプトの処理
    ISO 8601の文字列を本物の日付に変換する
  • author

    レスポンス内のフィールド
    user.username
    スクリプトの処理
    @を付ける
  • text

    レスポンス内のフィールド
    full_text
    スクリプトの処理
    先頭が =、+、- の場合は、前にアポストロフィを付ける
  • likes, reposts, replies, quotes, views

    レスポンス内のフィールド
    likes_count, retweet_count, reply_count, quote_count, view_count
    スクリプトの処理
    値がない場合は0を書き込む
  • url

    レスポンス内のフィールド
    user.username と id から組み立て
    スクリプトの処理
    x.com上の投稿へのリンク

シートが壊れる原因として知られている3つの落とし穴は次のとおりです。

  • ツイートIDは数値にすると桁が欠けます。 Xの投稿IDは19桁ですが、Googleスプレッドシートが保持する有効桁数は15桁なので、数値セルでは下4桁が0になり、IDが投稿を指さなくなります。スクリプトは、最初の行を書き込む前にA列をプレーンテキストに設定します。
  • 投稿テキストが数式に見えることがあります。 「=」「+」「-」で始まる投稿は、スプレッドシートが数式と解釈してエラーを表示します。スクリプトは先頭にアポストロフィを付けます。アポストロフィは表示されず、テキストはテキストのまま保たれます。
  • 日付は日付として並べ替えられるようにします。 APIは created_at をISO 8601で返すので、new Date() でそのまま読み取れ、列は日付として並べ替え、フィルタ、グラフ化ができます。

重複がシートに入ることはありません。スクリプトは検索のたびに、A列にあるIDを読み込み、最新の投稿から順に取得して、すでに保存済みの最初の投稿で停止します。新しい投稿がなかった実行は、リクエスト1回分で済みます。

#スケジュールでシートを更新し続ける

時間主導型トリガーはインポートを自動で実行します。createTriggers() を実行すると、検索を1時間ごと、フォロワーのエクスポートを完了するまで10分ごとという2つが一度に設定されます。スクリプトエディタを開き、関数リストから createTriggers を選んで、1回だけ実行してください。Googleは、シートが開いているかどうかに関係なく、自分のキーを使い、自分のアカウントとして関数を実行します。

新しい投稿が1ページ分見つかる1時間ごとの検索は、月に720リクエストを使います。Starterプランの10,000リクエストの7%です。Apps Script自体は無料で、1時間ごとの実行は、無料のGoogleアカウントの制限に十分収まります。スクリプトの設計に関わるGoogleのApps Script割り当てのページ(新しいタブで開きます)の制限は次のとおりです。

制限無料のGoogleアカウントGoogle Workspace
1日のURL Fetch呼び出し回数2万10万
1回の実行時間6分6分
1日のトリガー実行時間90分6時間
カスタム関数の実行時間30秒30秒
  • 1日のURL Fetch呼び出し回数

    無料のGoogleアカウント
    2万
    Google Workspace
    10万
  • 1回の実行時間

    無料のGoogleアカウント
    6分
    Google Workspace
    6分
  • 1日のトリガー実行時間

    無料のGoogleアカウント
    90分
    Google Workspace
    6時間
  • カスタム関数の実行時間

    無料のGoogleアカウント
    30秒
    Google Workspace
    30秒

時間主導型トリガーは、1、5、10、15、30分ごと、または指定した時刻に1日1回の間隔でも実行できます。トリガーによる実行が失敗すると、Googleがスクリプトの発生させたエラーを添えてメールで知らせます。

知っておきたいこと

検索は MAX_PAGES ページ、デフォルトでは200件の投稿で止まります。トラフィックの多いクエリで、2回の実行の間に新しい投稿が200件を超えた場合、古いものから順に取りこぼされます。ブランド名やアクセスの多いハッシュタグでは、MAX_PAGES を増やすか、トリガーの実行間隔を短くしてください。

#フォロワーリスト全体をGoogleスプレッドシートにエクスポートする

アカウントの全フォロワーをGoogleスプレッドシートにエクスポートするには、GET /followers(新しいタブで開きます) を200プロフィールずつ順に呼び出し、各ページを追記します。フォロワー1万人のリストなら50リクエストです。X data > Export followersを選ぶと、スクリプトが、各フォロワーのID、ハンドル、名前、フォロワー数とフォロー数、投稿数、登録日、所在地、プロフィール文、認証済みフラグをFollowersタブに書き込みます。

長いリストは1回の実行では収まらないため、スクリプトはそれを前提に作られています。Apps Scriptは実行を6分で停止するので、exportFollowers() は5分間動作してからカーソルをユーザープロパティに保存して終了します。次の実行は、メニューからでも10分ごとのトリガーからでも、そのカーソルから再開します。カーソルのないページを受け取った実行が、エクスポートの完了を記録します。フォロワー30万人のリストは200件のページ1,500枚で、必要な回数の実行にわたってタブが埋まっていき、同じページを2度取得することはありません。

1回目 最長5分 200 200 ... 1ページあたりのフォロワー数 カーソルを 保存 2回目 10分後 200 200 ... カーソルから再開 最終回 next_cursorなし 完了 以降は停止 フォロワー30万人 = 200件のページ1,500回分。必要な回数だけ実行して、Followersタブに書き込みます。 各実行はApps Scriptの6分制限内に収まります。
保存したカーソルが、6分の制限を、どんな長さのエクスポートにも変えます。

価格差が最も大きいのがフォロワーです。公式X APIは返したフォロワー1人につき$0.010を課金するので、10万人のリストは$1,000かかります。Sorsa APIなら同じリストを$0.70から、フォロワー1,000人あたり$0.007から取得できます。無料の100リクエストで、最初の2万フォロワーをカバーできます。フォロワーリストをシートではなくCSVやExcelファイルとして保存するには、Twitterフォロワーのエクスポートをご覧ください。

#1つの数式でプロフィール表を作る

=X_PROFILES(A2:A101) は、最大100件のハンドルの列を、1リクエストでプロフィールの表に変えます。A列にハンドルを入力し(@の有無は問いません)、B2に数式を入れると、行はハンドルごとに、同じ順序で右と下に展開されます。存在しないアカウントの行は空白になります。この関数は GET /info-batch(新しいタブで開きます) を呼び出し、1リクエストで最大100件のプロフィールを、1,000件あたり$0.014からで取得します。

カスタム関数にはGoogleのルールが適用されます。詳細はGoogleのカスタム関数のガイド(新しいタブで開きます)にあります。

  • 30秒以内に終了する必要があります。リクエスト1回なら余裕を持って収まります。
  • 範囲内のハンドルが変わったときにだけ、再実行されます。カウントを更新するには、範囲内のセルを編集するか、数式を入力し直します。
  • キーはスプレッドシートの所有者のユーザープロパティから読み込まれるため、キーを共有しなくても、シートを編集できる全員が使えます。
  • NOW() や RAND() は絶対に渡さないでください。揮発性の引数を持つカスタム関数は、Googleによって「読み込んでいます...」のまま固まります。

フォロワー数の日付つき履歴が必要な場合は、数式はライブビュー用にとっておき、その行を毎日のトリガーでログ用のタブにコピーしてください。

#コストと、1つのシートに入る量

Sorsa APIの課金は行ごとではなくリクエストごとなので、シートの料金は1リクエストが返す行数で決まります。どの種類の行でも、料金は公式X APIのごく一部です。表は、スクリプトが書き込むデータの種類ごとに、1,000行あたりの料金を示します。

シート上のデータSorsa API、1,000行あたり1リクエストの行数公式X API、1,000行あたり
検索またはタイムラインのツイート$0.07から20$5.00
アカウントのフォロワー$0.007から200$10.00
ハンドルのリストから取得するプロフィール$0.014から100$10.00
  • 検索またはタイムラインのツイート

    Sorsa API、1,000行あたり
    $0.07から
    1リクエストの行数
    20
    公式X API、1,000行あたり
    $5.00
  • アカウントのフォロワー

    Sorsa API、1,000行あたり
    $0.007から
    1リクエストの行数
    200
    公式X API、1,000行あたり
    $10.00
  • ハンドルのリストから取得するプロフィール

    Sorsa API、1,000行あたり
    $0.014から
    1リクエストの行数
    100
    公式X API、1,000行あたり
    $10.00

Google側にも上限があります。Googleドライブのファイル制限(新しいタブで開きます)によると、スプレッドシートには最大2,000万セルを保存できるため、スクリプトの10列なら、1つのスプレッドシートは200万行弱になります。データの種類と行数を選ぶと、2つの請求額と、シートがどれだけ埋まるかが分かります。

シートに入れるデータ
行数

公式X API

$500

1ポストあたり$0.005

Sorsa API

最安 $7

1,000ツイートあたり最安$0.07、1リクエストにつき20件

10列で100Kツイートを入れると、1シートの20Mセルのうち1Mを使います(5%)。無料の100リクエストでカバーできる件数: 2,000.

同じ行数に対する両方の請求額と、10列の場合にスプレッドシート1つのうち占める割合です。

失敗したリクエストには課金されません。プランの違いは、毎月含まれるリクエスト数だけです。

#代わりにコネクタ系アドオン、n8n、Make、Zapierを使う

Sorsa APIは、カスタムヘッダーつきのHTTPSリクエストを送れるツールなら何でも動くため、ノーコードの方法ではスクリプトが要りません。Googleスプレッドシート用のSorsa APIアドオンはありませんが、Google Workspace Marketplaceにある汎用のコネクタ系アドオンに、次の3つを渡せば同じことができます。

  • URL。例:https://api.sorsa.io/v3/followers?username=acme_coffee_roasters
  • 名前が ApiKey で、値がキーのヘッダー。キーをURLに入れてはいけません。
  • 書き込むデータとして users または tweets の配列。アドオンが結果のページ送りに対応しているなら、ページネーショントークンとして next_cursor。

自動化ツールも同じリクエストを使います。n8nでTwitter APIを使うのガイドでは、Schedule Trigger、Header Auth認証情報を設定したHTTP Requestノード、Split Outノード、行を追記するGoogle Sheetsノードでワークフローを組みます。MakeのHTTPモジュールとZapierのCustom Requestアクションも同じリクエストを送ります。設定方法は、MakeでTwitter APIを使うとZapierのガイドにあります。

ツールを一切使わずに1回だけコピーするなら、無料のエクスポートツールからCSVをダウンロードし、Googleスプレッドシートでファイル > インポート > アップロードを選びます。フォロワーリストには、無料のTwitterフォロワーをCSVにエクスポートツールが同じように使えます。

#Apps ScriptとAPIのエラーを解決する

失敗の多くは、Apps Scriptの実行ログかGoogleの失敗通知メールにメッセージとして表示され、それぞれ原因は1つです。

メッセージ原因対処
401 {"message":"invalid api key"}キーに誤字か余分なスペースがあるX data > Set API keyをもう一度実行する
403 {"message":"missing key in request header"}ヘッダーが送信されなかったヘッダー名を ApiKey のまま正確に保つ
403 でそれ以外のメッセージプランのリクエストを使い切ったアップグレードするか、ダッシュボードで追加リクエストを有効にする
429 three times in a row毎秒20リクエストを超えたスクリプトのコピーを複数同時に実行しない
Exceeded maximum execution time実行が6分を超えたか、カスタム関数が30秒を超えたMAX_PAGES を下げる。フォロワーのエクスポートは自動で再開する
Service invoked too many times for one day: urlfetchURL Fetch呼び出しが1日2万回を超えたトリガーの実行頻度を下げる
IDの末尾が0000になるA列が数値の列だったA列をプレーンテキストに設定し、行を削除して再実行する
  • 401 {"message":"invalid api key"}

    原因
    キーに誤字か余分なスペースがある
    対処
    X data > Set API keyをもう一度実行する
  • 403 {"message":"missing key in request header"}

    原因
    ヘッダーが送信されなかった
    対処
    ヘッダー名を ApiKey のまま正確に保つ
  • 403 でそれ以外のメッセージ

    原因
    プランのリクエストを使い切った
    対処
    アップグレードするか、ダッシュボードで追加リクエストを有効にする
  • 429 three times in a row

    原因
    毎秒20リクエストを超えた
    対処
    スクリプトのコピーを複数同時に実行しない
  • Exceeded maximum execution time

    原因
    実行が6分を超えたか、カスタム関数が30秒を超えた
    対処
    MAX_PAGES を下げる。フォロワーのエクスポートは自動で再開する
  • Service invoked too many times for one day: urlfetch

    原因
    URL Fetch呼び出しが1日2万回を超えた
    対処
    トリガーの実行頻度を下げる
  • IDの末尾が0000になる

    原因
    A列が数値の列だった
    対処
    A列をプレーンテキストに設定し、行を削除して再実行する

#最初のシートを埋める

TwitterのデータをGoogleスプレッドシートに取り込むのに必要なのは、API、短いApps Script、トリガーです。そしてシートのコストを決めるのはAPIです。ツイート、フォロワー、プロフィールをGoogleスプレッドシートにエクスポートするなら、Sorsa APIが向いています。1リクエスト200フォロワーでフォロワー1,000人あたり$0.007から、1リクエスト20ツイートで2006年まで遡る検索、=X_PROFILES() 数式用に1リクエスト100プロフィールです。スクリプトを貼り付けてキーを設定すれば、最初の2万フォロワーか2,000ツイートは無料です。

#よくある質問

GoogleスプレッドシートにデータをまとめるにはどのTwitter APIを使うべきですか?

TwitterのデータをGoogleスプレッドシートに取り込むなら、Sorsa APIを使ってください。1リクエストでフォロワー200件、プロフィール100件、ツイート20件を返し、フォロワーは1,000人あたり$0.007からです。Apps Script、コネクタ系アドオン、n8nのいずれも送れる ApiKey ヘッダー1つで使えます。Xの開発者アカウントもOAuthも不要で、全プランで毎秒20リクエストに対応し、有効期限のない100リクエストから始められます。公式X APIは、フォロワー1,000人あたり$10です。

Twitter広告のデータをGoogleスプレッドシートにエクスポートできますか?

広告費、広告のインプレッション、コンバージョンといったTwitter広告のデータは広告アカウントの非公開情報です。そのため、公開データAPIからではなく、X広告マネージャー自身のレポートエクスポートか、アカウントの認可を受けたXのAds APIから取得します。Sorsa APIでシートに入れられるのは、同じキャンペーンの公開側のデータ、つまり、投稿や、それが呼び込んだメンションに付く表示回数、いいね、リポスト、返信です。

Googleスプレッドシートからツイートを投稿できますか?

Googleスプレッドシートからツイートを投稿できるのは、公式X API経由だけです。開発者アプリとOAuthが必要で、1投稿あたり$0.015かかります。Sorsa APIは設計上、読み取り専用で、投稿もいいねもフォローも行わず、Xのログイン情報も求めないため、アカウントが危険にさらされることはありません。読み取りと投稿の両方を行うシートでは、読み取りにSorsa APIを、投稿に公式X APIを使ってください。

1つのGoogleスプレッドシートには何件のツイートを保存できますか?

Googleスプレッドシートには最大2,000万セルを保存できるので、ツイート1件あたり10列なら、全タブ合わせて200万件弱になります。ただし、その手前でシートは重くなるため、長期間の収集では、毎月か四半期ごとに新しいスプレッドシートを作ってください。Apps ScriptはIDを指定してスプレッドシートに書き込めるので、同じスクリプトで新しいファイルに切り替えることもできます。

TwitterのデータをGoogleスプレッドシートに無料で取り込む方法はありますか?

あります。Sorsa APIは、新規アカウントごとに、カード不要で有効期限のない100リクエストを付与します。このページのスクリプトを使えば、シートにツイート2,000件、フォロワー2万人、プロフィール1万件を取り込める量です。Apps ScriptとGoogleスプレッドシートは、無料のGoogleアカウントなら料金がかかりません。1つのファイルだけなら、無料のツイートエクスポートツールでCSVをダウンロードして、スプレッドシートにインポートできます。

#検証方法

2026年11月2日に、Sorsa APIの最新のAPIリファレンスに沿ってスクリプトを作成し、SpreadsheetApp、UrlFetchApp、PropertiesServiceの代役を用意してNode.jsで実行しました。代役には、リファレンスと同じ形式のレスポンスを渡しています。実行したのは、検索2回、カーソルから再開した3回のフォロワーエクスポート、X_PROFILES 関数、401、403、429の各経路です。掲載しているレスポンスはデモ用の値です。Googleの割り当て、スプレッドシートの制限、カスタム関数のルールはGoogleのページで、Xの料金はdocs.x.comで、いずれも同じ日に確認しました。

Sorsa APIチーム 読み取り専用のX(Twitter)APIとスクレイピングツール、Sorsa APIを開発・運営しています。2022年から本番稼働し(旧TweetScout API)、累計60億リクエスト以上を処理しています。 Sorsa APIについて

変更点
  • 全面的に書き直しました。メニュー、再開できるフォロワーのエクスポート、100プロフィールの数式を備えた完全なApps Script、確認済みのコストとGoogleの制限、コストとシートサイズを試算するプランナーを追加しています。
  • 初公開。