X 社区(Communities):怎么用代码读,以及那个排序陷阱
X 社区(Communities):怎么用代码读,以及那个排序陷阱
社区是 X 上被数据工作忽略得最彻底的一块,通常是因为人们默认它要么是私密空间、要么是换了名字的列表。两个都不对。
★一个社区是一个带主题边界的信息流,有自己的成员、自己的版主、自己的帖子 —— 而对公开社区来说,这些全都可读。★
这一页讲清楚到底什么是公开的,以及一个不知道就会悄悄污染你数据的实测行为。
社区和它像的那几样东西有什么不同
| 谁控制成员 | 帖子存在哪 | |
|---|---|---|
| 社区 | ★版主审核准入★ | ★在社区内部★ |
| 列表 | 任何人单方面就能建 | 在作者自己的时间线上 |
| 话题标签 | 没人 —— 它只是一个字符串 | 到处都是 |
对数据工作真正要紧的后果: ★发到社区里的帖子住在社区里,而不是像普通帖子那样出现在作者的公开时间线上。★
这意味着: 如果你监控一个账号的时间线,而他主要在社区里发帖,★你会得出"他安静下来了"的结论,而事实并非如此★。这是一个真实且极易漏掉的盲区。
公开社区的成员、版主和帖子都是公开的 —— 全部可作为公开数据读取。
有哪些内容可读
四样,各是一个调用:
import requests
BASE = "https://api.socialapi.tech"
KEY = "your_api_key"
HDRS = {"X-API-Key": KEY}
def community(cid):
"""一个社区的全部公开信息。"""
def get(path, **extra):
r = requests.get(f"{BASE}{path}",
params={"community_id": cid, **extra},
headers=HDRS, timeout=60)
r.raise_for_status()
return r.json()["data"]
return {
"info": get("/v1/community/info"),
"moderators": get("/v1/community/moderators"),
"members": get("/v1/community/members", limit=300),
"posts": get("/v1/community/tweets", limit=200),
}
c = community("1493446837214187523")
print(f"{c['info'].get('name')} · {c['info'].get('member_count'):,} 成员")
print(f"{len(c['moderators'])} 位版主 · 取到 {len(c['posts'])} 条帖子")
成员是分页的,而且规模真的很大。 一个大社区能到六位数,所以★你要靠游标翻页而不是一次拉完★ —— 响应里带 next_cursor 正是为此。
★那个排序陷阱★
取帖子的调用有一个 ranking 参数,三个取值。最自然的假设是:它们把同一批帖子按三种方式排序。
★★并不是。它们返回的是差异很大的三批帖子。★★
在一个活跃社区上的实测:
| ranking | 返回帖子的年龄中位数 |
|---|---|
Recency(默认) |
★0.2 小时★ |
Relevance |
5.4 小时 |
Likes |
★19.9 小时★(但互动量高得多) |
★★Recency 与 Likes 的重叠度实测为 0%。★★ 不是"很低" —— 样本里没有一条共同的帖子。
⚠️ 为什么这比听起来更要紧: 如果你用默认档拉取,然后得出"这个社区一天 30 条帖子",★你测的其实只是最近几小时,别的什么都没测到★。而如果你在两次运行之间换了 ranking,你的时间序列就是在比较两个不同的总体,而输出里没有任何东西会提示你这一点。
★把这三档当作三个独立的数据集。选定一个,研究中途绝不更换。★
两个会让朴素代码出错的行为
1. 每页条数参数完全无效。
不管你要 20、50 还是 200 条,★回来的都是 21-28 条★。量要靠翻页拿,不是靠多要。 按"limit 控制每页条数"写的代码会悄悄少取。
2. 翻页会返回重复。
帖子流上大约 ★4% 的帖子会跨页重复★。必须按帖子 ID 去重,否则你的计数会虚高几个百分点 —— ★小到看起来很合理,大到足以是错的★。
seen, posts = set(), []
for page in pages: # ★按 ID 去重,不是按位置★
for p in page:
if p["id"] not in seen:
seen.add(p["id"]); posts.append(p)
而成员翻页实测连翻 39 页零重复 —— ★两个端点行为不同,不要用其中一个去推断另一个★。
这两个坑在我们的实现里都在服务端处理掉了 —— 去重和游标翻页都包含在内。
大家会问的问题
推特社区是什么? 一个带主题边界的空间,有自己的成员、版主和帖子。 ★发到它里面的帖子就住在里面。★
怎么在 X 上找社区? 通过社区标签页或链接。 ★不存在全文搜索社区的端点★ —— 发现是这块的短板。
怎么搜索社区? 用代码基本做不到。 ★知道 ID 就能读到全部;难的是找到 ID。★
能看到一个社区里有谁吗? 公开社区可以 —— 成员列表分页可读。
别人能看到我加入了哪些社区吗? 公开社区的成员身份是公开的。 ★它不是一个私密分组★,这一点常让人意外。
怎么加入一个社区? 在 App 里申请,开放社区可直接加入。★我们是只读的,不能替你加入。★
怎么创建一个社区? 在 App 里创建,有资格门槛。 创建是写操作,所以任何数据接口都做不了。
版主是谁? 有一个单独的调用返回他们 —— 有用是因为★版主塑造了这个信息流里有什么★。
社区帖子会出现在普通搜索里吗? 经常不像你预期的那样出现。 ★这正是那个盲区★ —— 一个账号可以在社区里很活跃,而公开时间线看起来很安静。
社区帖子会显示在作者主页上吗? 不像普通帖子那样。 它们属于社区这个上下文。
能取多少条帖子? 你翻多少页就有多少。 ★每页条数固定,与你请求多少无关★ —— 深度来自更多页。
我要了 200 条为什么只回 25 条? 这个端点的每页条数参数没有效果。 ★多翻页,不要多要。★
ranking 有哪些选项?
Recency、Relevance、Likes。★它们是不同的数据集,不是不同的排序。★
该用哪个 ranking? 监控用 Recency,看什么内容打中人用 Likes。 ★同一项研究里绝不混用。★
为什么两个 ranking 返回的帖子不一样? 因为它们的筛选方式不同,而不只是排序不同。 ★Recency 与 Likes 的实测重叠为 0%。★
成员会跨页重复吗? 实测成员翻页很干净,帖子不干净。 ★帖子必须按 ID 去重。★
能持续监控一个社区吗? 能 —— 定时翻页并存下新增的。 和关键词监控是同一个模式。
一个社区能有多大? 能到几十万人。 ★按游标翻页来规划,不要指望一次拉完。★
社区和列表是一回事吗? 不是。 ★列表是你在别人时间线之上建的一个视图;社区是帖子真正存在的地方。★
做主题信息流该用社区还是列表? 知道有哪些账号就用列表。 ★想要一个被审核过的群体产出什么,就用社区★ —— 包括那些你从没挑过的账号。
能拿到社区的历史帖子吗? 翻页能挖得很深,但★存档深度那套现实同样适用★ —— 往回翻到底意味着什么。
社区帖子有正常的互动数据吗? 有 —— 回复、转发、引用和点赞都照常返回。
不加入能看社区帖子吗? 公开社区可读。 ★受限社区任何人都读不了。★
私密社区能访问吗? 不能。 ★和受保护账号是同一条边界★ —— 这条边界为什么存在。
怎么追踪一个社区的增长? 定时记录成员数。 ★没有历史会提供给你★ —— 它只在你采集过的情况下存在。
能查一个账号加入了哪些社区吗? 不能反查。 ★发现的方向是 社区 → 成员,不是 成员 → 社区。★
社区适合用来找垂直领域的账号吗? ★非常适合★ —— 一份被审核过的成员名单,就是一批预先筛选好、只谈一个主题的人,而这种名单用别的办法很难攒出来。
社区会影响算法吗? 它改变的是帖子被分发到哪里,不是互动怎么计数 —— 分发怎么运作。
能导出社区数据吗? 能 —— 翻页然后写出来,和任何导出一样。
我拿到的社区帖子数为什么比预期少? 通常是 ranking 档位的问题。 ★Recency 只给你最近几小时★ —— 那是筛选,不是帖子不够。
需要一个 ID 吗? 需要,社区 ID,在它的 URL 里能看到。★每个调用都以它为键。★
简短版
★社区可读、被严重低估,而且是这个平台上做垂直账号发现的最佳来源 —— 一份被审核过的成员名单,是你用搜索攒不出来的、预先筛选好的受众。★
两件没人提醒就会坑到你的事: ★每页条数参数完全无效(要靠翻页),以及那三个 ranking 档是独立数据集而不是排序 —— Recency 与 Likes 实测重叠 0%。★
我们的 API 把社区详情、版主、成员和帖子做成四个调用,大列表带游标,帖子去重在服务端完成。只读、固定按次价格。
没有任何接口能做什么: 加入、发帖或做版主 —— 而且★私密社区对所有人都保持私密★。
延伸阅读:为什么一个账号可能看起来安静实则活跃 · 列表与其它主题信息流 · 翻页到底能回溯多远 · 互动数字是什么意思。