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

在 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-timeline | keyword(可选 cursor) | 一条推文时间线 |
| 2. 衡量 | twitter/web/tweet-detail | tweet_id | 文本、点赞、转推、浏览 |
| 3. 建档 | twitter/web/user-profile | screen_name | 粉丝和账号信号 |
端点的 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 }
}
}
]
}
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 保持不变。
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() 帮助函数。
下一步
你现在有了一套可复用的”话题到作者”监测工作流,建立在三次公开、只读的调用上。