Blog/开发者工具/

Threads 创作者研究 API 教程

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

深色电影质感画面:一批 Threads 账号解析成排名的资料卡,汇入 Agent 内核

在 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-infousername一份嵌在 user 下的资料记录
2. 排序并提取(在你代码里)这些记录一张按粉丝数排名、带 bio 链接的表

SandBase Threads 端点参考,展示本工作流用到的 user-info 端点 端点 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"
        }
      }
    }
  ]
}

SandBase Threads user-info API 参考,展示 username 参数和响应 schema 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 的提取对每个链接都防御式读取,因为这个列表可能为空、它的元素形状也可能不同——以一份真实响应核对。

SandBase Threads 端点列表,展示资料和相关端点及其路径 读每个端点的结构;资料字段嵌在 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 密钥,解析一批账号,按传播度给它们排序。准备好后: