怎么从 Twitter/X API 迁移
从官方 X API 迁出只需改五处代码:把每个 v2 接口映射到新路径,用 API 密钥换掉 OAuth,去掉字段和 expansions,重写 JSON 解析,并换掉分页令牌。
简短回答: 该迁的是读取:官方 X API 每条帖子收费 $0.005,每个用户 $0.010,自 2026 年 11 月 22 日起,随帖子返回的每个作者再加 $0.010。要迁移它们,用 Sorsa API:一个 ApiKey 请求头,搜索每次请求 20 条推文,每 1,000 条 $0.07 起,另有 100 次免费请求用来测试这份对照表。
- 接口
POST /search-tweets- 每页条数
- 每次请求 20 条推文
- 价格
- 每 1,000 条 $0.07 起
#2023 年以来官方 X API 发生了什么变化
为官方 X API 写的代码大多数仍然能跑;2023 年以来变的是它能调用什么、返回什么,以及每个响应要花多少钱。下面这份带日期的清单来自 X 的更新日志(在新标签页打开)和它的开发者论坛通知,并标出了每项变化影响集成的哪一部分。
| 日期 | 官方 X API 的变化 | 对你的代码有什么影响 |
|---|---|---|
| 2023-03-30 | 付费档取代免费访问;Free 变成只写 | 免费应用上的读取会返回错误 |
| 2023-06-26 | Basic 和 Pro 移除关注类接口 | 这些套餐上的粉丝和关注调用停止工作 |
| 2023-09-28 | v1.1 的 search/tweets 和 users/search 被弃用 | v1.1 搜索代码需要换新接口 |
| 2026-01-19 | 搜索去掉 min_likes、min_replies、min_reposts | 用到它们的查询必须先去掉,直到 5 月 4 日 |
| 2026-02-06 | 按量付费成为模式;额度需预先购买 | 余额归零后调用即被阻止 |
| 2026-02-23 | 只有作者召唤你时才允许程序化回复 | 回复机器人会被拒绝 |
| 2026-04-20 | 自助档移除关注、点赞和引用发帖 | 这些写入调用停止工作 |
| 2026-05-04 | 搜索迁到新索引;关键词结果不再含转发 | 同一个查询返回的帖子变少 |
| 2026-06-01 和 2026-09-01 | 旧版 Basic,随后是 Pro 套餐迁到按量付费 | 月度上限变成按资源计费的账单 |
| 2026-11-22 | expansions 开始计费:每个作者 $0.010,每个媒体 $0.005 | expansions=author_id 能让一条帖子的成本翻三倍 |
2023-03-30
- 官方 X API 的变化
- 付费档取代免费访问;Free 变成只写
- 对你的代码有什么影响
- 免费应用上的读取会返回错误
2023-06-26
- 官方 X API 的变化
- Basic 和 Pro 移除关注类接口
- 对你的代码有什么影响
- 这些套餐上的粉丝和关注调用停止工作
2023-09-28
- 官方 X API 的变化
- v1.1 的
search/tweets和users/search被弃用 - 对你的代码有什么影响
- v1.1 搜索代码需要换新接口
2026-01-19
- 官方 X API 的变化
- 搜索去掉
min_likes、min_replies、min_reposts - 对你的代码有什么影响
- 用到它们的查询必须先去掉,直到 5 月 4 日
2026-02-06
- 官方 X API 的变化
- 按量付费成为模式;额度需预先购买
- 对你的代码有什么影响
- 余额归零后调用即被阻止
2026-02-23
- 官方 X API 的变化
- 只有作者召唤你时才允许程序化回复
- 对你的代码有什么影响
- 回复机器人会被拒绝
2026-04-20
- 官方 X API 的变化
- 自助档移除关注、点赞和引用发帖
- 对你的代码有什么影响
- 这些写入调用停止工作
2026-05-04
- 官方 X API 的变化
- 搜索迁到新索引;关键词结果不再含转发
- 对你的代码有什么影响
- 同一个查询返回的帖子变少
2026-06-01 和 2026-09-01
- 官方 X API 的变化
- 旧版 Basic,随后是 Pro 套餐迁到按量付费
- 对你的代码有什么影响
- 月度上限变成按资源计费的账单
2026-11-22
- 官方 X API 的变化
- expansions 开始计费:每个作者 $0.010,每个媒体 $0.005
- 对你的代码有什么影响
expansions=author_id能让一条帖子的成本翻三倍
其中两项改变了结果,却没动你的任何一行代码。自从新搜索索引(在新标签页打开)在 5 月 4 日上线,四月还好好的关键词搜索,现在返回的结果里不再有转发。而从 11 月 22 日起,同样带 expansions=author_id 的请求,原来每 1,000 条帖子 $5.00,现在最高要 $15.00,因为 X 的 expansions 通知(在新标签页打开)把每个返回的作者按一次用户读取计费。
套餐也在运行中的代码底下变了。X 在 2026 年 6 月 1 日之后的切换日期终止了旧版 Basic(在新标签页打开)订阅,并在 9 月 1 日之后终止了旧版 Pro(在新标签页打开);Pro 月付账号在切换时被充入 $5,000 的额度,并开启了自动充值。现在每次读取都从额度里扣,每条帖子 $0.005,每个用户或粉丝 $0.010,每月最多读取 300 万条帖子,完整的费率表在我们的 X API 价格拆解里。
#哪些迁到 Sorsa API,哪些留在 X
公开数据的每一种读取都可以迁:搜索、时间线、帖子和用户查询、粉丝、列表、引用、转发者、回复、Spaces、社区和趋势。Sorsa API 是一款 Twitter(X)API 与爬虫,2022 年起投入生产,累计处理请求超 60 亿次,一个密钥就能返回这一切,不需要 OAuth,也不需要 X 开发者账号,详见不用开发者账号的 Twitter API。它在设计上是只读的,所以写入类的调用还留在原处。
下面这些留在官方 X API:
- 发帖、回复、删除和媒体上传。
- 私信,以及点赞、关注、书签和屏蔽,无论是操作还是你自己账号的活动列表。
- 谁点赞了某条帖子,以及受保护账号的任何内容。
- Filtered stream、firehose、webhook 和 X Activity API:Sorsa API 是轮询的,不是推送的(轮询的写法见下文)。
- 帖子计数、Community Notes 和 Ads API。
你的 X 账号不参与这些读取,因为 Sorsa API 从不索要你的登录信息或 Cookie。拆分之后,官方 X API 只对写入按请求计费:每创建一条帖子 $0.015,带链接的帖子 $0.20。
钱主要花在读取上。就搜索和时间线读取而言,100 万条推文在官方 X API 上要 $5,000(还没算作者计费),在 Sorsa API 上 $70 起,而且每条推文自带作者,不另收费。按 ID 查询更进一步:POST /tweet-info-bulk 每次请求返回 100 条推文,每 1,000 条 $0.014 起。
官方 X API
$5,000
读取 100 万 条推文
每条帖子 $0.005,不含作者
Sorsa API
$70 起
读取 100 万 条推文
每 1,000 条 $0.07 起,含作者
#分六步迁移到 Sorsa API
六个步骤能把一套 v2 集成从盘点带到切换。前五步是改你的代码;最后一步要跑几天,期间两套 API 回答同样的读取。
-
**列出你的代码发出的每一个调用。**在代码里搜索
api.x.com/2、api.twitter.com/2和你的客户端的方法名,比如 Tweepy 的search_recent_tweets或 xdk 的posts.search_recent。在 X 的开发者控制台导出上个月按资源统计的用量,并把每个调用标记为读取或写入。 -
获取密钥,发出一个请求。注册,从控制台复制密钥,然后调用替代你最繁忙读取的那个接口。
终端 curl -X POST "https://api.sorsa.io/v3/search-tweets" \ -H "ApiKey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "from:mira_moves_reads", "order": "latest"}'响应200 OK { "tweets": [ { "id": "1852331946812457001", "full_text": "Read path moved. Writes stay where they were.", "created_at": "2026-10-30T14:05:12Z", "lang": "en", "likes_count": 41, "retweet_count": 6, "reply_count": 3, "quote_count": 1, "view_count": 2380, "conversation_id_str": "1852331946812457001", "is_reply": false, "user": { "id": "1592041833471827968", "username": "mira_moves_reads", "display_name": "Mira Moves Reads", "followers_count": 912, "verified": false } } ], "next_cursor": "DAADDAABCgABGbHk7m4WYAEKAAIZsd" } -
**把每个读取映射到对应接口。**下一节的表格和转换器,把每个 v2 读取与它的 Sorsa API 路径和参数配成对。
-
**用一个请求头换掉 OAuth、字段和 expansions。**在读取调用上,删掉 bearer 令牌或 OAuth 1.0a 签名,改为发送
ApiKey: YOUR_API_KEY。去掉tweet.fields、user.fields和expansions:所有字段和作者默认都会返回。 -
**重写解析和分页。**读取
data和includes的地方,改读tweets或users;重命名有差异的字段;把next_cursor送回去,直到它返回为空。 -
**影子运行,然后切换。**把你的读取复制一份发给 Sorsa API 跑几天,对比结果,分阶段迁移流量,并把 X 的密钥留给写入。
#把每个 X API v2 接口映射到 Sorsa API
集成可能调用的每个公开读取,在 Sorsa API 里都有一个对应的接口。下表把每个 v2 路径(按 X 的 OpenAPI 规范(在新标签页打开)所写)与它的替代接口配对,并标出一次请求返回多少;Sorsa API 按请求计费,不按条目计费,所以最后一列决定价格。
| X API v2 调用 | Sorsa API 接口 | 一次请求返回 |
|---|---|---|
GET /2/tweets/search/recent | POST /search-tweets(在新标签页打开) | 约 20 条推文 |
GET /2/tweets/search/all | POST /search-tweets,带 since: 和 until: | 约 20 条推文 |
GET /2/tweets/:id | POST /tweet-info(在新标签页打开) | 1 条推文 |
GET /2/tweets(按 ID) | POST /tweet-info-bulk(在新标签页打开) | 最多 100 条推文 |
GET /2/users/:id/tweets | POST /user-tweets(在新标签页打开) | 约 20 条推文 |
GET /2/users/:id/mentions | POST /mentions(在新标签页打开) | 约 20 条推文 |
GET /2/tweets/:id/quote_tweets | POST /quotes(在新标签页打开) | 约 20 条推文 |
GET /2/tweets/:id/retweeted_by | POST /retweeters(在新标签页打开) | 一页用户资料 |
用 conversation_id: 搜索 | POST /comments(在新标签页打开) | 约 20 条回复 |
GET /2/users/by/username/:username | GET /info(在新标签页打开) | 1 份资料 |
GET /2/users 和 GET /2/users/by | GET /info-batch(在新标签页打开) | 最多 100 份资料 |
GET /2/users/:id/followers | GET /followers(在新标签页打开) | 最多 200 份资料 |
GET /2/users/:id/following | GET /follows(在新标签页打开) | 最多 200 份资料 |
GET /2/users/search | POST /search-users(在新标签页打开) | 一页用户资料 |
GET /2/lists/:id/members | GET /list-members(在新标签页打开) | 一页用户资料 |
GET /2/lists/:id/followers | GET /list-followers(在新标签页打开) | 一页用户资料 |
GET /2/lists/:id/tweets | GET /list-tweets(在新标签页打开) | 约 20 条推文 |
GET /2/spaces/:id | GET /spaces(在新标签页打开) | 1 个 Space |
GET /2/communities/:id | POST /community-info(在新标签页打开) | 1 个社区 |
GET /2/communities/search | POST /search-communities(在新标签页打开) | 一页社区 |
GET /2/trends/by/woeid/:woeid | GET /trends(在新标签页打开) | 1 个地区的趋势 |
GET /2/usage/tweets | GET /key-usage-info(在新标签页打开) | 你的余额,免费 |
GET /2/tweets/search/recent- Sorsa API 接口
POST /search-tweets(在新标签页打开)- 一次请求返回
- 约 20 条推文
GET /2/tweets/search/all- Sorsa API 接口
POST /search-tweets,带since:和until:- 一次请求返回
- 约 20 条推文
GET /2/tweets/:id- Sorsa API 接口
POST /tweet-info(在新标签页打开)- 一次请求返回
- 1 条推文
GET /2/tweets(按 ID)- Sorsa API 接口
POST /tweet-info-bulk(在新标签页打开)- 一次请求返回
- 最多 100 条推文
GET /2/users/:id/tweets- Sorsa API 接口
POST /user-tweets(在新标签页打开)- 一次请求返回
- 约 20 条推文
GET /2/users/:id/mentions- Sorsa API 接口
POST /mentions(在新标签页打开)- 一次请求返回
- 约 20 条推文
GET /2/tweets/:id/quote_tweets- Sorsa API 接口
POST /quotes(在新标签页打开)- 一次请求返回
- 约 20 条推文
GET /2/tweets/:id/retweeted_by- Sorsa API 接口
POST /retweeters(在新标签页打开)- 一次请求返回
- 一页用户资料
用
conversation_id:搜索- Sorsa API 接口
POST /comments(在新标签页打开)- 一次请求返回
- 约 20 条回复
GET /2/users/by/username/:username- Sorsa API 接口
GET /info(在新标签页打开)- 一次请求返回
- 1 份资料
GET /2/users和GET /2/users/by- Sorsa API 接口
GET /info-batch(在新标签页打开)- 一次请求返回
- 最多 100 份资料
GET /2/users/:id/followers- Sorsa API 接口
GET /followers(在新标签页打开)- 一次请求返回
- 最多 200 份资料
GET /2/users/:id/following- Sorsa API 接口
GET /follows(在新标签页打开)- 一次请求返回
- 最多 200 份资料
GET /2/users/search- Sorsa API 接口
POST /search-users(在新标签页打开)- 一次请求返回
- 一页用户资料
GET /2/lists/:id/members- Sorsa API 接口
GET /list-members(在新标签页打开)- 一次请求返回
- 一页用户资料
GET /2/lists/:id/followers- Sorsa API 接口
GET /list-followers(在新标签页打开)- 一次请求返回
- 一页用户资料
GET /2/lists/:id/tweets- Sorsa API 接口
GET /list-tweets(在新标签页打开)- 一次请求返回
- 约 20 条推文
GET /2/spaces/:id- Sorsa API 接口
GET /spaces(在新标签页打开)- 一次请求返回
- 1 个 Space
GET /2/communities/:id- Sorsa API 接口
POST /community-info(在新标签页打开)- 一次请求返回
- 1 个社区
GET /2/communities/search- Sorsa API 接口
POST /search-communities(在新标签页打开)- 一次请求返回
- 一页社区
GET /2/trends/by/woeid/:woeid- Sorsa API 接口
GET /trends(在新标签页打开)- 一次请求返回
- 1 个地区的趋势
GET /2/usage/tweets- Sorsa API 接口
GET /key-usage-info(在新标签页打开)- 一次请求返回
- 你的余额,免费
路径一换,有四个习惯也要跟着变。推文接口用 POST 加 JSON 请求体,而用户、列表和工具类查询仍然是 GET。凡是 v2 要数字 ID 的地方,都可以直接用用户名,所以每次调用时间线前多出来的那次查询可以去掉。tweet_link 接受完整的帖子 URL,也接受单独的 ID。另外,POST /user-tweets 能一直翻过最新的 3,200 条帖子,而 X 的用户时间线到那里就停了。
有些 v2 读取没有对应项,因为 Sorsa API 只返回任何人在公开主页上都能看到的内容:点赞的用户、一个用户的点赞和书签、帖子计数、首页时间线,以及所有数据流。Sorsa API 还能回答 v2 回答不了的问题:POST /check-follow(在新标签页打开) 一次请求就能告诉你一个账号是否关注了另一个账号,GET /about(在新标签页打开) 返回一个账号的所在国家和用户名历史。
下面的转换器每次把一个 v2 调用转成对应的 Sorsa API 请求,列出变化的字段,以及两边各 1,000 条结果的价格。
curl -G "https://api.x.com/2/tweets/search/recent" \
-H "Authorization: Bearer $X_BEARER_TOKEN" \
--data-urlencode "query=from:mira_moves_reads" \
-d "max_results=100" \
-d "expansions=author_id" \
-d "tweet.fields=created_at,public_metrics"
curl -X POST "https://api.sorsa.io/v3/search-tweets" \
-H "ApiKey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "from:mira_moves_reads", "order": "latest"}'
- Sorsa API 接口
POST /search-tweets- 一次请求返回
- 约 20 条推文
- 官方 X API,1,000 条结果
- $5.00,作者另加最多 $10.00
- Sorsa API,1,000 条结果
- 低至 $0.07
分页: next_token 变为 next_cursor (放在 JSON body 里)。同一条查询可回溯到 2006 年,而不是 7 天,并且每条推文都带有作者。
选择你的代码用的 v2 调用,查看 Sorsa API 上的同一请求,以及两边 1,000 条结果各要多少钱。
如果你的代码用了 filtered stream 或 webhook
Sorsa API 没有数据流,也没有 webhook,所以一条 filtered stream 规则要改成你轮询的搜索查询。把规则作为 POST /search-tweets 的查询发送,带上 "order": "latest",加上 since_id: 和你存下的最新 ID,再沿着 next_cursor 翻页,这样即使某一分钟新增超过 20 条帖子,也一条不漏。
import os
import time
import requests
URL = "https://api.sorsa.io/v3/search-tweets"
HEADERS = {"ApiKey": os.environ.get("SORSA_API_KEY", "YOUR_API_KEY")}
RULE = '"open data" lang:en'
def new_tweets(since_id):
body = {"query": f"{RULE} since_id:{since_id}", "order": "latest"}
found = []
while True:
r = requests.post(URL, headers=HEADERS, json=body, timeout=30)
if r.status_code == 429:
time.sleep(1)
continue
r.raise_for_status()
page = r.json()
found += page.get("tweets", [])
if not page.get("next_cursor"):
return found
body["next_cursor"] = page["next_cursor"]
newest = "1852331946812457001"
while True:
tweets = new_tweets(newest)
for t in sorted(tweets, key=lambda t: int(t["id"])):
print(t["id"], t["user"]["username"], t["full_text"][:60])
if tweets:
newest = max((t["id"] for t in tweets), key=int)
time.sleep(15)
所有套餐上,一个密钥都是每秒 20 次请求,也就是每分钟 1,200 次单页轮询,由你的各条规则共用。监控任务的调度、告警和去重,见推特实时监控指南。
#重写响应解析
Sorsa API 把每条推文作为一个对象发来,作者就在里面,所以 data 与 includes 之间的关联消失了,剩下的工作就是重命名字段。
下面的字段名来自 X 的数据字典和 Sorsa API 的线上参考,读取于 2026 年 11 月 2 日。多数改动是拍平:某个指标离开 public_metrics,名字不变;少数是改名。
| X API v2 字段 | Sorsa API 字段 |
|---|---|
data[] | tweets[],账号列表则是 users[] |
text 和 note_tweet.text | full_text |
author_id 加 includes.users[] | user,完整资料 |
public_metrics.like_count | likes_count |
public_metrics.impression_count | view_count |
public_metrics.retweet_count、reply_count、quote_count、bookmark_count | 名字相同,上移一层 |
conversation_id | conversation_id_str |
referenced_tweets 加 includes.tweets[] | quoted_status、retweeted_status、in_reply_to_tweet_id |
attachments.media_keys 加 includes.media[] | entities[],含 type、link、preview |
用户的 name | display_name |
用户的 public_metrics.following_count、tweet_count | followings_count、tweets_count |
meta.next_token 和 pagination_token | next_cursor,两个方向都用它 |
含 title 和 detail 的 errors[] | {"message": "..."} |
data[]- Sorsa API 字段
tweets[],账号列表则是users[]
text和note_tweet.text- Sorsa API 字段
full_text
author_id加includes.users[]- Sorsa API 字段
user,完整资料
public_metrics.like_count- Sorsa API 字段
likes_count
public_metrics.impression_count- Sorsa API 字段
view_count
public_metrics.retweet_count、reply_count、quote_count、bookmark_count- Sorsa API 字段
- 名字相同,上移一层
conversation_id- Sorsa API 字段
conversation_id_str
referenced_tweets加includes.tweets[]- Sorsa API 字段
quoted_status、retweeted_status、in_reply_to_tweet_id
attachments.media_keys加includes.media[]- Sorsa API 字段
entities[],含type、link、preview
用户的
name- Sorsa API 字段
display_name
用户的
public_metrics.following_count、tweet_count- Sorsa API 字段
followings_count、tweets_count
meta.next_token和pagination_token- Sorsa API 字段
next_cursor,两个方向都用它
含
title和detail的errors[]- Sorsa API 字段
{"message": "..."}
ID 仍是字符串,日期仍是 ISO 8601,所以已存的数据结构不变。分页统一成一个名字:在 GET 接口里把 next_cursor 作为查询参数发送,在 POST 接口里放进 JSON 请求体,返回为空时停止。限流也变简单了。我们的 X API 速率限制指南里描述的 15 分钟窗口和 x-rate-limit-* 请求头,换成了每个密钥每秒 20 次请求这一个限制:429 表示等一秒再重试,403 表示密钥缺失或本月请求额度已用完。
须知
每页的条数不是固定的。一页搜索结果最多有 20 条推文,在还有后续页的情况下也可能更少,所以要按
next_cursor循环,永远不要按条目数量循环,否则一页偏短就会提前结束任务。
#Tweepy 和 xdk 代码,迁移前后
Tweepy 和 xdk 是免费的库;账单来自它们背后的官方 X API,所以迁移换的是客户端,不是语言。下面是同一个带作者、翻到底的搜索,先用 Tweepy,再用基于 Sorsa API 的普通 Python:
import tweepy
client = tweepy.Client(bearer_token="X_BEARER_TOKEN")
for page in tweepy.Paginator(
client.search_recent_tweets,
query="from:mira_moves_reads",
tweet_fields=["created_at", "public_metrics"],
expansions=["author_id"],
user_fields=["username", "name"],
max_results=100):
users = {u.id: u for u in page.includes.get("users", [])}
for t in page.data or []:
author = users[t.author_id]
print(t.id, author.username, t.public_metrics["like_count"])
字段列表、includes 关联和分页器从代码里消失了;多出来的只有一个请求头。xdk 代码的改法一样:Client(bearer_token=...) 和 client.posts.search_recent(...) 同样翻的是 data 和 includes,所以都换成上面的循环。迁移之后,查询的覆盖范围也更大了,因为 POST /search-tweets 在所有套餐上都能回溯搜索到 2006 年,而 v2 的近期搜索只覆盖最近 7 天。
基于 Node.js 的 twitter-api-v2 包的代码,迁移方式相同,换成 fetch 加一个请求头:
const URL = "https://api.sorsa.io/v3/search-tweets";
const headers = {
ApiKey: process.env.SORSA_API_KEY ?? "YOUR_API_KEY",
"Content-Type": "application/json",
};
const body = { query: "from:mira_moves_reads", order: "latest" };
do {
const res = await fetch(URL, {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const page = await res.json();
for (const t of page.tweets ?? []) {
console.log(t.id, t.user.username, t.likes_count);
}
body.next_cursor = page.next_cursor;
} while (body.next_cursor);
对于只读取的 Tweepy 代码,把读取迁到 Sorsa API:搜索每次请求返回 20 条推文,每 1,000 条 $0.07 起;用户资料每次请求 100 份,每 1,000 份 $0.014 起;不需要 OAuth。重试、批量和每秒 20 次请求的限速器,见 Python 调用 Twitter API指南。
#转换你的搜索查询
POST /search-tweets 接受 X 高级搜索的语法,也就是 x.com 搜索框用的那套语言,所以关键词、带引号的短语、from:、to:、lang:、conversation_id: 和话题标签都原样沿用。要改的是 v2 的 is: 和 has: 操作符及其时间参数,它们要挪进查询里:
| X API v2 | Sorsa API 查询 |
|---|---|
start_time 和 end_time 参数 | since:2026-10-01 和 until:2026-11-01 |
since_id 和 until_id 参数 | since_id:ID 和 max_id:ID(含该 ID) |
sort_order=recency 或 relevancy | 请求体里的 "order": "latest" 或 "popular" |
-is:retweet | 不用写:默认就不含转发 |
is:reply 和 -is:reply | filter:replies 和 -filter:replies |
is:quote | filter:quote |
has:media、has:images、has:links | filter:media、filter:images、filter:links |
has:video_link | filter:native_video |
min_likes:、min_reposts:、min_replies: | min_faves:、min_retweets:、min_replies: |
point_radius: | geocode: |
start_time和end_time参数- Sorsa API 查询
since:2026-10-01和until:2026-11-01
since_id和until_id参数- Sorsa API 查询
since_id:ID和max_id:ID(含该 ID)
sort_order=recency或relevancy- Sorsa API 查询
- 请求体里的
"order": "latest"或"popular"
-is:retweet- Sorsa API 查询
- 不用写:默认就不含转发
is:reply和-is:reply- Sorsa API 查询
filter:replies和-filter:replies
is:quote- Sorsa API 查询
filter:quote
has:media、has:images、has:links- Sorsa API 查询
filter:media、filter:images、filter:links
has:video_link- Sorsa API 查询
filter:native_video
min_likes:、min_reposts:、min_replies:- Sorsa API 查询
min_faves:、min_retweets:、min_replies:
point_radius:- Sorsa API 查询
geocode:
有三个细节容易让转换后的查询出错。原生转发默认被排除,除非加上 include:nativeretweets。geocode: 先写纬度,如 geocode:37.77,-122.41,5km,而 v2 的 point_radius: 先写经度。另外 max_id: 包含这个 ID 本身,而 v2 的 until_id 不含。完整的操作符列表和用法示例,见我们的 Twitter 搜索操作符指南。
#用影子运行测试,然后切换
影子运行,是在官方 X API 继续为你的应用服务的同时,把每个读取复制一份发给 Sorsa API,这样在任何东西依赖它之前,先把结果对比一遍。
这个脚本在两套 API 上对同一个已结束的日期,运行同一个查询,并比较帖子 ID。对你依赖的每个查询和每条时间线都运行一遍;用已结束的时间窗口,可以避免新帖子扭曲计数。
import os
import requests
X_URL = "https://api.x.com/2/tweets/search/recent"
X_HEADERS = {"Authorization": "Bearer " + os.environ["X_BEARER_TOKEN"]}
S_URL = "https://api.sorsa.io/v3/search-tweets"
S_HEADERS = {"ApiKey": os.environ.get("SORSA_API_KEY", "YOUR_API_KEY")}
def x_ids(query, start, end):
params = {"query": query, "start_time": start,
"end_time": end, "max_results": 100}
ids = set()
while True:
r = requests.get(X_URL, headers=X_HEADERS, params=params, timeout=30)
r.raise_for_status()
page = r.json()
ids |= {t["id"] for t in page.get("data", [])}
token = page.get("meta", {}).get("next_token")
if not token:
return ids
params["next_token"] = token
def sorsa_ids(query, since, until):
body = {"query": f"{query} since:{since} until:{until}",
"order": "latest"}
ids = set()
while True:
r = requests.post(S_URL, headers=S_HEADERS, json=body, timeout=30)
r.raise_for_status()
page = r.json()
ids |= {t["id"] for t in page.get("tweets", [])}
if not page.get("next_cursor"):
return ids
body["next_cursor"] = page["next_cursor"]
query = "from:mira_moves_reads"
a = x_ids(query, "2026-10-30T00:00:00Z", "2026-10-31T00:00:00Z")
b = sorsa_ids(query, "2026-10-30_00:00:00_UTC", "2026-10-31_00:00:00_UTC")
print(f"official X API {len(a)}, Sorsa API {len(b)}, shared {len(a & b)}")
print("only on X:", sorted(a - b)[:10])
print("only on Sorsa API:", sorted(b - a)[:10])
预期会有小的差异,而不是零差异:两次调用之间被删除的帖子会消失,而且 X 的关键词搜索自 2026 年 5 月 4 日起就不含转发。X 的近期搜索只能回溯 7 天,所以时间窗口要放在最近一周以内。等你最常用的那些读取,ID 都对得上了,就分阶段切换:
- 把两个客户端放到同一个接口后面,并用一个配置开关控制,这样任何一类读取都可以只改一个值就迁回去。
- 先迁 10% 的读取,观察错误和
GET /key-usage-info(它免费,从不计入你的请求数),然后再迁其余部分。 - 官方 X API 的密钥只留给写入,并在开发者控制台里调低它的消费上限和自动充值金额。
- 删除读取的代码路径、字段列表和旧的分页逻辑,等一个完整的计费周期过去之后再做。
#把你的读取迁出官方 X API
只读的 v2 集成迁到 Sorsa API,这个专为读取 X 公开数据而生的 Twitter API,只需一个请求头,每个调用对应一个接口,解析器也不再需要关联。搜索每 1,000 条推文 $0.07 起,而不是 $5.00;粉丝每 1,000 个 $0.007 起,而不是 $10.00;所有套餐都是每秒 20 次请求。从你最贵的那个读取开始,影子运行几天,官方 X API 只留给写入。
#常见问题
从官方 X API 迁移,该迁到哪个 Twitter API?
要读取 X 公开数据,迁到 Sorsa API:每个 v2 读取都对应一个接口,按 ID 查询推文每 1,000 条 $0.014 起,搜索 $0.07 起,而官方 X API 是 $5.00,所有套餐都是每秒 20 次请求,搜索可回溯到 2006 年。一个 API 密钥就能取代 OAuth。Sorsa API 是只读的,所以发帖仍请保留官方 X API。
从官方 X API 迁出要多久?
代码改动量取决于有多少种不同的 v2 调用,而不是你的数据量:每个调用变成一个接口、一个请求头和几个改名的字段。再加上几天的影子运行,期间两套 API 回答同样的读取,你对比 ID。发帖、私信和其他写入留在官方 X API,所以只有读取路径要迁。
把读取迁到 Sorsa API 之后,还能继续往 X 发帖吗?
能。发帖、回复和私信继续用官方 X API,读取则发给 Sorsa API。Sorsa API 在设计上是只读的:它的密钥无法操作任何 X 账号,也从不需要你的 X 登录信息,所以两者并存互不冲突。如果你的写入路径仍用 OAuth 1.0a 用户令牌签名,X 自 2026 年 9 月 21 日起已允许应用把它们换成 OAuth 2.0 令牌。
我需要先从 v1.1 迁到 v2 吗?
不需要。仍在调用 v1.1 读取的代码,可以直接映射到 Sorsa API:statuses/user_timeline 变成 POST /user-tweets,statuses/show 变成 POST /tweet-info,users/show 变成 GET /info。X 把 v1.1 列为旧版、只提供有限支持,并在 2023 年弃用了 v1.1 的搜索,所以停在 v2 意味着你得先为 OAuth 2.0 和按量付费计费重写一遍,之后还得再迁一次。
旧版 Basic 和 Pro 的 X API 套餐怎么样了?
X 把它们迁到了按量付费:旧版 Basic 订阅在 2026 年 6 月 1 日之后的切换日期终止,Pro 在 2026 年 9 月 1 日之后终止。Pro 月付账号在切换时被充入 $5,000 的额度,并开启自动充值。现在读取按资源计费,每条帖子 $0.005,每个用户 $0.010,而同样的推文在 Sorsa API 上通过搜索每 1,000 条 $0.07 起。
#我们如何核实
2026 年 11 月 2 日,我们把 X 自己的 OpenAPI 规范(版本 2.173,200 个路径)与 Sorsa API 的线上参考(45 个接口)逐一比对,做出接口对照表,并对照 X 的操作符列表核对了每个 v2 操作符,对照 Sorsa API 的搜索操作符核对了每个替代项。同一天,我们阅读了 X 的更新日志、价格页面、分页和速率限制文档、Python XDK 参考和 Tweepy 4.17.0 的客户端,以及 X 关于搜索索引、旧版套餐和 expansions 计费的论坛通知。这一轮我们没有 Sorsa API 密钥,所以展示的响应使用的是演示值;Python 和 Node.js 代码块,我们是对着一个本地模拟服务运行的,它按两套 API 文档里的结构作答。
来源
- X API changelog (docs.x.com)(在新标签页打开)
- X API pay-per-usage pricing (docs.x.com)(在新标签页打开)
- X API OpenAPI specification(在新标签页打开)
- Search operators, X API v2 (docs.x.com)(在新标签页打开)
- Search endpoints migrating to a new index (X Developers, May 2026)(在新标签页打开)
- Legacy Basic plans moving to pay-per-use (X Developers, May 21, 2026)(在新标签页打开)
- Legacy Pro plans moving to pay-per-use (X Developers, August 14, 2026)(在新标签页打开)
- X API PPU: expansions update (X Developers, October 23, 2026)(在新标签页打开)
- Python XDK (docs.x.com)(在新标签页打开)
- Sorsa API reference, OpenAPI
- Migration from the official X API (Sorsa API docs)(在新标签页打开)
- Search operators (Sorsa API docs)(在新标签页打开)