跳到正文

把 Twitter 数据导出到 Google Sheets

Google Sheets 自己拉不到 Twitter 数据,推文和粉丝要靠 Apps Script 定时调用 API 才能进入表格;一个电子表格最多容纳 2,000 万个单元格,约 200 万条推文。

  • 更新于
  • 发布于
  • 阅读约 12 分钟

简短回答: 数据进入表格的每一条路线,背后都是一个返回 JSON 的 API。官方 X API 每条帖子收费 $0.005,每个粉丝收费 $0.010,还需要开发者应用。要导入 Google Sheets,用 Apps Script 调用 Sorsa API:只需一个 ApiKey 请求头,每次请求返回 20 条推文或 200 个粉丝,粉丝每 1,000 个 $0.007 起,并有 100 次免费请求可以先试。

接口
POST /search-tweets
每页条数
每次请求 20 条推文
价格
每 1,000 条推文 $0.07 起

#Twitter 数据如何进入 Google Sheets

Twitter 数据进入 Google Sheets 有四种方式,而且四种都需要一个返回 JSON 的数据源:用 Apps Script 定时调用 API;用连接器类插件,在表格里发送带请求头的 URL;用 n8n、Make 或 Zapier 这类自动化工具,加一个 Google Sheets 步骤;或者先下载 CSV 文件,再用“文件 > 导入”带进来。只需要一次性文件的话,免费的把 Twitter 推文导出为 CSV工具可以直接生成 CSV。

公式算不上第五种方式。IMPORTXML 和 IMPORTHTML 只能读取单个网页,而 x.com 不会向未登录的访客展示粉丝列表或搜索结果,所以公式没有可翻页的内容,x.com 一改版式,公式也就失效了。

路线需要配置什么能否自动刷新能否翻页取完结果
Apps Script(本指南)一段脚本,粘贴一次能,用定时触发器能,任意长度
连接器插件在插件表单里填 URL 和请求头能,按插件自己的计划取决于插件
n8n、Make 或 Zapier带 Google Sheets 步骤的场景能能,需要循环
CSV 文件下载,再“文件 > 导入”不能由工具完成
  • Apps Script(本指南)

    需要配置什么
    一段脚本,粘贴一次
    能否自动刷新
    能,用定时触发器
    能否翻页取完结果
    能,任意长度
  • 连接器插件

    需要配置什么
    在插件表单里填 URL 和请求头
    能否自动刷新
    能,按插件自己的计划
    能否翻页取完结果
    取决于插件
  • n8n、Make 或 Zapier

    需要配置什么
    带 Google Sheets 步骤的场景
    能否自动刷新
    能
    能否翻页取完结果
    能,需要循环
  • CSV 文件

    需要配置什么
    下载,再“文件 > 导入”
    能否自动刷新
    不能
    能否翻页取完结果
    由工具完成

数据源决定了成本和工作量。官方 X API 需要开发者账号、应用和 OAuth,其按量付费方案读取每条帖子收费 $0.005,每个粉丝或用户资料收费 $0.010,每月最多读取 300 万条帖子,详见 X 的价格页面(在新标签页打开)。从 2026 年 11 月 22 日起,随帖子一起返回的作者资料也按 $0.010 计费,所以一条带作者的帖子要 $0.015(见 X 在 10 月 23 日的通知(在新标签页打开))。除了表格之外,获取 X 公开数据的所有方式,在如何抓取 Twitter 数据这篇指南里都有对比。

#用 Sorsa API 把推文导入 Google Sheets

Sorsa API 是一款 Twitter(X)API 与爬虫,只要 HTTP 请求带一个 ApiKey 请求头,它就会响应,而这正是 Apps Script 的 UrlFetchApp 所需要的全部。要把推文、粉丝和用户资料拉进 Google Sheets,用 Sorsa API:每次请求 20 条推文或 200 个粉丝,搜索可回溯到 2006 年,所有套餐统一每秒 20 次请求,不需要 X 开发者账号、OAuth 应用,也不需要登录 X。

下面的脚本会在你的电子表格里加一个“X data”菜单。它可以导入某个搜索的新推文,导出某个账号的完整粉丝列表,还提供一个 =X_PROFILES() 公式。配置一共六步。

  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 询问的两项权限:访问这个电子表格,以及连接外部服务。表格里会出现一个 Search 标签页,最多 200 条推文,最早的在最上面。

    触发 每小时一次,或 X data 菜单 Apps Script UrlFetchApp 带 ApiKey 请求头 请求 JSON Sorsa API 每次请求 20 条推文 每次请求 200 个粉丝 每次请求 100 份资料 Google 表格 每页写一次,ID 存为文本,只写最新的行
    一个请求头,每页一次请求,每页一次写入:整条管道都在你的电子表格里运行。

脚本的搜索调用的是 POST /search-tweets(在新标签页打开),并把 order 设为 latest。每次响应最多包含 20 条推文,每条都带作者的完整资料,另有一个 next_cursor 指向下一页。cursor 是指向下一页结果的令牌,最后一页返回时没有它。如果想填入某个账号的推文,而不是搜索结果,就调用 POST /user-tweets(在新标签页打开),在请求体里传 username;这个接口在下载某个用户的全部推文里有更详细的介绍。

Response200 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"
}

#每一列里写入什么

每条推文变成一行,共十列,其中有三列需要特别处理,直接复制 JSON 会出错。下表列出了每一列对应响应中的哪个字段。

列响应中的字段脚本的处理
idid写入设为纯文本格式的列
created_atcreated_at把 ISO 8601 字符串转成真正的日期
authoruser.username加上 @
textfull_text开头是 =、+ 或 - 时,前面加一个撇号
likes, reposts, replies, quotes, viewslikes_count、retweet_count、reply_count、quote_count、view_count数值缺失时写 0
url由 user.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 上的这条推文

三个陷阱,每一个都是表格出错的常见原因:

  • 推文 ID 按数字存储会丢位数。 X 的推文 ID 有 19 位,而 Google Sheets 只保留 15 位有效数字,所以数字单元格会把后四位变成零,ID 就不再指向这条推文。脚本在写入第一行之前,先把 A 列设为纯文本格式。
  • 推文文本可能被当成公式。 以“=”“+”或“-”开头的推文会被 Sheets 当作公式,显示错误。脚本在前面加一个撇号,Sheets 会隐藏它,并把内容按文本保存。
  • 日期应该按日期排序。 API 返回的 created_at 是 ISO 8601 格式,new Date() 可以直接读取,所以这一列能按日期排序、筛选和画图。

重复数据不会进入表格。每次搜索之前,脚本会读取 A 列已有的 ID,先请求最新的推文,遇到第一条已保存的推文就停下来,所以没有新内容的那次运行只花一次请求。

#让表格按计划自动更新

定时触发器可以自动运行导入,createTriggers() 一次设好两个:每小时搜索一次,以及每 10 分钟导出一次粉丝,直到导出完成。打开脚本编辑器,在函数列表里选 createTriggers,点一次“运行”。无论表格是否打开,Google 都会以你的身份、用你的密钥运行这些函数。

每小时搜索一次,如果每次只有一页新推文,一个月用 720 次请求,占 Starter 套餐 1 万次的 7%。Apps Script 本身免费,每小时一次的运行频率,在免费 Google 账号的限制之内绰绰有余。下面是 Google 的 Apps Script 配额页面(在新标签页打开)里,对脚本设计有影响的几项限制:

限制免费 Google 账号Google Workspace
每天 URL Fetch 调用次数2 万10 万
单次执行的运行时间6 分钟6 分钟
每天触发器运行时间90 分钟6 小时
自定义函数的运行时间30 秒30 秒
  • 每天 URL Fetch 调用次数

    免费 Google 账号
    2 万
    Google Workspace
    10 万
  • 单次执行的运行时间

    免费 Google 账号
    6 分钟
    Google Workspace
    6 分钟
  • 每天触发器运行时间

    免费 Google 账号
    90 分钟
    Google Workspace
    6 小时
  • 自定义函数的运行时间

    免费 Google 账号
    30 秒
    Google Workspace
    30 秒

定时触发器还可以设为每 1、5、10、15 或 30 分钟运行一次,或者每天在你指定的整点运行一次。触发的运行失败时,Google 会给你发邮件,附上脚本抛出的错误。

须知

搜索在达到 MAX_PAGES 页后停止,默认是 200 条推文。如果某个热门查询在两次运行之间新增了超过 200 条推文,其中最早的那些会被跳过。对流量大的品牌名和话题标签,请调大 MAX_PAGES,或者更频繁地运行触发器。

#把完整的粉丝列表导出到 Google Sheets

要把某个账号的全部粉丝导出到 Google Sheets,就对 GET /followers(在新标签页打开) 每次翻 200 个用户资料,逐页追加;1 万个粉丝的列表要 50 次请求。选择“X data > Export followers”,脚本会在 Followers 标签页里填入每个粉丝的 ID、用户名、名称、粉丝数和关注数、推文数、加入日期、所在地、简介和认证标记。

长列表一次运行放不下,脚本就是为此设计的。Apps Script 会在 6 分钟时终止任何一次执行,所以 exportFollowers() 只工作五分钟,把 cursor 存进你的用户属性后返回。下一次运行,无论来自菜单还是 10 分钟的触发器,都从这个 cursor 接着取,拿到没有 cursor 的一页时,就把导出标记为完成。30 万个粉丝的列表是 1,500 页、每页 200 个,会分成需要的若干次运行填满标签页,任何一页都不会重复取。

第 1 次运行 最长 5 分钟 200 200 ... 每页粉丝数 游标 已保存 第 2 次运行 10 分钟后 200 200 ... 从游标处开始 最后一次运行 无 next_cursor 完成 之后的运行停止 30 万个粉丝 = 1,500 页,每页 200,分多次运行写入 Followers 标签页。 每次运行都在 Apps Script 的 6 分钟限制内结束。
保存下来的 cursor,把 6 分钟的限制变成了可以导出任意长度的列表。

粉丝是价格差距最大的地方。官方 X API 每返回一个粉丝收费 $0.010,所以 10 万个粉丝的列表要 $1,000;Sorsa API 返回同样的列表只要 $0.70 起,也就是每 1,000 个粉丝 $0.007 起。你的 100 次免费请求可以覆盖前 2 万个粉丝。如果想把粉丝列表存成 CSV 或 Excel 文件,而不是表格,请看导出 Twitter 粉丝。

#用一个公式填满用户资料表

=X_PROFILES(A2:A101) 只用一次请求,就能把 A 列最多 100 个用户名变成一张用户资料表。在 A 列输入用户名,带不带 @ 都可以,在 B2 里输入公式:结果会向右、向下展开,每个用户名一行,顺序与输入一致,账号不存在的话就留一行空白。这个函数调用的是 GET /info-batch(在新标签页打开),每次请求最多返回 100 个用户资料,每 1,000 个 $0.014 起。

自定义函数要遵守 Google 对它的规定,详见 Google 的自定义函数指南(在新标签页打开):

  • 必须在 30 秒内完成,一次请求完全来得及。
  • 只有区域内的用户名变化时才会重新运行。要刷新计数,请修改区域内的某个单元格,或者重新输入公式。
  • 它从表格所有者的用户属性里读取密钥,所以所有能编辑表格的人都能用,而不必共享密钥。
  • 不要给它传 NOW() 或 RAND():Google 会让带易变参数的自定义函数永远停在“Loading...”。

如果想要带日期的粉丝数历史记录,就保留公式作为实时视图,再用每日触发器把它的行复制到一个日志标签页里。

#费用多少,一张表能放多少数据

Sorsa API 按请求计费,而不是按行计费,所以一张表的价格取决于每次请求返回多少行,而且每种行的价格都只是官方 X API 费率的零头。下表列出脚本写入的每种数据,每 1,000 行的价格。

表格里的数据Sorsa API,每 1,000 行每次请求的行数官方 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 起
    每次请求的行数
    20
    官方 X API,每 1,000 行
    $5.00
  • 某个账号的粉丝

    Sorsa API,每 1,000 行
    $0.007 起
    每次请求的行数
    200
    官方 X API,每 1,000 行
    $10.00
  • 由用户名列表得到的用户资料

    Sorsa API,每 1,000 行
    $0.014 起
    每次请求的行数
    100
    官方 X API,每 1,000 行
    $10.00

Google 这边也有上限。根据 Google 云端硬盘的文件限制(在新标签页打开),一个电子表格最多容纳 2,000 万个单元格,所以按脚本的十列算,一个电子表格能放下略少于 200 万行。选一种数据和行数,就能看到两边的账单,以及它们会占满多少表格。

表格要装的数据
行数

官方 X API

$500

每条帖子 $0.005

Sorsa API

低至 $7

每 1,000 条推文低至 $0.07,每次请求 20 条

100K 条推文,按 10 列计,占用 1M 个单元格(占一张表格 20M 个单元格的 5%)。100 次免费请求覆盖前 2,000 条。

同样行数下的两份账单,以及按十列计占用一张电子表格的比例。

失败的请求不会计费,各档套餐的区别只在于每月包含多少次请求。

#改用连接器插件、n8n、Make 或 Zapier

Sorsa API 适用于任何能发送带自定义请求头的 HTTPS 请求的工具,所以无代码路线不需要脚本。Google Sheets 没有 Sorsa API 专用插件;Google Workspace Marketplace 里的通用连接器插件也能胜任,只要你提供三样东西:

  • 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 的指南。

如果只是一次性复制,连工具都不想用,就从免费的导出工具下载 CSV,再在 Google Sheets 里选择“文件 > 导入 > 上传”。免费的把 Twitter 粉丝导出为 CSV工具对粉丝列表也是这么做的。

#排查 Apps Script 和 API 错误

大多数故障会以消息的形式出现在 Apps Script 的执行日志或 Google 的失败邮件里,而且每种都只有一个原因。

消息原因解决办法
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 调用超过 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 调用超过 2 万次
    解决办法
    降低触发器的运行频率
  • ID 以 0000 结尾

    原因
    A 列是数字列
    解决办法
    把 A 列设为纯文本格式,删除这些行,重新运行

#填满你的第一张表

把 Twitter 数据导入 Google Sheets,需要一个 API、一段简短的 Apps Script 和一个触发器,而 API 决定了这张表的成本。要把推文、粉丝和用户资料导出到 Google Sheets,用 Sorsa API:每次请求 200 个粉丝,每 1,000 个 $0.007 起;每次请求 20 条推文,搜索可回溯到 2006 年;=X_PROFILES() 公式每次请求处理 100 个用户资料。粘贴脚本,填上密钥,前 2 万个粉丝或 2,000 条推文就是免费的。

#常见问题

要把数据导入 Google Sheets,该用哪个 Twitter API?

把 Twitter 数据导入 Google Sheets,用 Sorsa API。每次请求返回 200 个粉丝、100 个用户资料或 20 条推文,粉丝每 1,000 个 $0.007 起,只需一个 ApiKey 请求头,Apps Script、连接器插件和 n8n 都能发送。它不需要 X 开发者账号或 OAuth,所有套餐统一每秒 20 次请求,并附 100 次永不过期的免费请求。官方 X API 每 1,000 个粉丝收费 $10。

能把 Twitter 广告数据导出到 Google Sheets 吗?

Twitter 广告数据,例如花费、广告展示次数和转化,属于广告账号的私有数据,所以要从 X Ads Manager 自带的报表导出,或用你账号授权的 X Ads API 获取,公开数据 API 拿不到。Sorsa API 能放进表格的,是同一个广告活动的公开部分:你的推文,以及它们引来的提及,各自的浏览量、点赞、转发和回复。

Google Sheets 能发推文吗?

Google Sheets 只能通过官方 X API 发推文,需要开发者应用和 OAuth,每条帖子收费 $0.015。Sorsa API 从设计上就是只读:它从不发帖、点赞或关注,也从不索要你的 X 登录信息,所以你的账号不会有风险。如果表格既要读取又要发帖,读取用 Sorsa API,发帖用官方 X API。

一个 Google 表格最多能放多少条推文?

一个 Google 电子表格最多容纳 2,000 万个单元格,所以每条推文占十列的话,所有标签页加起来略少于 200 万条推文。Sheets 在到达这个数量之前很早就会变慢,所以长期收集的话,请每月或每季度新建一个电子表格。Apps Script 可以按 ID 写入电子表格,所以同一段脚本可以切换到新文件。

有没有免费的办法把 Twitter 数据导入 Google Sheets?

有。Sorsa API 给每个新账号 100 次免费请求,无需绑卡,永不过期:通过本页的脚本,足够往表格里写入 2,000 条推文、2 万个粉丝或 1 万个用户资料。免费 Google 账号使用 Apps Script 和 Google Sheets 不花钱。只要单个文件的话,免费的推文导出工具会下载一个 CSV,Sheets 可以导入。

#我们如何核实

我们在 2026 年 11 月 2 日对照 Sorsa API 的实时接口文档写好脚本,然后在 Node.js 里运行:用 SpreadsheetApp、UrlFetchApp 和 PropertiesService 的替身,喂入符合文档格式的响应,覆盖两次搜索运行、一次从 cursor 续跑的三轮粉丝导出、X_PROFILES 函数,以及 401、403 和 429 这几条路径。文中展示的响应使用演示数据。Google 的配额、Sheets 的限制和自定义函数规则,是在 Google 的页面上核对的,X 的价格是在 docs.x.com 上核对的,都在同一天完成。

Sorsa API 团队撰写。 我们开发和运营 Sorsa API,这是只读的 Twitter/X API 与爬虫,2022 年起运行于生产环境(前身为 TweetScout API),累计处理请求超 60 亿次。 关于 Sorsa API

更新内容
  • 从头重写:带菜单的完整 Apps Script、可续跑的粉丝导出、一次处理 100 个用户资料的公式;核实了费用和 Google 的限制;新增成本与表格容量规划器。
  • 首次发布。