核心要点: 从官方 Twitter/X API 迁移到第三方 REST API 涉及四处改动:把 OAuth 换成单个 API 密钥请求头、更换基础 URL 并重新映射接口路径、把响应解析扁平化,以及把分页切换到一个游标字段。一次读取路径迁移通常需要一到三天。
作者:Sorsa 编辑部 · 更新于 2026 年 7 月 4 日:新增免费起步路径(100 次免费请求,无需绑卡),把价格改用每千次为单位,并对照当前 Sorsa v3 的 40 个接口重新核实了接口映射。
这是给已经决定离开、需要真正迁移方案的团队的技术详解:认证、接口映射、响应解析、分页、HTTP 方法和可用代码。全文以 Sorsa API(一个我们自己开发并运营的 Twitter/X API 替代方案)作为迁移目标,因为从官方 v2 接口过来的映射是现有方案里最干净的之一。把 OAuth 换成一个 ApiKey 请求头、按请求计费(一次批量调用返回最多 100 条推文或资料)、所有套餐统一 20 次/秒,加上无需等待的开发者账号审批,正是一次读取路径迁移落在几天而不是几周的实际原因。读取从每 1,000 条推文 $0.02、每 1,000 份资料 $0.01 起;注册即送 100 次免费请求(无需绑卡、一次性),足够在投入之前跑完一整轮并行对比测试。如果你还在挑服务商,先看我们的 Twitter/X API 替代方案对比;本指南假设那个决定已经做完。
我们自 2022 年起为团队跑过这类迁移,累计处理请求超 50 亿次,所以下面的步骤遵循一份固定清单,而不是泛泛而谈。原则适用于任何只读 REST 服务商;接口名称、响应字段和代码则是 Sorsa 专有的。
目录
- 从 Twitter/X API 迁移涉及什么?
- 第 1 步:把 OAuth 换成一个 API 密钥
- 第 2 步:更换基础 URL 并重新映射接口
- 第 3 步:扁平化响应解析并重映射字段
- 第 4 步:把分页切换到游标
- 第 5 步:在需要处把 GET 换成 POST
- 第 6 步:迁移代码
- 第 7 步:保留你的搜索查询并处理错误
- 迁移清单
- 实战:一个学术团队的存档迁移
- 常见问题
- 我们如何核实本指南
- 如何开始
从 Twitter/X API 迁移涉及什么? {#what-does-migrating-from-the-twitterx-api-involve}
从官方 Twitter/X API 迁移到按请求计费的 REST API,整体上是做减法:移除 OAuth、删掉字段选择字符串、丢掉响应外壳,再重新映射几个接口路径。核心工作触及认证、接口路径、响应解析和分页,中等规模的读取路径代码库需要一到三天。
在讲分步细节之前,先看完整变更集一览:
- 把
Authorization: Bearer ...换成一个ApiKey请求头。 - 把基础 URL 从
https://api.x.com/2换成https://api.sorsa.io/v3。 - 重新映射接口路径(表在第 2 步)。
- 把推文和搜索接口从 GET 换成 POST(第 5 步)。
- 删除
tweet.fields、user.fields和expansions:每个字段都默认返回。 - 扁平化解析器:移除
data、includes和meta外壳。 - 重命名字段:
name改display_name,text改full_text,指标移到顶层。 - 把
pagination_token和next_token换成next_cursor。 - 简化错误处理:错误以单个 message 字段返回。
下表映射了与迁移相关的差异。这是这里唯一要紧的对比,因为它决定了你有多少代码要改。
| 维度 | 官方 X API v2 | Sorsa API |
|---|---|---|
| 认证 | OAuth 2.0 bearer(用户上下文用 1.0a) | 单个 ApiKey 请求头 |
| 基础 URL | https://api.x.com/2 | https://api.sorsa.io/v3 |
| 字段选择 | tweet.fields、user.fields、expansions | 默认返回所有字段 |
| 响应结构 | data / includes / meta 外壳 | 扁平对象,作者内联 |
| 分页 | pagination_token / next_token | next_cursor |
| HTTP 方法(推文、搜索) | GET | POST |
| 批量 | 有限 | 每次调用最多 100 条推文或资料 |
| 速率限制 | 按接口,15 分钟窗口 | 所有套餐统一 20 次/秒 |
| 计费单位 | 按抓取的资源 | 按请求 |
| 写操作 | 发帖、私信(关注、点赞、引用仅限 Enterprise) | 只读 |
除一处外,每一行都是简化:官方 API 能向 X 写入,只读 API 不能。如果你发帖、发私信或投广告,那条路径仍留在官方 API 上。对读取公开数据来说,这次更换移除了 OAuth、字段选择层和逐接口的速率窗口,那就是迁移的绝大部分。
第 1 步:把 OAuth 换成一个 API 密钥 {#step-1-replace-oauth-with-an-api-key}
认证是删代码最多的改动。官方 API 对仅应用请求用 OAuth 2.0 bearer 令牌,对任何需要用户上下文的东西用 OAuth 1.0a(消费者密钥、access token、每次请求的 HMAC 签名)。只读服务商用请求头里的一个密钥把这一整套替换掉。
之前,在官方 API 上:
curl -X GET "https://api.x.com/2/users/by/username/elonmusk" \
-H "Authorization: Bearer AAAAAAAAAAAAAAAAAAAA..."
之后:
curl -X GET "https://api.sorsa.io/v3/info?username=elonmusk" \
-H "ApiKey: YOUR_API_KEY"
没有令牌刷新、没有签名生成、没有回调 URL。生成一次密钥、存进一个环境变量,此后每个请求都带同一个 ApiKey 请求头。完整参考见认证文档。
第 2 步:更换基础 URL 并重新映射接口 {#step-2-swap-the-base-url-and-remap-endpoints}
每个官方 v2 路径都映射到一个新路径。多数调用只改 URL,而推文接口还要改 HTTP 方法。
基础 URL 变成 https://api.sorsa.io/v3。用户接口这样映射:
| 动作 | 官方 X API v2 | Sorsa API v3 |
|---|---|---|
| 按用户名取用户 | GET /2/users/by/username/:username | GET /info?username=:username |
| 按 ID 取用户 | GET /2/users/:id | GET /info?user_id=:id |
| 多个用户 | GET /2/users?ids=... | GET /info-batch?usernames=... |
| 粉丝 | GET /2/users/:id/followers | GET /followers?user_id=:id |
| 关注 | GET /2/users/:id/following | GET /follows?user_id=:id |
| 已认证粉丝 | 不可用 | GET /verified-followers?user_id=:id |
| 账号 “about” 元数据 | 不可用 | GET /about?username=:username |
GET /info-batch 每次调用接收最多 100 个用户名或 ID。GET /followers 和 GET /follows 每页返回最多 200 份完整资料,而官方接口返回的是 ID,之后你还要用第二次调用去补全。
推文:
| 动作 | 官方 X API v2 | Sorsa API v3 |
|---|---|---|
| 单条推文 | GET /2/tweets/:id | POST /tweet-info |
| 多条推文 | GET /2/tweets?ids=... | POST /tweet-info-bulk |
| 用户时间线 | GET /2/users/:id/tweets | POST /user-tweets |
| 引用推文 | GET /2/tweets/:id/quote_tweets | POST /quotes |
| 转发用户 | GET /2/tweets/:id/retweeted_by | POST /retweeters |
| 回复(评论) | 无专用接口 | POST /comments |
| 长文文章 | 不可用 | POST /article |
POST /tweet-info-bulk 一次请求返回最多 100 条推文,循环调用 /tweet-info 则会花掉 100 次。POST /user-tweets 没有 3,200 条推文上限:用 next_cursor 一路分页到账号的第一条帖子。请求体字段 tweet_link 接受完整 URL 或纯数字 ID。削减请求数的模式,见 API 用量优化指南。
搜索:
| 动作 | 官方 X API v2 | Sorsa API v3 |
|---|---|---|
| 近期或全量存档搜索 | GET /2/tweets/search/recent | POST /search-tweets |
| 提及 | GET .../search/recent?query=@user | POST /mentions |
| 搜索用户 | 不可用 | POST /search-users |
POST /search-tweets 在同一个接口里覆盖历史搜索,而 POST /mentions 加了官方 API 不暴露的互动筛选:min_likes、min_replies、min_retweets、since_date 和 until_date。
列表、社区、验证接口和分析也都有映射,其中好几个没有官方 API 对应项。列表用 GET /list-members、GET /list-followers 和 GET /list-tweets。社区(官方 API 根本不暴露)用 POST /community-members、POST /community-tweets 和 POST /community-search-tweets。单次调用的验证接口(POST /check-follow、GET /check-comment、POST /check-quoted、POST /check-retweet、POST /check-community-member)一次请求回答一个是/否问题,否则就得去爬完整的粉丝列表或转发用户列表。完整的逐路径表见接口映射参考。
第 3 步:扁平化响应解析并重映射字段 {#step-3-flatten-response-parsing-and-remap-fields}
这一步在认证之后触及的代码最多。官方 API 把一个响应拆成 data、includes 和 meta。只读服务商返回一个扁平对象、作者内嵌在每条推文里,所以那套“用户拼接”逻辑消失了。
一份用户资料,之前:
{
"data": {
"id": "44196397",
"name": "Elon Musk",
"username": "elonmusk",
"public_metrics": {
"followers_count": 100000000,
"following_count": 500,
"tweet_count": 30000
}
}
}
之后:
{
"id": "44196397",
"username": "elonmusk",
"display_name": "Elon Musk",
"followers_count": 100000000,
"followings_count": 500,
"tweets_count": 30000,
"verified": false,
"created_at": "2009-06-02T20:12:29Z"
}
字段重命名很小,但测试时容易漏。映射一次,其余就顺了:
| 官方 X API v2 | Sorsa API | 备注 |
|---|---|---|
name | display_name | 重命名 |
text | full_text | 重命名 |
public_metrics.followers_count | followers_count | 已扁平化 |
public_metrics.following_count | followings_count | 已扁平化,多一个 “s” |
public_metrics.tweet_count | tweets_count | 已扁平化并重命名 |
public_metrics.like_count | likes_count | 已扁平化,多一个 “s” |
public_metrics.retweet_count | retweet_count | 已扁平化,无 “s” |
public_metrics.impression_count | view_count | 已扁平化并重命名 |
conversation_id | conversation_id_str | 重命名 |
in_reply_to_user_id | in_reply_to_username | 用户名,而非 ID |
author_id 加 includes.users[] | user(完整对象内联) | 内嵌 |
通过 includes 引用的推文 | quoted_status、retweeted_status | 内联 |
有一处不一致值得点出来,免得浪费你的调试时间:likes 和 follows 是复数(likes_count、followings_count),而 retweet_count、reply_count 和 quote_count 保持单数。作者资料在每个推文响应里都位于 user 之下,所以你在官方 API 上维护的那张 includes.users 查找表可以直接删掉。
第 4 步:把分页切换到游标 {#step-4-switch-pagination-to-a-cursor}
分页收缩成一个字段。官方 API 在查询里用 pagination_token、返回 meta.next_token;只读服务商在响应顶层用 next_cursor。
对 GET 接口,把 next_cursor 作为查询参数传;对 POST 接口,把它放进 JSON 请求体:
curl -X POST "https://api.sorsa.io/v3/search-tweets" \
-H "ApiKey: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "from:elonmusk", "next_cursor": "ABC123" }'
响应是扁平的,next_cursor 缺失或为 null 就说明你已经到达最后一页:
{ "tweets": [], "next_cursor": "XYZ789" }
完整模式,包括粉丝列表分页,见游标分页文档。
第 5 步:在需要处把 GET 换成 POST {#step-5-switch-get-to-post-where-required}
这是团队容易忘、然后调试十分钟的改动。在官方 API 上,推文和搜索读取是 GET。在这个 REST API 上,任何接收推文标识符或搜索查询的都变成 POST,而任何接收用户标识符或列表 ID 的仍是 GET。
| 动作 | 官方 API | Sorsa API |
|---|---|---|
| 获取一条推文 | GET | POST |
| 搜索推文 | GET | POST |
| 用户时间线 | GET | POST |
| 引用推文、转发用户 | GET | POST |
| 回复(评论) | 不适用 | POST |
| 用户资料 | GET | GET |
| 粉丝、关注 | GET | GET |
| 列表 | GET | GET |
经验法则:推文标识符或搜索查询意味着 POST;用户标识符或列表 ID 意味着 GET。
第 6 步:迁移代码 {#step-6-migrate-the-code}
下面是三个有代表性的迁移,分别用 curl、Python 和 JavaScript 呈现。这些是你在读取路径代码库里会反复用到的模式。
获取一份用户资料
之前:
import requests
r = requests.get(
"https://api.x.com/2/users/by/username/elonmusk",
params={"user.fields": "public_metrics,verified,created_at"},
headers={"Authorization": f"Bearer {BEARER_TOKEN}"},
)
user = r.json()["data"]
followers = user["public_metrics"]["followers_count"]
name = user["name"]
之后:
import requests
r = requests.get(
"https://api.sorsa.io/v3/info",
params={"username": "elonmusk"},
headers={"ApiKey": API_KEY},
)
user = r.json()
followers = user["followers_count"]
name = user["display_name"]
字段选择字符串没了,指标也在顶层。
搜索推文
之前,作者拼接是必需的:
const params = new URLSearchParams({
query: "from:elonmusk since:2024-01-01",
"tweet.fields": "created_at,public_metrics",
expansions: "author_id",
"user.fields": "username,name",
});
const res = await fetch(`https://api.x.com/2/tweets/search/recent?${params}`, {
headers: { Authorization: `Bearer ${BEARER_TOKEN}` },
});
const data = await res.json();
const users = Object.fromEntries((data.includes?.users || []).map(u => [u.id, u]));
for (const t of data.data || []) {
console.log(t.text, "by", users[t.author_id].username);
}
之后,每条推文已经带着作者:
const res = await fetch("https://api.sorsa.io/v3/search-tweets", {
method: "POST",
headers: { ApiKey: API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ query: "from:elonmusk since:2024-01-01" }),
});
const data = await res.json();
for (const t of data.tweets) {
console.log(t.full_text, "by", t.user.username);
}
用户拼接表消失了,因为作者内嵌在每条推文里。
分页遍历每一个粉丝
def fetch_all_followers(user_id, api_key):
url = "https://api.sorsa.io/v3/followers"
headers = {"ApiKey": api_key}
followers, next_cursor = [], None
while True:
params = {"user_id": user_id}
if next_cursor:
params["next_cursor"] = next_cursor
data = requests.get(url, headers=headers, params=params).json()
followers.extend(data.get("users", []))
next_cursor = data.get("next_cursor")
if not next_cursor:
break
return followers
每页返回最多 200 份完全补全的资料,所以一次在官方 API 上需要“一次 ID 调用加一次补全调用”的粉丝关系图拉取,在这里一遍就走完。按语言的细节,我们的 Python 版 Twitter API 指南覆盖了完整的读取路径。
第 7 步:保留你的搜索查询并处理错误 {#step-7-keep-your-search-queries-and-handle-errors}
你的搜索查询原样迁移。支持相同 Twitter 高级搜索操作符的服务商,读取 from:、to:、since:、until:、带引号短语、话题标签、OR 和 -is:retweet 的方式,与官方近期搜索接口完全一致。现有的查询字符串因此不用改。
把现有查询字符串照搬过来;高级搜索语法参考列出了完整集合。mentions 接口还暴露了互动筛选(min_likes、min_replies、min_retweets、since_date、until_date),所以你在官方 API 上写的任何客户端“按互动下限过滤”逻辑,都可以搬到服务端。
错误处理也简化了。官方 API 返回一个结构化错误数组,只读服务商则返回一个单一的 message 字段,配标准状态码:400、401、403、404、429 和 500。对 429,策略是等一秒再重试,因为限制是所有接口和套餐统一 20 次/秒,没有逐接口的窗口要追踪。一个防御性的重试封装:
import time, requests
def call_with_retry(method, url, max_retries=3, **kwargs):
for attempt in range(max_retries):
r = requests.request(method, url, **kwargs)
if r.status_code == 429:
time.sleep(2 ** attempt)
continue
r.raise_for_status()
return r.json()
raise RuntimeError(f"failed after {max_retries} retries")
完整的状态码列表见错误码参考。
迁移清单 {#migration-checklist}
把这个当作一次读取路径迁移的工作清单:
- 到处把
Authorization: Bearer ...换成ApiKey请求头。 - 移除 OAuth 1.0a 签名逻辑(消费者密钥、access token、签名)。
- 把基础 URL 更新为
https://api.sorsa.io/v3。 - 用第 2 步的表重映射每一个接口路径。
- 为推文、搜索、评论、引用和转发用户接口把 GET 换成 POST。
- 删除
tweet.fields、user.fields和expansions。 - 移除
data/includes/meta的拆包。 - 在你的模型里重命名字段(
name、text、public_metrics块)。 - 把
pagination_token和next_token换成next_cursor。 - 为单一 message 字段的形态更新错误处理。
- 把速率限制逻辑设为统一 20 次/秒,无逐接口窗口。
- 部署前在在线试用工具 Playground 里测试关键接口。
- 用
key-usage-info接口追踪额度消耗(见密钥用量参考)。 - 如果你也向 X 写入,就保留官方密钥;只迁移读取路径。
实战:一个学术团队的存档迁移 {#in-practice-an-academic-groups-archival-migration}
一个学术研究团队在 2025 年年中找到我们,当时在官方 API 上跑一条为纵向研究采集推文的数据管道。有两个问题卡住了他们:用户时间线接口每个账号封顶在最近 3,200 条推文,弄坏了他们的历史覆盖;而粉丝接口返回的是裸 ID,还要第二次查询才能变成可用的资料。
读取路径迁移由一名研究者花了约两天。POST /user-tweets 移除了 3,200 条推文的上限,用 next_cursor 一路分页回每个账号的第一条帖子,存档缺口就补上了。粉丝拉取也收缩成一遍走完,因为 GET /followers 每页返回最多 200 份完整资料,这大致把那部分工作的请求数减半。唯一真正的摩擦是字段重命名(name 改 display_name、text 改 full_text,以及复数化的 likes_count),在几小时的测试里、而不是设计阶段被抓出来。具体数字因负载而异,迁移的形状不会变。
常见问题 {#frequently-asked-questions}
能一个接口一个接口地从 Twitter/X API 迁移吗?
能,而且渐进式迁移通常是更稳妥的路径。在你的数据调用前放一个薄抽象层,把一个接口指向新服务商,对照官方 API 校验它的输出几天,再迁下一个。应用代码永远不必在一次大重写里改动,而且哪里看着不对,你可以独立回滚某个接口。
如何在不弄坏生产的情况下测试 Twitter/X API 迁移?
切换之前,并行跑两个 API 并对比解析后的输出。最轻的选项是在线试用工具 Playground,它发请求而无需集成代码。更强的选项是一个并排脚本,同时调用两个 API 并比较结果;最彻底的是一个功能开关,把一定比例的流量路由到新服务商,让你能立即回滚。
迁移后 Twitter 高级搜索查询还能用吗?
能,只要服务商支持相同的高级搜索操作符。Sorsa 的 search-tweets 和 mentions 接口读取 from:、to:、since:、until:、带引号短语、话题标签、OR 和 -is:retweet 的方式,与官方近期搜索接口完全一致,所以现有查询字符串不用改就能迁移。mentions 接口还加了官方 API 不暴露的互动筛选,比如 min_likes 和 min_retweets。
迁移后如何处理速率限制和错误?
错误处理变得更简单。Sorsa 把每个错误以单一 message 字段返回,而不是官方 API 的结构化错误数组,配标准状态码(400、401、403、404、429 和 500)。速率限制是所有套餐统一 20 次/秒,没有逐接口的 15 分钟窗口。遇到 429,等一秒再重试,没有 reset 头要追踪。
迁移会移除 3,200 条推文的时间线限制吗?
会。官方 v2 用户时间线接口每个账号封顶在最近 3,200 条推文。Sorsa 的 POST /user-tweets 没有这种上限:用 next_cursor 一直分页,直到响应不再返回它,你就到了账号的第一条帖子。做存档、情感历史和训练数据工作时,这往往正是迁移发生的原因。
如果替代方案是只读的,如何继续向 X 写入?
写操作保留官方 X API,只迁移读取路径。发帖、回复、私信、点赞、关注和广告全都留在官方 API 上,它是唯一能代表用户行事的系统。多数团队最终采用混合方案:一小笔官方 API 预算用于写入,一个按请求计费的读取 API 用于高量数据拉取。Sorsa 从设计上就是只读,这也顺带移除了一类写权限和账号封禁风险。
我们如何核实本指南 {#how-we-verified-this-guide}
本详解取材于我们自 2022 年起运营 X API 替代方案的一线工作,以及我们为离开官方平台的团队跑过的读取路径迁移。接口路径、参数名、响应字段和 ApiKey 请求头对照线上的 Sorsa API 文档核实;官方 X API 的认证模型、响应外壳和按量付费费率则对照 X 的开发者文档和定价页面核查,反映了 2026 年 4 月 20 日的变化。这个决定的成本面,见我们的当前 X API 价格详解。每个代码示例都按所示可运行。于 2026 年 6 月 13 日核实。
审校:Keksich(Sorsa 创始人,X API 研究者)
如何开始 {#getting-started}
如果你的数据管道读取推文、资料、粉丝或搜索结果,给迁移划定范围最快的办法,是跑几次调用、把响应结构和你当前的解析器比一比。Sorsa API 快速上手在拿到密钥之后几分钟内让你完成第一次请求,历史数据文档则展示了时间线接口如何在没有 3,200 条推文上限的情况下回溯到 2006 年。注册即送 100 次免费请求,一次性、无需绑卡、永不过期,足够在付费之前跑完一整轮并行对比测试。之后,读取从每 1,000 条推文 $0.02、每 1,000 份资料 $0.01 起,套餐 $49/月含 1 万次请求起,每档都统一 20 次/秒,而且你和第一次调用之间没有开发者账号审批。先把一个接口放在开关后面指过去、对比输出,再迁移其余的。