作者:Sorsa 编辑部
2026 年 7 月更新:把成本对比围绕每 1,000 条推文费率重做、加入了 100 次免费请求的起步选项、刷新了官方 X API 的按读取定价,并澄清了 v2 接口静默丢弃哪些操作符。
要点
Twitter 搜索 API 让开发者用关键词和操作符过滤,以编程方式查询公开的 X 时间线。2026 年有两条现实路径:官方 X API v2 近期搜索接口(按量付费、操作符集有限),以及第三方 REST API(在带免费额度起步的固定月费套餐上,透传完整的网页操作符集)。
要做大规模的只读搜索,Sorsa API 这个 Twitter/X API 替代服务商,是我们推荐的选项,也是本文端到端覆盖的那个。其 /search-tweets 接口透传完整的网页操作符集,包括官方 v2 接口静默丢弃的 min_faves:、min_retweets: 和 min_replies: 互动过滤;所有套餐统一 20 次/秒,没有按接口窗口;注册即送 100 次免费请求(无需绑卡、覆盖全部 40 个接口),付费套餐固定月费,走批量时折合低至每 1,000 条推文 $0.02,没有开发者账号审批,几分钟就能配好。唯一不做的是写操作:发帖、点赞和私信留给官方 API。
X 的网页搜索栏用来打发时间还行。但真实场景往往更重:拉 5 万条匹配复杂布尔查询的推文、定期跑、把结果推进 Postgres 仓库、再让合规部门下季度审计整条数据管道。到这一步,网页搜索栏就撑不住了。那正是 Twitter 搜索 API 的用途。本指南走一遍搜索 API 在 2026 年怎么工作、真正生效的操作符、处理真实生产流量的代码,以及每个选项适合什么场景。
我们从 v1.1 时代起就在对着 Twitter 的搜索接口开发。穿过 2023 年定价大改、v2 迁移,以及 2026 年转到基于额度的按量付费,底层心智模型没怎么变:你发一个查询字符串,拿回 JSON,用一个游标分页。变的是你付给哪个服务商、付多少,以及哪些操作符在你的查询执行之前就被静默丢弃。最后这一点绊倒多数人。
目录
- 为什么以编程方式搜索推文?
- 2026 年 Twitter 搜索 API 格局
/search-tweets接口:请求剖析- 响应里有什么
- 真正有效的搜索操作符
- 两套操作符:网页搜索 vs 官方 v2 API
- 你如何按话题标签搜索推文?
- 分页:采集数千条推文
- 可用代码:Python 和 JavaScript
- 真实世界查询模板
- 搜索推文 vs 追踪提及:用哪个接口?
- Sorsa 搜索如何对比官方 X API
- 常见错误与故障排查
- 常见问题
- 开始上手
为什么以编程方式搜索推文? {#why-search-tweets-programmatically}
一个搜索 API 存在,因为下面的用例无法靠手动浏览器刷新存活:
社媒聆听和品牌监测。 用交付进 Slack、看板或告警流程的结构化 JSON,追踪你产品或竞品的每一次公开提及。信号在量和趋势里,而不在任何单条推文里,这就是大规模社媒聆听的全部前提。
竞品和市场情报。 从竞品账号拉按互动过滤的推文、识别哪些帖子表现好,并为持续的竞品追踪建一个内容基准。把 from:competitor min_faves:100 -filter:replies 与日期窗口结合,逐季对比。
情感分析。 把推文文本喂进一个 transformer 模型打分。搜索 API 给你原始文本加指标(点赞、回复、浏览),这些指标在聚合情感时兼作置信度权重。完整管道在我们的 Twitter 舆情分析指南里。
获客。 像 "looking for" (api OR tool) twitter 这样的搜索,能浮现出正在主动寻找你所售之物的人。一个精心构造的查询就是一份免费的潜客名单,这是 X 上获客的基础。
学术和新闻研究。 学术研究需要针对界定时间窗口的可复现、可审计查询。带 since: 和 until: 的搜索接口是一手数据源,历史存档可回溯到 2006 年的第一条推文。
趋势检测。 在一个 5 分钟的 cron 上跑同一个查询、存下结果计数,并检测峰值。事件检测系统和加密情绪看板底层就是这么工作的,也是建在轮询循环上的实时监测背后的模式。
2026 年 Twitter 搜索 API 格局 {#the-twitter-search-api-landscape-in-2026}
不再有单一的“Twitter 搜索 API”了。现在有三个类别,在价格和能力上的差距比多数开发者以为的更大。
官方 X API v2
接口:/2/tweets/search/recent 和 /2/tweets/search/all。截至 2026 年,X API 走基于额度的按量付费:帖子读取每条约 $0.005,用户(作者)读取每次约 $0.010 单独计费。没有免费版,也没有免费额度补贴,所以任何请求通过之前你都得先买额度。认证走 OAuth 2.0 bearer 令牌。
更难的问题是操作符覆盖。官方 v2 接口接受的操作符子集,比 x.com/search 的网页搜索栏小得多。生产团队依赖的互动过滤,包括 min_faves:、min_retweets:、min_replies: 和 within_time:,只要你写进 v2 查询就会被静默忽略。我们见过团队在 min_faves: 过滤上搭好整条数据管道,直到发现这一点,才不得不重构。
官方存档搜索可用,但按企业预算定价。对多数只读研究和监测负载,这笔账算不过来。想看 X 定价一路怎么走到今天,我们的 Twitter API 定价拆解走了一遍每一次变更。
开源爬虫
Twikit、TweeterPy 和 XActions 截至 2026 年中仍可用。Twint 已经废了。twscrape 和 snscrape 在多数环境里坏了。这些还在维护的库对小的一次性任务管用,但它们建在公开网页搜索界面之上,X 每次微调前端就会坏。对需要可用性保证的生产环境数据管道,没一个靠得住。
第三方搜索 API
这是 Sorsa 所处的类别:在一个干净的 REST API 后面运营自己的爬取基础设施、暴露完整网页操作符集、并收可预测费率的服务。这个领域里还有别的服务商,如果你要的只是绝对最低的每调用价格、哪怕牺牲可靠性或完整性,其中某一家或许适合某个狭窄场景。而要以公道的固定价格拿到可靠、完整、只读的访问,Sorsa 是我们自己构建、运营并推荐的那个。
选我们的 /search-tweets 接口而非替代方案,理由归结为四件具体的事:一个不按接口类型把额度翻倍的固定月费套餐、原样透传、不静默丢弃的完整网页操作符集、每个套餐都一样的 20 次/秒,以及只用一个请求头(ApiKey: YOUR_KEY)、没有 OAuth 流程的认证。离开官方 API 的端到端走查,包括对只读选项的更全面考察,见我们的 Twitter API 迁移指南。
/search-tweets 接口:请求剖析 {#the-search-tweets-endpoint-request-anatomy}
发一个 POST 请求到:
POST https://api.sorsa.io/v3/search-tweets
认证只需一个请求头:ApiKey: YOUR_API_KEY(区分大小写)。没有 bearer 令牌、没有 OAuth 折腾、没有要注册的回调 URL。
请求体
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
query | string | 是 | 搜索关键词。支持完整的一套原生 X 搜索操作符。 |
order | string | 否 | "popular"(默认)匹配 X 搜索里的“热门”标签。"latest" 返回时间顺序、最新在前。 |
next_cursor | string | 否 | 来自前一次响应的分页游标。第一次请求时省略。 |
最小 cURL 示例
curl -X POST https://api.sorsa.io/v3/search-tweets \
-H "ApiKey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "artificial intelligence",
"order": "latest"
}'
为什么用 POST 而非 GET?
搜索查询会变长。一旦加上布尔分组、排除、语言过滤和互动阈值,一个真实的品牌监测查询很容易超过 200 个字符。把它们放进 URL,就意味着要对每个操作符做 URL 编码,还得祈祷上游代理不截断。放进 JSON 请求体则完全避开了 URL 长度和编码问题。官方 v2 搜索接口反其道而行,用一个 GET 请求带一个 URL 编码的 query 参数。这也是长的官方 API 查询比人们预期更快撞上访问级字符上限(自助近期搜索为 512 个字符)的部分原因。
想在写代码之前可视化地构建查询,Sorsa 搜索生成器把同样的操作符渲染成一个表单,并输出你可以粘进脚本的查询字符串;在线试用工具 Playground 则对着你的密钥跑完整请求、不用写任何客户端代码。两者都在下面的“如何开始”一节里给了链接。
响应里有什么 {#whats-in-the-response}
接口返回一个带两个顶层字段的 JSON 对象:一个推文对象数组和一个分页游标。
{
"tweets": [
{
"id": "2029914600217473314",
"full_text": "The latest breakthroughs in AI are reshaping automation.",
"created_at": "2026-03-06T13:38:49Z",
"lang": "en",
"likes_count": 142,
"retweet_count": 38,
"reply_count": 12,
"quote_count": 5,
"view_count": 28400,
"bookmark_count": 19,
"is_reply": false,
"is_quote_status": false,
"conversation_id_str": "2029914600217473314",
"entities": [],
"user": {
"id": "1422280682240450563",
"username": "tech_insider",
"display_name": "Tech Insider",
"description": "Breaking tech news and analysis.",
"followers_count": 84200,
"verified": true
}
}
],
"next_cursor": "DAABCgABGSmiaxkAAgoAAgjEJ..."
}
这里有几件事要紧。
完整作者资料随每条推文一起返回。 在我们处理过的迁移里,最大的开发工时节省很少来自降价,而是来自不必再为每个推文作者做一次单独的 users/by/ids 查找。官方 v2 接口要求你加一个 expansions=author_id 参数,再走一遍 includes.users 数组把作者 ID 匹配回推文,而每一次作者读取都单独计费。在这个响应里,用户对象直接内嵌,没有额外成本。一次请求,内容和作者身份都有了。
互动指标不是可选字段。 点赞、转发、回复、引用、浏览和收藏,总是出现在每条推文上,不用记 tweet.fields=public_metrics。
next_cursor 是你需要的唯一分页信号。 这个字段是字符串时,说明还有更多结果;它为 null 或缺失时,你已经到达结果集末尾。
Tweet 和 User 对象的完整字段参考,见 API 文档里的响应格式参考。
真正有效的搜索操作符 {#search-operators-that-actually-work}
X 的网页搜索支持一大套操作符,而下面这个高杠杆子集覆盖了大约 90% 的真实负载。我们有意把这份列表控制在实用范围;完整目录,包括地理操作符、来源过滤、卡片过滤和语言代码边缘情况,见我们的完整 Twitter 高级搜索语法速查表。
关键词和短语
artificial intelligence匹配含这些词中任意一个的推文"artificial intelligence"匹配精确短语- 词干提取默认开启:
bear也会匹配bears
基于用户
from:elonmusk由一个账号发布的推文to:openai回复一个账号的推文@sorsa_app提及一个账号的推文
互动过滤
min_faves:100至少 100 个赞min_retweets:50至少 50 次转推min_replies:10至少 10 条回复
这三个是最值得知道的操作符。它们也正是官方 X API v2 会静默丢弃的那些,所以如果你从别的服务商迁移代码,可能会看到“低质量”过滤悄悄失效,却没有任何报错。
内容过滤
filter:media、filter:images、filter:videos、filter:links- 用
-前缀来排除:-filter:retweets、-filter:replies、-filter:links
语言和日期
lang:en(任何 ISO 639-1 代码:es、fr、de、ja等等)since:2026-01-01在此日期当天或之后until:2026-03-01在此日期之前(不含)
布尔逻辑
(bitcoin OR ethereum) min_faves:100 lang:en用括号分组crypto -scam -airdrop用一个减号前缀排除
关于操作符行为参考,GitHub 上社区维护的 igorbrigadir/twitter-advanced-search 仓库是最彻底的公开来源。
两套操作符:网页搜索 vs 官方 v2 API {#two-operator-sets-web-search-vs-the-official-v2-api}
X 上有两套不同的操作符,把它们混淆,是一个在一处“有效”的查询在另一处返回空的最常见原因。写查询之前,先明确你针对的是哪一套。
网页搜索操作符是 twitter.com/search、TweetDeck 和基于爬取的 REST API 所接受的。就是上一节那套,也是 Sorsa /search-tweets 接口原样透传的那套。
官方 X API v2 操作符是一个语法不同的更小子集。v2 接口用 has:media、has:links、is:retweet 和 is:reply,而不是 filter:media 和 -filter:retweets。它还加了针对地理的 place_country:US 和针对话题与实体标注的 context:,但完全不接受互动过滤。在官方接口上,min_faves:、min_retweets:、min_replies:、within_time: 和 filter:blue_verified 都会被忽略,且不返回任何错误。
在 2026 年,有几个操作符在任何提供方上都被广泛误解或不可靠:
| 操作符 | 人们弄错什么 |
|---|---|
filter:verified vs filter:blue_verified | filter:verified 匹配传统认证账号;filter:blue_verified 匹配付费的 X Premium 账号。对编辑信号你通常想要前者、而非后者。 |
within_time:7d | 一个从查询时间起测量的滚动窗口,所以同一个查询明天返回一个不同的集。对可复现数据集,改用显式的 since: 和 until: 日期。 |
from:user vs @user | from:user 返回该账号所写的推文;@user 返回任何提及该账号的推文,包括别人的回复和引用。 |
near:、within:、geocode: | 精确坐标地理标记基本已弃用,所以地理操作符现在覆盖减少、不应为完整性依赖它们。 |
清楚自己在用哪一套,能省下你几个小时,不用去调试一个语法没问题、却悄悄返回空的查询。
你如何按话题标签搜索推文? {#how-do-you-search-tweets-by-hashtag}
要通过 API 按话题标签搜索推文,把话题标签作为查询里的一个独立操作符传入,比如 #worldcup。话题标签单独就能用,也能和互动、语言、日期过滤结合来收窄结果集,这就是把一个嘈杂的话题标签变成可用数据集的办法。
在 Sorsa /search-tweets 接口上,体看起来像这样:
{
"query": "#worldcup min_faves:50 lang:en -filter:retweets since:2026-06-01",
"order": "latest"
}
这会返回携带 #worldcup、至少 50 个赞、在 6 月 1 日当天或之后发布的英语原创(非转发)推文。去掉互动过滤就取到原始量,抬高下限则只浮现传播开的帖子。
官方 X API v2 也把 #hashtag 当作一个查询词匹配,但有两点会绊住话题标签追踪项目。一是那个能让热门话题标签不至于把你淹没在噪音里的互动下限(min_faves:)用不了;二是近期搜索封顶在大约最近七天,除非你在一个更高承诺的存档档位上。如果你的目标是“每一条带这个话题标签、互动高于 N 的推文,回溯数月”,那么按请求计费的网页操作符路径才是真正能做到的那个。话题标签相关操作符的完整集(filter:hashtags、cashtag,以及仅媒体的语言代码)在前面链接的高级搜索语法速查表里。
分页:采集数千条推文 {#pagination-collecting-thousands-of-tweets}
单次搜索请求返回一页约 20 条推文。要拉更大的数据集,你用基于游标的分页。
逻辑是四步:
- 第一次请求。 发查询和 order。不要包含
next_cursor。 - 读游标。 响应包含一个
next_cursor字符串。 - 下次请求。 发同一个查询、同一个 order,加上你刚收到的
next_cursor值。 - 重复、直到
next_cursor为null、空或缺失。
这比基于偏移的分页更可靠,因为在你请求之间发布的新推文不会造成重复或跳过的结果。游标编码结果集里的一个位置、而非一个数字偏移。
深度分页的按日期范围分块
在你把 10 万条推文的采集托付给单个游标之前,有一个值得知道的边缘情况。
在一次匹配 bitcoin lang:en、共 20 万条推文的市场研究拉取里,单游标循环在大约第 70 页开始对更早的页返回重复,并在大约第 90 页完全停止推进。这不是我们 API 特有的:任何对着移动时间线的搜索索引,在你翻得足够深时都有这个毛病。
解决办法是把查询拆成按日期范围的块。不用一个跨全部时间的查询,而是为每一周跑同一个查询:
import datetime as dt
import time
import requests
API_KEY = "YOUR_API_KEY"
URL = "https://api.sorsa.io/v3/search-tweets"
def search_in_window(base_query, since, until, max_pages=50):
"""在一个 since/until 窗口内分页。"""
full_query = f"{base_query} since:{since} until:{until}"
cursor = None
out = []
for _ in range(max_pages):
body = {"query": full_query, "order": "latest"}
if cursor:
body["next_cursor"] = cursor
resp = requests.post(
URL,
headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
json=body,
)
resp.raise_for_status()
data = resp.json()
out.extend(data.get("tweets", []))
cursor = data.get("next_cursor")
if not cursor:
break
time.sleep(0.1)
return out
def search_chunked(base_query, start, end, days_per_chunk=7):
"""按块走过一个日期范围。"""
all_tweets = []
cursor_date = start
while cursor_date < end:
next_date = min(cursor_date + dt.timedelta(days=days_per_chunk), end)
chunk = search_in_window(
base_query,
cursor_date.strftime("%Y-%m-%d"),
next_date.strftime("%Y-%m-%d"),
)
all_tweets.extend(chunk)
print(f"{cursor_date} -> {next_date}: {len(chunk)} tweets")
cursor_date = next_date
return all_tweets
tweets = search_chunked(
"bitcoin lang:en",
dt.date(2026, 1, 1),
dt.date(2026, 4, 1),
days_per_chunk=7,
)
在 bitcoin lang:en 这类嘈杂查询上,每周一块通常能让游标干净地走到完成、不出现重复漂移。更安静的查询可以拉长到每月一块。极高量的查询(想想没有其他过滤的 lang:en)可能要每天一块。
关于更多分页模式和感知速率限制的重试逻辑,见我们的 Twitter API 速率限制指南。
可用代码:Python 和 JavaScript {#working-code-python-and-javascript}
下面的示例是我们对着线上接口跑的生产模式:游标分页、带指数退避的 429 重试,以及一个尊重 20 次/秒上限的小批处理间隔。
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
URL = "https://api.sorsa.io/v3/search-tweets"
def search_tweets(query, order="popular", max_pages=5, max_retries=3):
"""
带游标分页和 429 退避搜索推文。
Args:
query: 搜索字符串(支持 X 操作符)。
order: "popular" 或 "latest"。
max_pages: 要取的最大页数。
max_retries: 放弃一页之前对 429 的重试次数。
Returns:
推文 dict 的列表。
"""
all_tweets = []
next_cursor = None
for page in range(max_pages):
body = {"query": query, "order": order}
if next_cursor:
body["next_cursor"] = next_cursor
for attempt in range(max_retries):
resp = requests.post(
URL,
headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
json=body,
)
if resp.status_code == 429:
wait = 2 ** attempt
print(f"Rate limited. Sleeping {wait}s.")
time.sleep(wait)
continue
resp.raise_for_status()
break
else:
print(f"Page {page + 1} failed after {max_retries} retries.")
break
data = resp.json()
tweets = data.get("tweets", [])
all_tweets.extend(tweets)
print(f"Page {page + 1}: {len(tweets)} tweets (total {len(all_tweets)})")
next_cursor = data.get("next_cursor")
if not next_cursor:
print("End of results.")
break
time.sleep(0.1)
return all_tweets
# 用法
tweets = search_tweets('"Sorsa API" min_faves:5 lang:en', max_pages=10)
for t in tweets:
u = t["user"]
print(f"@{u['username']} ({u['followers_count']} followers)")
print(f" {t['full_text'][:120]}")
print(f" L:{t['likes_count']} RT:{t['retweet_count']} V:{t.get('view_count', 'N/A')}")
JavaScript(Node.js)
const API_KEY = "YOUR_API_KEY";
const URL = "https://api.sorsa.io/v3/search-tweets";
async function searchTweets(query, order = "popular", maxPages = 5, maxRetries = 3) {
const allTweets = [];
let nextCursor = null;
for (let page = 0; page < maxPages; page++) {
const body = { query, order };
if (nextCursor) body.next_cursor = nextCursor;
let data;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const resp = await fetch(URL, {
method: "POST",
headers: { "ApiKey": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (resp.status === 429) {
const wait = Math.pow(2, attempt) * 1000;
console.log(`Rate limited. Sleeping ${wait}ms.`);
await new Promise((r) => setTimeout(r, wait));
continue;
}
if (!resp.ok) throw new Error(`API error: ${resp.status}`);
data = await resp.json();
break;
}
if (!data) break;
const tweets = data.tweets || [];
allTweets.push(...tweets);
console.log(`Page ${page + 1}: ${tweets.length} tweets (total ${allTweets.length})`);
nextCursor = data.next_cursor;
if (!nextCursor) break;
await new Promise((r) => setTimeout(r, 100));
}
return allTweets;
}
(async () => {
const tweets = await searchTweets("bitcoin lang:en min_faves:50", "latest", 5);
for (const t of tweets) {
console.log(`@${t.user.username}: ${t.full_text.slice(0, 100)}`);
}
})();
CSV 导出流程
一个常见的下游模式是搜索到 CSV、然后加载进一个笔记本或 BI 工具:
import requests, time, csv
API_KEY = "YOUR_API_KEY"
URL = "https://api.sorsa.io/v3/search-tweets"
def search_to_csv(query, order="popular", max_pages=10, out="tweets.csv"):
fields = [
"tweet_id", "created_at", "full_text", "lang",
"likes", "retweets", "replies", "quotes", "views",
"username", "display_name", "followers_count", "verified",
]
with open(out, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=fields)
w.writeheader()
cursor, total = None, 0
for _ in range(max_pages):
body = {"query": query, "order": order}
if cursor:
body["next_cursor"] = cursor
r = requests.post(
URL,
headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
json=body,
)
r.raise_for_status()
data = r.json()
for t in data.get("tweets", []):
u = t.get("user", {})
w.writerow({
"tweet_id": t["id"],
"created_at": t["created_at"],
"full_text": t["full_text"],
"lang": t.get("lang", ""),
"likes": t.get("likes_count", 0),
"retweets": t.get("retweet_count", 0),
"replies": t.get("reply_count", 0),
"quotes": t.get("quote_count", 0),
"views": t.get("view_count", 0),
"username": u.get("username", ""),
"display_name": u.get("display_name", ""),
"followers_count": u.get("followers_count", 0),
"verified": u.get("verified", False),
})
total += 1
cursor = data.get("next_cursor")
if not cursor:
break
time.sleep(0.1)
print(f"Exported {total} tweets to {out}")
search_to_csv(
'(bitcoin OR ethereum) lang:en min_faves:10 -filter:retweets',
order="latest",
max_pages=20,
out="crypto_tweets.csv",
)
每页约 20 条推文,max_pages=50 给你约 1,000 条。和按日期范围分块结合,你不用重写循环就能扩展到六七位数,组装一个用于机器学习的 Twitter 数据集正是这么做的。
真实世界查询模板 {#real-world-query-templates}
复制这些、换掉变量、发货。
品牌监控(仅自然提及)
("yourbrand" OR "@yourbrand") -from:yourbrand -filter:retweets lang:en
捕捉人们关于你说什么,排除你自己的帖子和转推,仅英语。在一个 5 分钟的 cron 上跑、导进 Slack。
竞品内容基准
(from:competitor1 OR from:competitor2 OR from:competitor3) min_faves:100 -filter:replies since:2026-01-01
它们在日期窗口里表现最好的原创帖子。放进电子表格、按互动排序、学什么管用。我们的 Twitter 竞品分析指南把这搭成一个可重复的工作流。
情绪追踪
(bitcoin OR $BTC) (bullish OR bearish OR moon OR crash OR pump OR dump) min_faves:20 lang:en
带情绪、且在一个质量下限之上的推文。与上面链接的情感分析管道配合来打分。
产品反馈挖掘
"yourproduct" (bug OR broken OR issue OR love OR amazing OR hate) -filter:retweets
自然反馈、两种口味。对支持和产品路线图输入有用。
线索生成
("looking for" OR "anyone recommend" OR "best tool for") (api OR scraping OR twitter data) -filter:retweets lang:en
正主动索求的人。用 min_faves:1 进一步过滤来丢掉机器人流量。
事件反应窗口
"product launch" OR "announcement" from:yourbrand since:2026-05-01 until:2026-05-08
在一个界定窗口内对你自己发布的反应。把 from:yourbrand(你的帖子)与一个针对周边对话的单独查询配对。
搜索推文 vs 追踪提及:用哪个接口? {#search-tweets-vs-track-mentions-which-endpoint}
两个接口在提及追踪的负载上有重叠。怎么选:
/search-tweets 是通用接口,接受任何操作符组合、任何查询形状。需要灵活性、查询不只是围绕一个用户名、或者想在单个布尔表达式里把提及和互动过滤混在一起时,用它。
/mentions 专为追踪单个用户名的 @ 提及打造,暴露我们 API 里最丰富的过滤集:min_likes、min_replies、min_retweets、since_date、until_date,全都是一等参数,而不是内联操作符。当工作流就是“@brand 出现互动高于 X 的新提及时发告警”时,用它。
快速判断:查询以 @handle 开头、以互动过滤结尾,用 /mentions;涉及多个词、布尔分组或非提及操作符,用 /search-tweets。
想更近地看提及接口和品牌监测工作流,见我们的 Twitter 提及 API 指南。
Sorsa 搜索如何对比官方 X API {#how-sorsa-search-compares-to-the-official-x-api}
Sorsa 是我们自己的产品,所以下面是诚实的并排对比,两侧都带真实数字,也包括官方 API 仍然胜出的地方。对纯只读搜索,操作符被静默丢弃、以及作者按读取计费,是团队离开 v2 的两个实际原因。
| 维度 | Sorsa /search-tweets | 官方 X API v2 /2/tweets/search/recent |
|---|---|---|
| 定价模型 | 固定月费套餐;批量时每 1,000 条推文 $0.02 起 | 按量付费,帖子读取每条约 $0.005(约每 1,000 条 $5.00) |
| 起步免费 | 100 次免费请求、无需绑卡、覆盖全部接口 | 无免费额度;第一次调用前先买额度 |
| 响应里的作者资料 | 默认内嵌、无额外收费 | 单独用户读取、约每个 $0.010、经 expansions=author_id |
| 认证 | ApiKey 请求头里的 API 密钥 | OAuth 2.0 Bearer Token |
| 账号审批 | 即时注册、无审批队列 | 开发者控制台注册 |
互动操作符(min_faves:、min_retweets:、min_replies:) | 有 | 被静默忽略 |
| 网页操作符对等 | 完整集透传 | 仅子集、不同语法(has:、is:) |
| 历史存档 | 回溯到 2006 | 约 7 天近期;全量存档仅在更高承诺层级 |
| 速率限制 | 每秒 20 次请求、所有套餐 | 基于额度、各异 |
| 实时监测 | 每秒 20 次请求轮询 | 有过滤流 |
| 写动作(发帖、点赞、私信) | 无(只读) | 有 |
读取成本上差距很大。官方 X API 上读 1,000 帖约 $5.00,而这还没算每作者 $0.010 的单独收费。在 Sorsa 上,同样的 1,000 条推文,走分页的 /search-tweets 接口(每请求约 20 条)约 $0.10;同样这些 ID 走 /tweet-info-bulk 批量接口拉取则降到约 $0.02,作者资料两种方式都包含。在批量基准上,这是每 1,000 条推文最多便宜 50 倍,而且 X 会当作第二次读取收费的作者数据,这里不额外收费。
官方 API 在这些地方才是对的选择:真正基于推送的实时过滤流,以及我们根本不做的写操作(发帖、点赞、关注)。而只读的一切,按请求计费的路径在任何有意义的量上都更便宜、也更省事。
实战。 我们合作过的一个约 12 人的社交分析团队,正卡在那个尴尬的中间地带:他们每月拉的读取量在 5 万到几百万帖之间,早过了按量付费还便宜的那个点,却远不到能证明企业合同合理的量。在官方 API 上,作者资料让他们每次搜索的成本翻倍,因为每条推文的作者都单独计一次读取。把读取负载迁到一个内嵌作者数据的固定套餐后,他们的月度数据账单降了一个数量级还多,而且这个数字变得可预测,对他们的财务团队来说,这比省下的绝对金额更要紧。驱动这次改变的迁移,正是前面链接的迁移指南里一步步映射的那套。
常见错误与故障排查 {#common-errors-and-troubleshooting}
下面这些问题最常绊住人,大致按我们在支持里见到的频率排序。
一个你知道应该有结果的查询却返回空。 常见嫌疑:操作符里拼错(min_likes 而不是 min_faves)、一个需要双引号的短语("black cat" 而不是 black cat),或一个排除太多的 -filter:。把查询剥到只剩一个关键词、确认有结果,然后一次加一个地把操作符加回去。
429 Too Many Requests。 你在这个密钥上超过了 20 次/秒。退避一秒、重试。上面的 Python 和 JavaScript 示例都实现了指数退避。20 次/秒的限制在我们各套餐间通用;如果你需要持续的更高吞吐,联系我们定制限制。
深度分页后游标停止推进或返回重复。 这就是按日期范围分块那一节讲的问题。任何搜索索引在嘈杂查询上翻过某个深度就变得不稳定。切到 since:/until: 的每周块。
操作符被静默忽略。 如果某个过滤好像没起作用,你很可能是在向官方 v2 API 发网页搜索操作符,而 v2 会丢弃互动过滤。在断定查询写错之前,先确认你的服务商接受哪一套操作符。
操作符数量上限。 X 的搜索索引似乎会静默地让带 22 到 23 个以上操作符的查询失败,而官方 API 上的自助近期搜索把查询字符串封顶在 512 个字符。如果你的查询分组比这更多还返回空,就简化查询,或者拆成多次请求。
user 对象缺失或不完整。 作者在推文创建和你请求之间被封、被删或把账号设为受保护。推文还在索引里,但作者不再能被公开枚举。代码里用 tweet.get("user", {}) 默认值处理这种情况。
加了 lang:en 却出现意外语言的结果。 X 的语言检测器不完美,尤其在带话题标签或混合文字的短推文上。对分析负载,用语言检测库(比如 langdetect 或 fasttext-langdetect)对 full_text 字段做后过滤。
私密和被限流的账号不出现。 受保护账号不在搜索索引里。被封和被锁的账号也被隐藏,没有操作符能把它们浮现出来。限流是和搜索可见性无关的另一个诊断问题,也不是搜索索引能回答的。
常见问题 {#frequently-asked-questions}
如何不用官方 Twitter API 搜索推文?
用第三方搜索 API 或开源爬虫。第三方 REST API 把自己的爬取基础设施封装在一个 API 密钥后面,没有 OAuth,且支持完整网页操作符;Sorsa 就是这样一个 Twitter/X API 替代方案,带一个透传每个操作符、按请求计费的 /search-tweets 接口。Twikit 这类开源库对小的一次性任务管用,但对生产环境不够稳定。
如何用 API 按话题标签搜索推文?
把话题标签作为一个查询词传入,比如 #worldcup,再和过滤结合来控制量:#worldcup min_faves:50 lang:en -filter:retweets 返回携带那个话题标签的热门英语原创推文。官方 X API v2 也匹配 #hashtag,但施加不了互动下限,还把近期搜索限在约七天,所以要拿更深的话题标签历史,按请求计费的网页操作符 API 是更强的路线。
能搜索超过 7 天的推文吗?
能,在第三方 API 上。Sorsa /search-tweets 接口用 since: 和 until: 操作符支持可回溯到 2006 年的完整历史存档。官方 X API v2 近期搜索限在约 7 天;官方 API 上的全量存档搜索限在更高承诺的档位。
每次搜索请求能拿到多少条推文?
单次 /search-tweets 请求返回一页约 20 条推文。要拉更多,用带 next_cursor 的游标分页。几千条推文以上的数据集,把游标分页和按日期范围分块结合,避免长行走上的游标漂移。
对开发者最有用的 Twitter 搜索操作符是什么?
生产代码里高价值的操作符是 from:、to:、min_faves:、min_retweets:、since:、until:、lang:,以及 -filter:retweets 和 -filter:replies 排除。互动过滤尤其有价值,因为它们在不丢相关内容的前提下去掉低质量噪音,也因为在官方 X API v2 上不生效。
Twitter 搜索 API 支持布尔逻辑(AND、OR、NOT)吗?
支持。空格分隔的词隐含 AND。大写的 OR 是显式 OR。括号给表达式分组。减号前缀排除词:crypto -scam。一个完整示例是 (bitcoin OR ethereum) min_faves:100 -filter:retweets lang:en。
2026 年通过 API 搜索推文要花多少钱?
官方 X API v2 按量付费下,帖子读取每条约 $0.005,外加作者资料读取每份约 $0.010,这在作者数据之前就合到约每 1,000 条推文 $5.00。Sorsa 是固定月费,注册即送 100 次免费请求,无需绑卡。走 /tweet-info-bulk 批量,推文数据在 Pro 套餐上折合约每 1,000 条推文 $0.02(Starter 的 $0.049 降到 Enterprise 的 $0.018),含作者资料;走分页的 /search-tweets 接口、每请求约 20 条,则更接近每 1,000 条 $0.10。
能实时搜索推文吗?
实际上能,靠轮询。用 20 次/秒的速率限制加 order: "latest",你能每几秒轮询一个查询,在发布后几秒内拿到新推文。要真正基于推送的流式,官方 X API 过滤流是唯一不用轮询就能交付的选项。对多数监测负载,每 30 到 60 秒轮询一次就够,运营起来也更便宜。
开始上手 {#getting-started}
你可以在写一行客户端代码之前先测这个接口。在线试用工具 Playground 从浏览器对着你的密钥跑真实请求,可视化搜索生成器给你一个操作符表单,并输出要发送的精确 JSON 请求体。
准备好拿密钥时:注册即送 100 次免费请求,无需绑卡,覆盖全部 40 个接口,注册只需几分钟、无开发者账号审批,每个套餐都跑同样的统一 20 次/秒。走批量时,固定套餐折合低至每 1,000 条推文 $0.02。在 Sorsa 控制台注册,跟着快速上手指南做五分钟配置。如果你在离开官方 API,前面链接的迁移指南把每个 v2 接口映射到 Sorsa 的对应项,两边都有代码。
审校:Keksich(Sorsa 创始人,X API 研究者)
本指南是怎么写成的:内容取材于我们构建并运营 Sorsa 搜索基础设施的一线工作、对着 /search-tweets 接口的线上测试,以及和官方 X API v2 近期搜索接口的直接对比。操作符行为对照社区维护的 igorbrigadir/twitter-advanced-search 参考和官方 X API 文档核查;定价反映截至 2026 年 7 月 8 日的官方 X API 按读取费率。接口细节来自 Sorsa API 文档。谁在发布这个博客,更多信息见我们的关于页面。