A 关注了 B 吗?查两个都不属于你的账号之间的关系

13 min readSocialAPI 工程团队

A 关注了 B 吗?查两个都不属于你的账号之间的关系

最直觉的做法,恰好是最贵的做法。

要知道一个账号有没有关注另一个,大多数代码会去翻粉丝列表找匹配。★对一个百万粉的账号,这是几千次请求,只为回答一个是/否★ —— 而且账号越大越慢。

有一个直接的做法,而大多数人漏掉的是:★这个答案有两个互相独立的半边★。


关系是有方向的

"这两个账号互相关注吗"其实是两个问题:

A → B ?     A 有没有关注 B
B → A ?     B 有没有关注 A

四种可能状态,而其中只有一种是"互关":

A 关注 B B 关注 A 含义
✅ ✅ ★互关★
✅ ❌ A 是单方面关注者
❌ ✅ B 是单方面关注者
❌ ❌ 没有关系

★把这个塌缩成一个布尔值,是这里最常见的建模错误★ —— 也是"他们有联系吗"这类功能给出令人困惑答案的根源。

两个方向来自同一个调用,这正是这个检查便宜的原因。


直接查

import requests

BASE = "https://api.socialapi.tech"
KEY  = "your_api_key"
HDRS = {"X-API-Key": KEY}

def relationship(source, target):
    """两个都不属于你的账号之间,两个方向的关系。"""
    r = requests.get(f"{BASE}/v1/user/relationship",
                     params={"source": source, "target": target},
                     headers=HDRS, timeout=60)
    r.raise_for_status()
    d = r.json()["data"]
    return {
        "follows":     d["following"],     # source → target
        "followed_by": d["followed_by"],   # target → source
        "mutual":      d["following"] and d["followed_by"],
    }

r = relationship("nasa", "esa")
print(f"nasa → esa: {r['follows']}   esa → nasa: {r['followed_by']}   互关: {r['mutual']}")

★两个账号都不需要是你的。★ 这是一次第三方对第三方的检查 —— 你读的是两个与你毫无关系的账号之间的公开关系。

这一点值得验证而不是相信: 我们用已知不对称的账号对测过,★因为一个很合理的故障模式是:接口悄悄忽略 source 参数,返回的其实是"我们自己的账号 vs target"★。对不对称的账号对跑两个方向,结果是严格镜像,这才证明 source 确实被尊重了。

★这个验证值得对任何关系类接口都做一遍,包括我们的★ —— 一个"错得很合理"的布尔值,是那种能存活好几个月的 bug。


什么时候用它、什么时候翻列表

★规则:两个账号都已知就直接查。需要发现他们是谁,才去翻列表。★

问题 做法
A 关注 B 吗? ★一次关系调用★
这 50 个账号关注 B 吗? 50 次关系调用
★谁关注了 B?★ ★翻粉丝列表★
B 的粉丝里谁也关注了 C? 翻一次列表,再逐个查

交叉点比人们预期的低。 ★直接查 50 对已知账号,比翻一份大粉丝列表更划算★ —— 因为任何有真实规模的列表都是很多页,而每次检查只是一个小请求。

⚠️ 例外: 一旦你要的是身份而不是是/否,★再多次关系检查也替代不了列表★ —— 你没法靠猜来枚举。

两种形态各是一个调用 —— 选哪个取决于你在问哪个问题。


它不能告诉你什么

响应里带的字段不止关注状态,而其中一个需要一句警告。

★★拉黑和静音状态不是第三方可见的。★★ B 有没有拉黑 A,★是任何接口都无法就"两个不属于你的账号"告诉你的★ —— 那条边界是真实且普适的,和让"谁看过我的资料"不可能的是同一条。

如果你要知道的是有没有人拉黑了你,那是完全另一套方法 —— 退出登录做对比。

★把这个端点当作只回答两个问题:A 有没有关注 B,B 有没有关注 A。★ 再多的,就是没人能跨过的边界了。


大家会问的问题

怎么查一个人有没有关注另一个人? 一次关系调用,带上两个用户名。 ★两个都不必是你的账号。★

能查两个都不属于我的账号之间的关系吗? ★能。★ 公开账号之间的关注关系本来就是公开的。

需要翻粉丝列表吗? 只要是/否就不需要。 ★只有当你要发现身份时才翻列表。★

怎么找互关? 两个方向都为真。 要算一整批的话,对两份列表求交集,而不是逐对查。

关系是对称的吗? 不对称 —— 这正是全部要点。 ★A 关注 B,完全不说明 B 关注 A。★

方向为什么重要? 因为★"他们有联系吗"把四种不同状态塌缩成了一种★,而其中三种都不是提问者的本意。

怎么查很多对? 每对一个调用。 ★对一批固定的账号对,这比翻任何大列表都便宜。★

什么时候粉丝列表更好? 当你要的是"谁"而不是"是否"。 ★靠检查是枚举不出身份的。★

能查有没有人拉黑我吗? 用这个不行。 ★拉黑不是第三方可见的★ —— 真正的方法。

能查两个账号有没有互相拉黑吗? 不能。 对不属于你的账号,这不向任何人开放。

能查谁静音了谁吗? 不能。 ★静音在设计上就是隐形的★,任何方向都是。

对受保护账号有效吗? 只对公开关系有效。 ★受保护账号不暴露自己的关系。★

答案有多新? 调用那一刻的实时状态。 ★一分钟前发生的关注也会反映出来。★

能追踪关注关系什么时候变的吗? 只能反复查并存下结果。 ★不存在关注变化通知★ —— 追踪怎么做。

能知道这个关注是什么时候发生的吗? 不能。 ★关注关系不带时间戳★ —— 只有你自己拍的快照才能给它定日期。

如果用户名改了会怎样? 检查会失败,或者解析到错误的账号。 ★任何要存下来的东西都用 ID★ —— 为什么。

如果账号被封了呢? 关系无法解析。 ★要处理这个错误,而不是当成"没有关注"。★

"following"和"好友"是一回事吗? 不是。 ★X 没有需要双方同意的加好友★ —— 关注是单向的。

这算一次请求吗? 算,而且是很小的一次 —— 这正是它在定向检查上胜过翻页的原因。

能查某人有没有关注我吗? 能,把你的用户名作为 target。 ★不过对你自己的账号,粉丝列表能一次给你全部。★

怎么验证一个接口真的尊重了 source 参数? ★拿一对不对称的账号跑两个方向。★ 如果两次调用不成镜像,说明 source 被忽略了 —— 这是个值得尽早抓出来的 bug。

接口为什么会忽略 source? 因为底层平台调用往往是相对会话的。 ★不处理这一点的服务商,返回的是他们自己的关系,不是你要查的那个。★

能用它建一张关注图谱吗? 对一批已知账号可以 —— 把每一对都查一遍。★要做发现仍然需要列表。★

查多少对算太多? 当你要查的已经是一个大集合里的大部分配对时,翻列表再在本地求交会更便宜。

转发意味着关注吗? 不意味着。 ★任何人都能转发任何人★ —— 这是两个无关的信号。

能看历史上的关注关系吗? 只能看你自己记录过的。 ★没有历史会提供给你。★

这和粉丝数是一回事吗? 不是 —— 粉丝数是一个聚合值,这个是某一对具体账号。

能检测到软拉黑吗? 只能通过它的效果: ★一个悄无声息消失的关注★ —— 什么是软拉黑。

关注会影响我看到什么吗? 会,不过时间线里还混着推荐 —— 分发怎么运作。

能在一个调用里批量查关系吗? 不能在一个调用里 —— 但★每次都足够小,几百对用循环完全可行★。


简短版

★查 A 有没有关注 B,是一个很小的请求,而且对两个与你毫无关系的账号同样有效。★ 为了同一个问题去翻粉丝列表,是大多数代码默认走上的那条贵路。

把它建模成两个布尔值,不是一个。 ★存在四种状态,而"互关"只是其中之一。★

我们的 API 在一个调用里返回两个方向,拍平成朴素的布尔值。只读、固定按次价格 —— 而且★我们用不对称账号对做镜像测试,验证过这个方向确实是第三方对第三方★,这个检查值得对任何服务商都跑一遍。

它不会告诉你什么: 不属于你的账号之间的拉黑与静音。★那不是缺口 —— 那是让这个平台能正常用下去的边界。★

延伸阅读:读取一份完整的关注列表 · 长期追踪粉丝变化 · 查某人是否拉黑了你 · 为什么数字 ID 才是稳定的键。