接入 X(Twitter)公开数据,稳妥的接入路径是走 v2 的 App-Only Bearer Token,调用 GET /2/tweets/search/recent 拉取近 7 天推文。真正的坑不在认证本身,而在计费模型与速率限制:接口按请求次数扣费,超出 15 分钟窗口会返回 429。下文给出对接链路、端点限流数据,以及三个高频踩坑点。
一、为什么选 App-Only Bearer Token
只读公开数据的服务端程序,没必要引导用户走 OAuth 授权流。Bearer Token 用一对 API Key/Secret 换取,换取后长期有效,无需刷新逻辑,部署门槛也低。
| 认证方式 | 适用场景 | 能否发帖/私信 | 获取复杂度 |
|---|---|---|---|
| App-Only Bearer Token | 服务端只读公开数据 | 否 | 低,无用户交互 |
| OAuth 2.0 授权码 + PKCE | 代用户操作(发帖、读私信) | 是 | 高,需同意流 |
| OAuth 1.0a 用户上下文 | 旧版用户操作、媒体上传 | 是 | 中 |
监控热点只需读公开推文,选该方案即可,省去整套用户授权与令牌刷新代码。
二、对接链路(四步走)
完整对接拆成四步:
- 在开发者后台建立 Project 与 App,复制 API Key 与 API Key Secret;
- 用
API_KEY:API_KEY_SECRET做 Base64 编码,向oauth2/token申请 Bearer Token; - 把 Token 写入环境变量
TWITTER_BEARER_TOKEN,不要硬编码进代码仓库; - 携带
Authorization: Bearer <token>调用 search/recent,解析data、includes、meta三段式响应。
下面给出换取 Token 与发起检索的精简实现:
import os
import base64
import requests
API_KEY = os.environ["TWITTER_API_KEY"]
API_KEY_SECRET = os.environ["TWITTER_API_KEY_SECRET"]
def get_bearer_token() -> str:
cred = base64.b64encode(f"{API_KEY}:{API_KEY_SECRET}".encode()).decode()
resp = requests.post(
"https://api.twitter.com/oauth2/token",
headers={
"Authorization": f"Basic {cred}",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
},
data="grant_type=client_credentials",
)
resp.raise_for_status()
return resp.json()["access_token"]
def search_recent(bearer: str, query: str, max_results: int = 100):
resp = requests.get(
"https://api.twitter.com/2/tweets/search/recent",
headers={"Authorization": f"Bearer {bearer}"},
params={
"query": query,
"max_results": max_results,
"tweet.fields": "public_metrics,created_at,author_id",
"expansions": "author_id",
"user.fields": "username,name",
},
)
if resp.status_code == 429:
reset = resp.headers.get("x-rate-limit-reset")
raise RuntimeError(f"触发限流,窗口重置时间戳: {reset}")
resp.raise_for_status()
return resp.json()
返回的 JSON 不再像 v1.1 那样把用户对象平铺在推文里,而是用 includes.users 通过 author_id 关联,解析时记得按 ID 做映射。
三、核心端点与限流数据
限流按端点、按认证类型、按 15 分钟窗口分别计数。监控场景里频繁遇到的三个端点如下:
| 端点 | App-Only 限流 | 用户认证限流 | 窗口 |
|---|---|---|---|
GET /2/tweets/search/recent |
450 次 | 300 次 | 15 分钟 |
GET /2/tweets/counts/recent |
300 次 | 不适用 | 15 分钟 |
GET /2/tweets/search/stream(过滤流) |
Basic 及以上 | — | 连接级 |
每次响应会带三个关键响应头:x-rate-limit-limit 是窗口总预算,x-rate-limit-remaining 是剩余次数,x-rate-limit-reset 是窗口重置的 Unix 时间戳。生产环境应记录 remaining,在低于额度 10% 时告警。
四、三个高频踩坑点
7 天检索硬墙。 Basic 档的 recent search 只返回近 7 天推文,再往前一律查不到。要回溯历史必须升到 Pro 或 Enterprise 的 full-archive search。
429 必须退避。 命中窗口上限会返回 429,正确做法是读 x-rate-limit-reset 后等待,或做指数退避(从 1 秒起,每次翻倍至 60 秒封顶),而不是立刻重试。
按量计费叠加在限流之上。 留在限流窗口内不代表请求免费;每次读取他人推文都按量扣费,高频轮询成本会快速累积。用 counts/recent 做音量统计比逐条拉推文便宜,实时场景则改用过滤流避免轮询。
连续触发限流时把请求分散到整个窗口,而不是开头几秒打满,能显著降低被熔断的概率。
常见问题(FAQ)
Q1:Bearer Token 会过期吗?
不会自动过期,主动作废前长期有效,但泄露后需立即在后台重置。
Q2:免费档能读推文做监控吗?
新注册账号已无免费读权限,需开通付费档,否则无法调用搜索接口。
Q3:过滤流比轮询省在哪?
过滤流由平台推送匹配推文,省去主动轮询的请求次数与计费开销。