Threads 创作者研究 API 教程
搭一套 Threads 创作者研究工作流:把一批账号解析成资料记录、按传播度排序、提取 bio 链接——一个 SandBase 密钥,无需登录。

在 Threads 上做创作者研究,从一批账号开始,以一张你可以据以行动的排名表结束:谁有传播度、谁认证了、他们的受众下一步去哪。这篇教程用 SandBase Threads API 搭出这套工作流——把每个账号解析成一份公开资料、按粉丝数排序、再拉出 bio 链接。一个 SandBase 密钥,不需要登录 Threads,也不需要 SDK。
完整的端点全景见 Threads 公开数据 API 总览。这一篇是落地的研究工作流。
先说结论
- 一个端点就够用:
threads/web/user-info接一个username,每个账号调一次。- 每次都是
POST /v1/api/threads/<path>,一个SANDBASE_API_KEY;响应共用同一个信封——completed 运行才有outputs,failed 或 timeout 的运行带error、不含outputs。- 资料嵌在一个
user键下,含full_name、follower_count、is_verified、bio_links等字段——防御式读取。- 仅公开、只读数据;你这边不用登录 Threads,但仍需要一个 SandBase API 密钥。
工作流全貌
| 步骤 | 端点 | 输入 | 你拿到 |
|---|---|---|---|
| 1. 解析资料 | threads/web/user-info | username | 一份嵌在 user 下的资料记录 |
| 2. 排序并提取 | (在你代码里) | 这些记录 | 一张按粉丝数排名、带 bio 链接的表 |
端点 API 参考是每个参数名和响应路径的事实来源。
第 1 步 —— 把每个账号解析成资料
先写一个判 status 的辅助函数,再读一份资料。因为单个账号偶尔会从上游返回空,防御式读取,并跳过任何没有 user 的:
import os
import requests
BASE = "https://api.sandbase.ai/v1/api/threads"
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", "请求未完成"))
# 参考只保证信封;业务字段随端点而定,
# 用防御式读取,并以一份真实响应核对。
return body["outputs"][0]["data"]
def profile(username: str) -> dict | None:
data = call("web/user-info", {"username": username})
return data.get("user") # 偶发上游遗漏时可能缺失
p = profile("zuck")
if p:
print(p.get("full_name"), p.get("follower_count"), p.get("is_verified"))
只有信封是保证的,所以代码对每个业务字段都用 .get() 读取:completed 运行才有 outputs,failed 或 timeout 的运行带 error、不含 outputs。下面是我实测的真实响应(测试于 2026-09-27(UTC))——数值会变,请把它当作某一时刻的读数,并对照真实响应核对:
{
"id": "77059cb9-8616-4be1-abfe-96e82a12b54f",
"status": "completed",
"model": "threads/web/user-info",
"outputs": [
{
"data": {
"user": {
"full_name": "Mark Zuckerberg",
"biography": "Mostly superintelligence and MMA takes",
"follower_count": 5744972,
"is_verified": true,
"bio_links": [],
"id": "63055343223",
"pk": "63055343223"
}
}
}
]
}
user-info 把资料返回在一个 user 键下——防御式读取这个路径。
第 2 步 —— 解析一批账号
把你的候选账号过一遍同一个辅助函数,跳过任何返回空的:
HANDLES = ["zuck", "mosseri", "natgeo"]
records = []
for handle in HANDLES:
p = profile(handle)
if not p:
continue # 跳过偶发的空上游结果
records.append({
"username": handle,
"name": p.get("full_name"),
"followers": p.get("follower_count") or 0,
"verified": p.get("is_verified", False),
"bio": p.get("biography"),
"links": [l.get("url") for l in (p.get("bio_links") or []) if isinstance(l, dict)],
})
每条记录是一个扁平、可存储的行。bio_links 的提取对每个链接都防御式读取,因为这个列表可能为空、它的元素形状也可能不同——以一份真实响应核对。
读每个端点的结构;资料字段嵌在 user 下,且可能随时间变化。
第 3 步 —— 排序并出表
手里有了扁平记录,排序就是一次 sort。按粉丝数排序,把认证账号排在前面,得到一张快速研究表:
def report(records: list[dict]) -> list[dict]:
return sorted(
records,
key=lambda r: (r["verified"], r["followers"]),
reverse=True,
)
for r in report(records):
badge = "✓" if r["verified"] else " "
print(f"{badge} {r['followers']:>12,} @{r['username']} {r['name']}")
整个循环就这么多:解析、收集、排序。因为记录是带命名字段的扁平 JSON,同一张表可以直接进电子表格、数据库或 Agent 的上下文,不用重新整形。
把它串起来
一次最小的创作者研究过程长这样:
HANDLES = ["zuck", "mosseri", "natgeo"]
records = []
for handle in HANDLES:
p = profile(handle)
if p:
records.append({
"username": handle,
"name": p.get("full_name"),
"followers": p.get("follower_count") or 0,
"verified": p.get("is_verified", False),
"links": [l.get("url") for l in (p.get("bio_links") or []) if isinstance(l, dict)],
})
ranked = report(records)
因为每次调用共用同一个信封和同一个 call 辅助函数,加重试或速率退避是一处改动的事。当你需要的不止资料读取时——某用户的帖子或回复——注意这些端点以 user_id 为入口,而不是 username:把 user-info 已经返回的 pk/id 作为 user_id(可选 end_cursor 分页)传给 user-posts 或 user-replies。查线上 Threads 列表,并在接入前对照它的参考确认端点可用性,因为某些帖子和搜索读取可能不稳定。
为什么在 API 层做这件事
你当然可以在浏览器里逐个打开资料抄数字,但那超过几个账号就撑不住,也给不了你可以排序的结构化数据。通过一层统一 API 来读,意味着每个账号都返回相同的信封——completed 运行才有 outputs、资料在 user 下,而 failed 或 timeout 的运行带 error、不含 outputs——于是你的循环就几行、记录也一致。鉴权是一个密钥,重试在一个辅助函数里,排序逻辑是对扁平行的一次普通 sort。
这种一致性正是让工作流可组合的原因。扩大账号列表、给每条记录加个字段,或把排名表喂给一个打分步骤——解析循环都不用变。你的时间花在”这些资料对你的研究意味着什么”上,而不是花在抓取和重新整形上。
局限与边界
- 仅公开、只读数据。 不发帖、不关注、不涉及私有或仅账号可见的数据。
- 防御式读取。 资料嵌在
user下,个别账号偶尔会从上游返回空——跳过空的、用.get()。 - 参数与结构随上游面而定。
user-info接一个username;bio_links可能为空。先看一次真实响应、读一遍结构。 - 速率与量级。 把响应当作尽力而为的读取;遇到 HTTP 429 等瞬时错误时按退避策略重试,并控制循环节奏。
- 以线上参考核对。 可用性和字段可能变化,某些帖子/搜索读取可能不稳定;在依赖某个具体端点前先确认。
常见问题
我需要 Threads 或 Meta 登录吗?
不需要。你用自己的 SANDBASE_API_KEY 向 SandBase 鉴权。这套工作流读的是公开资料数据,不需要你这边有 Threads 账号或 OAuth。
为什么要对每个账号检查空结果?
单次 user-info 读取偶尔会从上游返回一个不带 user 对象的结果。读 data.get("user") 并跳过空的,能让批量循环稳健,而不是因为一次遗漏就崩掉。
我能读私有或仅账号可见的数据吗? 不能。这套工作流只是公开资料数据——名称、简介、粉丝数、认证状态和 bio 链接。私有和账号授权内容不在范围内。
动手搭
创建一个 SandBase API 密钥,解析一批账号,按传播度给它们排序。准备好后: