Blog/开发者工具/

用一个 API 做知乎专家发现:一套实用工作流 | SandBase

搭一套知乎专家发现工作流:搜用户、读资料、拉他们的回答——一把 SandBase key,不用知乎登录。

深色电影质感画面:知乎用户搜索解析成一个资料和他们的回答,汇入 agent 核心

在知乎上找领域专家,归根到底是三个动作:在一个话题里搜用户、读一个候选人的公开资料、翻一翻他写过的回答。这篇教程用 SandBase 知乎 API 把这三个动作串成一套知乎专家发现工作流——不用知乎登录,不用爬虫。端点参考保证的是响应信封(id、status、model,以及 outputs[0].data);下面出现的业务字段是示例结构,不是保证的 schema,请以一份真实响应为准核对。

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

先说结论

  • 三步:user-search-v3(搜索)→ user-info(资料)→ user-answers(回答)。
  • 每次都是 POST /v1/api/zhihu/<path>,一把 SANDBASE_API_KEY;响应共用 { id, status, model, outputs } 信封。
  • 搜索接一个 keyword;资料和回答读取接一个你从搜索结果里提取的 user_url_token。
  • 只是公开、只读数据;你这边不用登录,也不能发帖。

工作流全貌

步骤端点输入你拿到
1. 搜用户zhihu/web/user-search-v3keyword匹配的用户,带一个 url_token
2. 读资料zhihu/web/user-infouser_url_token昵称、关注数/回答数
3. 拉回答zhihu/web/user-answersuser_url_token(外加按 schema 的分页请求参数,如 offset/limit/cursor)该用户的一页回答

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

第 1 步 —— 在一个话题里搜用户

先写一个判 status 的帮助函数,再搜用户,并抓一个 url_token:

import os
import requests

BASE = "https://api.sandbase.ai/v1/api/zhihu"
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"))
    # 只有信封是保证的;用 .get() 防御式读取 outputs[0].data。
    return (body.get("outputs") or [{}])[0].get("data", {})


# 嵌套的业务字段(object/url_token/headline/follower_count)是示例结构,
# 不是保证的 schema,所以用 .get() 读取。
search = call("web/user-search-v3", {"keyword": "人工智能"})
results = search.get("data", [])
first = results[0].get("object", {}) if results else {}
url_token = first.get("url_token")
print(first.get("headline"), first.get("follower_count"), "->", url_token)

每一条搜索结果把用户包在一个 object 下,含像 url_token、headline、follower_count 和 answer_count 这样的字段。存在时,这些数量给你一个初筛,判断谁值得读。这些是示例结构、不是保证的 schema——以一份真实响应为准核对。

第 2 步 —— 读资料

用 user-info 读完整资料:

profile = call("web/user-info", {"user_url_token": url_token})
# 这些业务字段是示例结构、不是保证的 schema;用 .get()。
print(profile.get("name"), profile.get("follower_count"), profile.get("answer_count"))

资料读取可能返回像 name、headline、follower_count、answer_count 和 articles_count 这样的字段。这些是示例结构、不是保证的 schema,请以一份真实响应为准核对。存在时,它们合起来让你在拉回答之前,按影响力和产出给候选人排序。

SandBase 知乎 user-info API 参考,展示 user_url_token 参数和响应 schema user-info 返回一个用户的昵称、关注数和回答数。

第 3 步 —— 拉他们的回答

用 user-answers 读用户的一页回答。分页由请求参数驱动——按端点 schema,可能是一对 offset/limit,也可能是你在下一次调用里带上的 cursor,而不是你在响应里去跟的某个字段:

# 分页是请求参数。确切名称看端点 schema;许多知乎列表端点
# 接受 offset/limit(有的用 cursor)。要读下一页,在下一次调用里
# 递增 offset,而不是读响应里的某个字段。
result = call("web/user-answers", {"user_url_token": url_token, "offset": 0, "limit": 20})
answers = result.get("data", [])
print(len(answers), "answers on this page")

回答响应把一个 data 回答列表嵌在里面。下面是一个示例响应结构——其中的业务字段是示例结构、不是保证的 schema,请把字段名和数值当作示例,以一份真实响应为准核对:

{
  "id": "876962b1-9b17-409f-a654-4a5004199e31",
  "status": "completed",
  "model": "zhihu/web/user-answers",
  "outputs": [
    {
      "data": {
        "data": [
          { "answer_type": "…", "content": "…", "comment_count": 0 }
        ]
      }
    }
  ]
}

SandBase 知乎 user-answers API 参考,展示 user_url_token 参数和响应 schema user-answers 返回用户的回答 feed 和一个 paging 块。

把它串起来

一次最小的专家发现过程长这样:

search = call("web/user-search-v3", {"keyword": "人工智能"})
report = []

for hit in search["data"][:10]:
    user = hit.get("object")
    if not user or "url_token" not in user:
        continue  # 搜索结果里可能混入非用户对象
    report.append({
        "name": user.get("name"),
        "url_token": user["url_token"],
        "followers": user.get("follower_count"),
        "answers": user.get("answer_count"),
    })
    # 按关注数/回答数排序,再对值得读的人调 user-answers

因为每次调用共用同一个信封和同一个 call 帮助函数,加重试或速率退避是一处改动的事。要读到第一页回答之后,按 schema 跟着 paging 块走。当你需要的不止这些读取时,查线上知乎列表找到合适的端点,接入前先确认它的参数。

处理粗糙的边角

  • 从搜索 object 里提取 url_token。 user-search-v3 把每个用户包在一个 object 下,而结果里可能混入非用户类型——用之前先判 url_token 是否存在。
  • 先排序再读。 用搜索或 user-info 里的 follower_count 和 answer_count 先挑候选人,再对他们花 user-answers 的调用。
  • 用请求参数翻页。 user-answers 通过请求参数翻页——按 schema 是一对 offset/limit 或一个 cursor——所以在下一次调用里递增参数,别假设一页就是整个 feed。
  • 对 status 分支。 failed 或 timeout 的请求带 error 而没有 outputs。call 帮助函数已经强制这一点。
  • 尊重速率限制。 作为客户端韧性措施,遇到 HTTP 429 这类瞬时错误时用退避重试。
  • 只是公开数据。 不登录、不发帖,也拿不到私密/仅账号可见的内容。

为什么在 API 层做这件事

你当然可以在浏览器里搜知乎、用眼睛扫资料,但那不 scale,也给不了你可以排序的结构化数据。把这三次调用排成定时任务,就把定性的浏览变成了可度量的信号:能按 url_token 去重的候选人、能排序的关注数和回答数、以及能整份拉下来做话题分析的回答 feed。因为调用返回的是命名的 JSON 字段,每一轮都能干净地落进一张表,再和上一轮做 diff——某个话题上新冒出的专家、影响力的增长,以及谁在回答的变化。从回答文本给一个专家的相关性打分,是你在采集到的数据之上另跑的一个分析步骤。

组合这套工作流

同样的统一信封让它可组合。把话题关键词换成任意垂类,再加第四次读取——比如对一个 user_url_token 调 user-articles——它就用同一个 call 帮助函数、同一套判状态接进来。你也可以把漏斗放宽:一次过程里对几个相关关键词各跑一次 user-search-v3,按 url_token 去重候选人,先按关注数和回答数给合并后的集合排序,再拉任何回答,这样调用只花在值得细看的专家上。因为这些读取共用一种结构,从一个快速脚本走到一个定时任务,基本上只是加个退避和一个存每轮结果的地方。

常见问题

我需要知乎登录或 OAuth 吗? 不需要。你用自己的 SANDBASE_API_KEY 向 SandBase 鉴权,搜用户、读资料以及拉回答,都不需要你这边有知乎账号或 OAuth。发起这些调用时你仍然要提供一把 SandBase API key。

搜索结果和一个用户的回答怎么翻页? 带上每个端点接受的分页请求参数——下一次调用时传一对 offset/limit,不要假设固定的每页大小。具体参数名以端点参考为准,迭代前先对着一份真实响应核对。

我能用这种方式读私密或仅账号可见的数据吗? 不能。这些是公开、只读的读取——拿不到私密或需登录才可见的数据。另外,端点参考只保证 { id, status, model, outputs } 这层信封,里面的业务字段会变,请以一份真实响应为准去对准。

下一步

你现在有了一套可复用的专家发现工作流,建立在三次公开、只读的调用上。