Blog/开发者工具/

X/Twitter 监测 API:从搜索到作者 | SandBase

搭一套 X/Twitter 监测工作流:搜索一个话题、读一条推文的互动、给它的作者建档——一把 SandBase key,不用 X 开发者套餐,不用 OAuth。

深色电影质感画面:一个搜索 query 解析成推文卡片和一个作者资料信号,汇入 agent 核心

在 X(Twitter)上做监测,归根到底是三个动作:找到关于某话题的推文、衡量每一条的反响、以及搞清楚是谁发的。这篇教程用 SandBase X/Twitter API 把这三个动作串成一套工作流——你自己不用 X 开发者套餐,也不用 OAuth,但仍然需要一把 SandBase API key。这些是同步的请求/响应读取,所以这里的”实时”指的是按计划定时轮询,而不是流式订阅。端点参考只保证响应信封(id、status、model、outputs[0].data);下面出现的业务字段是示例结构、并非保证的 schema,所以字段名和数值都当作示例看,以真实响应为准核对。

如果你想先看完整的端点全景,从 X/Twitter 公开数据 API hub 开始。这一篇是落地的监测工作流。

先说结论

  • 三步:search-timeline(发现)→ tweet-detail(互动)→ user-profile(作者)。
  • 每个 web 调用都是 POST /v1/api/twitter/web/<endpoint>,一把 SANDBASE_API_KEY;响应共用 { id, status, model, outputs } 信封。
  • 把搜索拿到的 tweet_id 带进 tweet-detail,把 screen_name 带进 user-profile。
  • 只有信封(id、status、model、outputs[0].data)是保证的;业务字段是示例——以真实响应为准核对。
  • 只是公开、只读数据;你自己不用 X 开发者套餐,也不能发帖,但仍需要一把 SandBase API key。

工作流全貌

步骤端点输入你拿到
1. 发现twitter/web/search-timelinekeyword(可选 cursor)一条推文时间线
2. 衡量twitter/web/tweet-detailtweet_id文本、点赞、转推、浏览
3. 建档twitter/web/user-profilescreen_name粉丝和账号信号

SandBase X 端点参考,展示本工作流用到的搜索、推文和资料端点 端点的 API 参考是每个参数名和响应路径的权威来源。

第 1 步 —— 发现关于你话题的推文

以 Top 或 Latest 模式搜索一个关键词。search-timeline 接受一个可选的 cursor 请求参数,你从上一次请求里把它带过来翻页:

import os
import requests

BASE = "https://api.sandbase.ai/v1/api/twitter"
HEADERS = {
    "Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}",
    "Content-Type": "application/json",
}


def call(path: str, payload: dict) -> dict:
    resp = requests.post(f"{BASE}/{path}", headers=HEADERS, json=payload, timeout=90)
    resp.raise_for_status()
    body = resp.json()
    if body.get("status") != "completed":
        raise RuntimeError(body.get("error", {}).get("message", "request did not complete"))
    return body["outputs"][0]["data"]


search = call("web/search-timeline", {"keyword": "NASA", "search_type": "Top"})
# 看一份真实响应,读 schema 定位 tweet id;
# 把上一次请求的可选 `cursor` 请求参数带过来翻页,
# 不要依赖某个固定的响应字段名

先看一份真实响应,弄清 tweet id 在结果里的确切位置,因为只有信封是保证的,业务布局可能和任何示例都不同。然后再收集你想衡量的 id。要翻页,就传可选的 cursor 请求参数——这个值是你从上一次请求里带过来的——而不是去依赖响应体里某个固定字段。

第 2 步 —— 衡量一条推文的互动

对每个 tweet_id,读出完整推文和它的互动数:

tweet = call("web/tweet-detail", {"tweet_id": "2041690396586090592"})
print((tweet.get("display_text") or "")[:60])
# 示例输出:It's not just a phase 🌕 Artemis II astronauts captured these views...
print(tweet.get("retweets"), "retweets,", tweet.get("replies"), "replies,", tweet.get("views"), "views")
# 示例输出:17850 retweets, 1173 replies, 3880967 views

在这个示例结构里,一次推文读取可以包含像 display_text、created_at、lang、互动数(likes、retweets、replies、views、quotes、bookmarks)、entities、media 这样的字段,以及一个内嵌的 author。只有信封是保证的,所以代码用 .get() 而不是假设这些键一定存在,打印出的数值也是示例。当你只需要基本账号上下文时,这个内嵌 author 可以帮你省一次调用。下面是一个示例响应结构——业务字段不是保证的 schema,请把字段名和数值都当作示例,以一份真实响应为准核对(载荷会变、数值也会随时间变):

{
  "id": "117db5dd-1189-4830-9459-98b624960286",
  "status": "completed",
  "model": "twitter/web/tweet-detail",
  "outputs": [
    {
      "data": {
        "id": "2041690396586090592",
        "created_at": "Wed Apr 08 01:31:37 +0000 2026",
        "display_text": "It's not just a phase 🌕 ...",
        "retweets": 17850,
        "replies": 1173,
        "views": 3880967,
        "author": { "name": "NASA", "screen_name": "NASA", "blue_verified": true }
      }
    }
  ]
}

SandBase X tweet-detail API 参考,展示 tweet_id 参数和响应 schema tweet-detail 返回互动数和一个内嵌的 author 对象。

第 3 步 —— 给作者建档

当你需要完整的账号画像——粉丝数、账号年龄、认证——按 screen_name 读取资料:

profile = call("web/user-profile", {"screen_name": "NASA"})
print(profile.get("name"), profile.get("rest_id"), profile.get("statuses_count"))
# 示例输出:NASA 11348282 74334
print("verified:", profile.get("blue_verified"), "| since", profile.get("created_at"))
# 示例输出:verified: True | since Wed Dec 19 20:20:32 +0000 2007

上面的字段名和数值都是示例——只有信封是保证的,所以代码用 .get() 读取,你应以一份真实响应为准核对这些业务字段。user-profile 也接受 rest_id 作为可选入参,所以你可以把拿到的那个存下来,以后重读资料就不依赖 screen name 保持不变。

SandBase X user-profile API 参考,展示 screen_name 参数和响应 schema user-profile 返回粉丝、认证和账号年龄信号。

把它串起来

一次最小的监测过程长这样:

search = call("web/search-timeline", {"keyword": "NASA", "search_type": "Top"})
report = []

for tweet_id in extract_tweet_ids(search):  # extract_tweet_ids: 你为搜索响应结构写的解析器
    tweet = call("web/tweet-detail", {"tweet_id": tweet_id})
    author = tweet.get("author", {})
    report.append({
        "tweet_id": tweet.get("id"),
        "text": tweet.get("display_text"),
        "views": tweet.get("views"),
        "retweets": tweet.get("retweets"),
        "author": author.get("screen_name"),
        "author_verified": author.get("blue_verified", False),
    })
    # 只在需要完整账号画像时才花一次 user-profile 调用

上面的代码对每个业务字段都用 .get() 读取,因为只有信封是保证的,示例字段在真实响应里可能缺失或叫别的名字。extract_tweet_ids 是伪代码——等你看过一份真实响应后,按实际的搜索响应结构去实现它。因为这篇教程里三个 web 调用共用同一个信封和同一个 call 帮助函数,加重试或速率退避是一处改动的事。要做更高量级的采集,有一个单独的 twitter/bulk/tweet-search 模型,但注意它是一个不同的异步面:它走 Unified Run API(POST /v1/run,带 model: "twitter/bulk/tweet-search" 和状态轮询),不是这里用的同步 call() 帮助函数。

处理粗糙的边角

  • 复用内嵌 author。 在这个示例结构里,tweet-detail 已经带一个基本的 author;只在需要粉丝数或账号年龄时才调 user-profile。以真实响应为准核对字段。
  • 有意识地翻页。 search-timeline 接受一个可选的 cursor 请求参数;从上一次请求里把它带过来,别假设一页就是整个结果集。
  • 对 status 分支。 failed 或 timeout 的请求带 error 而没有 outputs。call 帮助函数已经强制这一点。
  • 尊重速率限制。 作为客户端韧性措施,遇到 HTTP 429 这类瞬时错误时用退避重试。
  • 只是公开数据。 你自己不用 X 开发者套餐,不能发帖、回复、私信,也读不了受保护账号——但每次调用都用一把 SandBase API key 鉴权。

为什么在 API 层做监测

你当然可以在客户端里盯一列搜索流,但那给不了你结构化、可存储的数据。把这三次调用排成定时任务,就把持续的浏览变成了可度量的信号:能做趋势的浏览量和转推数、能按 tweet_id 去重的推文、以及能按粉丝数和认证加权的作者。因为调用返回的是命名的 JSON 字段,每一轮都能干净地落进一张表,再和上一轮做 diff——新的峰值、新的声音,以及一个话题反响的变化。

同样的统一信封让这套工作流可组合。把关键词换成任意话题,加一次 post-comments 调用去读某个帖子的回复,它就用同一个 call 帮助函数、同一套错误处理接进来。这就是一把 key、一种结构的 API 在监测上的回报:你把时间花在解读信号上,而不是花在维持一条脆弱管道的存活上。

常见问题

需要 X 开发者套餐或 OAuth 吗? 不需要。你用 SANDBASE_API_KEY 向 SandBase 鉴权。这些读取端点不用 X 开发者账号,你这边也不用 OAuth。

搜索结果怎么翻页? search-timeline 接一个可选的 cursor 请求参数,你从上一次请求里把它带过来取下一页。看一份真实响应,弄清下一个 cursor 值在哪里,有意识地翻页,别假设一次调用就是整个结果集。

更高量级的采集怎么做? 这里的三步都是同步单次读取。要做更高量级,有一个单独的 twitter/bulk/tweet-search 模型,但它是一个不同的异步面:它走 Unified Run API(POST /v1/run,带状态轮询),不是这套工作流里用的同步 call() 帮助函数。

下一步

你现在有了一套可复用的”话题到作者”监测工作流,建立在三次公开、只读的调用上。