作者:Sorsa 编辑部
2026 年 7 月更新:新增了 100 次免费请求的起步选项(无需绑卡),把转换定价重新表述为按每 1,000 次转换计费,并重新核验了官方按量付费读取费率。
要点: 一个 Twitter (X) 用户 ID 是账号创建时分配的永久 64 位数字,@用户名可以随时改变。要在两者间转换,把用户名、数字 ID 或资料 URL 发给一个查找接口。存 ID、别存用户名,因为 ID 在改名后仍留存。
如果你曾把 @brand_name 存作主键、然后眼看那个账号改名成 @new_brand,你已经知道这为何要紧。X 让用户想什么时候改用户名就改;一旦 @brand_name 被释放,任何人都能认领。数字用户 ID 是唯一终身黏在原账号上的标识符。
要在代码里做这些转换,Sorsa API,一个替代性的 Twitter/X API 提供方,在单个 API 密钥之后暴露三个专门接口(用户名转 ID、ID 转用户名、资料 URL 转 ID),没有 OAuth 流程、也没有应用审批队列。每次转换算作一次请求、而非官方 API 用的按资源计量,所以解析 10 万个用户名在 Pro 套餐上从 $199 起、对比大约 $1,000 的官方 X API 额度。每个新密钥都附带 100 次免费请求(一次性、无需绑卡、永不过期、全部 40 个接口),足够在决定一个套餐之前转换第一批账号。对你不想写任何代码的一次性查找,免费的 ID 转换器 playground 完全不用密钥就在浏览器里做全部三种转换。
披露:Sorsa API 是我们的产品。下面的定价对比用官方 X API 发布的按量付费费率,于 2026 年 7 月核验。在投入之前对照你自己的工作负载查当前费率。
本指南是穿过这个主题的开发者路径:你何时真的需要转换、这些标识符的设计为何要紧、如何在代码里做每种转换。最后,如何跨数千个账号跑转换而不弄坏你的数据管道。
目录
- 为什么用户 ID 是你唯一能信任的标识符
- Twitter 用户 ID 如何生成
- 如何找到一个 Twitter 用户 ID
- 三种转换操作
- 2026 年 ID 转换要花多少钱?
- 用 Sorsa API 转换
- 大规模批量转换
- 规范化混合输入
- 检测用户名变更
- 实战中
- 开始上手
- 常见问题
为什么用户 ID 是你唯一能信任的标识符 {#why-user-ids-are-the-only-identifier-you-can-trust}
X 上的用户名是可变的。一个用户今天能把 @old_name 改成 @new_name,明天别人能抢下 @old_name。如果一个应用把用户名当作主键,两件事同时崩。每一条存下的引用都指向错的账号;一个第三方现在还可能持有你曾用来关联原用户的那个用户名。
这不是罕见的边缘情况。一项监测 870 万账号的纵向研究发现,大约 10% 的 Twitter 用户随时间更改其用户名(Jain 和 Kumaraguru,IIIT-Delhi)。在我们帮助公司在 2023 年定价大改后迁离官方 X API 的自身工作里,用户名流失是我们发现的最常见的静默数据损坏问题之一。几家客户跑的监控看板把用户名存作外键,当一个被追踪的竞品改名时,看板继续乐呵呵地从一个捡起旧用户名、毫无关联的账号收集推文。
用户 ID 是账号创建时分配的 64 位整数,不能被改、不能被转移、在整个平台上唯一,也是每一条可靠 X 数据管道所构建其上的连接键:
- 用户名变更不弄坏你的系统。 无论一个账号改名一次还是五十次,ID 指向同一个资料。
- 索引更快。 整数索引比变长字符串更省存储,按数字主键的查找在每种规模上都胜过按用户名的查找。
- 跨时间连接保持干净。 当跨不同时间收集的数据集匹配账号时,ID 是唯一安全的键。跨年份的一次用户名匹配可能悄悄匹配上两个不同的人。
- 若干接口要求 ID。 Sorsa 的
/info-batch接受user_ids,列表和社区接口按数字 ID 引用账号,多数官方 X API 接口同样期待数字 ID。
如果你从本文只带走一件事:在你的数据库里存 ID、别存用户名。把用户名当作可能过期的缓存展示数据。
把 ID 存为字符串、而非整数
把 Twitter 用户 ID 存储和传输为字符串,即便看起来像数字。现代 19 位 Snowflake ID 超出 JavaScript 的安全整数上限(Number.MAX_SAFE_INTEGER,即 2^53 − 1、或 9007199254740991),所以把一个 ID 解析成原生 JS 数字会悄悄把末尾几位四舍五入并损坏 ID。同样的风险出现在任何 64 位值落进 32 位或浮点类型的地方。把 ID 保存在字符串列或一个 64 位 BIGINT 里,绝不要放进默认的 int 或一个 JavaScript number。Sorsa API 正是出于这个原因把每个 ID 都作为 JSON 字符串返回。
Twitter 用户 ID 如何生成 {#how-twitter-user-ids-are-generated}
Twitter 在 2010 年打造了自己的 ID 生成系统 Snowflake 来解决分布式系统问题。在 Twitter 规模上,为每一条新推文、消息或账号去问一个中央数据库“下一个 ID 是什么?”行不通。Snowflake 让任何服务器都能独立地、无需协调地铸造全局唯一的 64 位整数,方法是组合三个值:
- 时间戳(41 位): 自 Twitter 纪元(
2010-11-04 01:42:54.657 UTC)以来的毫秒。 - 工作机/机器 ID(10 位): 标识生成该 ID 的服务器。
- 序列号(12 位): 在同一毫秒内递增,支持每台机器每毫秒最多 4,096 个 ID。
加一个符号位,你就有共 64 位。因为时间戳坐在最高有效位里,Snowflake ID 大致可按时间排序,这就是为什么你比较推文 ID 时是按时间排序的。
多数转换器页面弄错的那个用户 ID 细节
这里有一点多数 ID 转换器页面陈述得不对:推文 ID 在 2010 年走了 Snowflake,但 X 直到 2016 年 2 月才把用户 ID 切到 Snowflake 格式,而不少页面仍在重复一个错误日期(常是 2020 年)。据 X 自己的 64 位 ID 迁移公告,Snowflake ID 于 2016 年 2 月 1 日开始向用户、列表和已保存搜索推出。在那之前,X 多年发放短的、顺序的整数用户 ID。这就是为什么 Jack Dorsey 的账号是 12,也是为什么切换前创建的账号有短的、低的 ID,而其后创建的账号有长的、18 到 19 位的 Snowflake ID。
实际后果:你能从任意推文 ID 提取一个创建时间戳,但你无法可靠地从一个旧用户 ID 提取一个注册日期,因为 2016 年前的用户 ID 不携带内嵌时间戳。如果你需要一个较老账号的加入日期,直接从资料读 created_at 字段,或用账号年龄查询器做一次快速的一次性查找。
如何找到一个 Twitter 用户 ID {#how-to-find-a-twitter-user-id}
X 不在界面的任何地方显示用户 ID,所以你得推导。有三条实际路径,大致按费力程度排序:
手动方式(慢)。 打开资料,查看页面源码(Ctrl+U 或 Cmd+U),在 HTML 里搜 user_id 或 rest_id。或打开浏览器开发者工具(F12),盯着 Network 标签、刷新,从一个 API 响应里读出 ID。两者对单个账号都行,但都很繁琐、每当 X 重排标记就坏、且过不了几次查找就不再可扩展。
一个免费转换器(一次性)。 把一个用户名、ID 或资料 URL 粘进查找工具、读结果。Sorsa ID 转换器 playground 在浏览器里处理全部三个方向、无需注册也无需 API 密钥,而且还返回资料、这样你能确认你拿对了账号。被封、已删除或从不存在的账号返回未找到结果;受保护(私密)账号返回 ID 但统计有限。
API(大规模)。 当你需要解析多于几个账号、或在一条数据管道内部无人值守地跑查找时,直接调用一个转换接口。下面的代码在 Python 里展示全部三种操作。
三种转换操作 {#the-three-conversion-operations}
你曾需要的转换操作恰好有三种:
| 你有 | 你想要 | 怎么做 |
|---|---|---|
用户名(带或不带 @) | 数字用户 ID | 把用户名解析成 ID |
| 数字用户 ID | 当前用户名 | 把 ID 解析成用户名 |
资料 URL(x.com/username 或 twitter.com/username) | 数字用户 ID | 从 URL 提取用户名、解析成 ID |
每种操作是一次 API 调用。当你用像 Sorsa 这样的第三方 API 时,这些操作都不要求 OAuth、回调 URL 或应用审批。一个第三方密钥完全替代官方开发者账号流程,所以没有申请要提交、也没有什么要等,这正是我们在不用开发者账号使用 X API指南里覆盖的路径。全部三个的 Python 和 curl 示例在下面的代码一节,每个都映射到一个有文档的接口:用户名转 ID、ID 转用户名,以及资料链接转 ID。
推文没有等价操作。一个推文 ID 直接在推文 URL(x.com/user/status/1234567890)里可见,所以你从 URL 字符串里解析、而非调用一次查找。
何时改用 /info 接口
如果你既需要用户 ID 又需要完整资料(显示名、粉丝数、简介、头像 URL),不要分别调用 /username-to-id 然后 /info。直接调用 /info?username=...:该接口在单次请求里返回完整资料、包括 id 字段。把这拆成两次调用是我们在代码评审里见到的最常见成本错误之一。在较大量上,/info-batch 把最多 100 份资料打包进一次请求,这把连同完整资料一起拉 ID 的成本带到在批量定价套餐上从每 1,000 份资料 $0.01 起。
2026 年 ID 转换要花多少钱? {#how-much-does-id-conversion-cost-in-2026}
定价今年发生了显著变化。在 2026 年 2 月,X 把自己的 API 转到按量付费额度模型、作为新开发者的默认,取代旧的 Basic 和 Pro 层级。官方 X API 上的一次用户名转 ID 查找是一次用户读取,按每返回资源约 $0.010 计费,折合每 1,000 次转换约 $10.00。对一个内部工具里的偶尔查找,那还行。对批量或周期性作业,成本累加得很快:解析 10 万个用户名花大约 $1,000 的官方 API 额度。
在 Sorsa 上,三个转换接口每一个都算作一次请求,所以成本落在每 1,000 次转换从 $1.80 起。每个新密钥都附带 100 次免费请求(一次性、无需绑卡、永不过期、全部 40 个接口)来先测试工作流:
| Sorsa Starter | Sorsa Pro | 官方 X API(按量付费) | |
|---|---|---|---|
| 计费单位 | 1 次请求 | 1 次请求 | 每返回资源 |
| 每次转换成本 | $0.0049 | $0.00199 | ~$0.010(用户读取) |
| 每 1,000 次转换成本 | $4.90 | $1.99 | ~$10.00 |
| 每月含请求数 | 10,000 | 100,000 | 无,买额度 |
| 100,000 次转换 | 用 Pro 套餐 | 从 $199/月起 | ~$1,000 额度 |
| 认证 | API 密钥请求头 | API 密钥请求头 | OAuth 2.0 |
| 搭建 | ~30 秒 | ~30 秒 | 应用加买额度 |
按每次转换算,Pro 套餐折合比官方 API 便宜约 5 倍;如果你批量处理差距还会拉大:通过 /info-batch 的资料查找把最多 100 个账号打包进单次请求。要看官方模型今年如何变化的全貌,见我们的 Twitter API 定价拆解以及官方 API 为何这么贵背后的原因。我们这边的套餐细节在定价页上。
官方侧诚实的提醒:最便宜的读取层级、每资源 $0.001 的自有读取,确实有竞争力。但这只在一个应用通过一组固定接口读自己账号的数据时适用。对任意第三方账号的用户名转 ID 查找是非自有读取,按标准用户读取费率计费。所以对本指南覆盖的转换工作,按请求计费模型仍领先。
用 Sorsa API 转换 {#converting-with-the-sorsa-api}
认证是单个请求头:ApiKey: YOUR_API_KEY。没有 OAuth 流程、没有作用域、没有回调。三个操作每一个都映射到一个有文档的接口,共享同一个密钥。
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://api.sorsa.io/v3"
HEADERS = {"ApiKey": API_KEY}
def username_to_id(handle: str) -> str:
"""把一个用户名(不带 @)解析成它的永久数字用户 ID。"""
resp = requests.get(f"{BASE}/username-to-id/{handle}", headers=HEADERS)
resp.raise_for_status()
return resp.json()["id"] # 作为字符串返回,就保持那样
def id_to_username(user_id: str) -> str:
"""把一个数字用户 ID 解析成账号的当前用户名。"""
resp = requests.get(f"{BASE}/id-to-username/{user_id}", headers=HEADERS)
resp.raise_for_status()
return resp.json()["handle"]
def link_to_id(profile_url: str) -> str:
"""从一个完整资料 URL 提取数字用户 ID。"""
resp = requests.get(
f"{BASE}/link-to-id",
headers=HEADERS,
params={"link": profile_url},
)
resp.raise_for_status()
return resp.json()["id"]
# 示例
print(username_to_id("elonmusk")) # "44196397"
print(id_to_username("44196397")) # "elonmusk"
print(link_to_id("https://x.com/elonmusk")) # "44196397"
第一个调用的 curl 等价形式:
curl "https://api.sorsa.io/v3/username-to-id/elonmusk" \
-H "ApiKey: YOUR_API_KEY"
# {"id": "44196397"}
大规模批量转换 {#batch-conversion-at-scale}
当你有一张竞品用户名的电子表格、一份 CRM 导出,或一份要给一条监控管道做种的账号列表时,你需要把它们一次全转成 ID。下面的模式加了重试逻辑、软速率限制(Sorsa 在每个套餐上都允许每个密钥每秒 20 次请求),以及对被封或已删除账号的优雅处理。要看 Python 里贯穿 API 其余部分的分页、批处理和错误处理的更广覆盖,见 Twitter API Python 指南。
import requests
import time
from typing import Iterable
API_KEY = "YOUR_API_KEY"
BASE = "https://api.sorsa.io/v3"
HEADERS = {"ApiKey": API_KEY}
def batch_username_to_id(handles: Iterable[str], pause: float = 0.05) -> dict[str, str | None]:
"""
把一份用户名列表解析成用户 ID。
返回一个映射 用户名 -> id 的字典(若查找失败则为 None)。
软速率限制:约每秒 20 次请求,远在 Sorsa 每密钥上限之下。
"""
results: dict[str, str | None] = {}
for handle in handles:
handle = handle.strip().lstrip("@")
try:
resp = requests.get(f"{BASE}/username-to-id/{handle}", headers=HEADERS, timeout=10)
if resp.status_code == 200:
results[handle] = resp.json()["id"]
elif resp.status_code == 404:
results[handle] = None # 账号不存在或被封
elif resp.status_code == 429:
time.sleep(1) # 退避并重试一次
retry = requests.get(f"{BASE}/username-to-id/{handle}", headers=HEADERS, timeout=10)
results[handle] = retry.json()["id"] if retry.status_code == 200 else None
else:
results[handle] = None
except requests.RequestException:
results[handle] = None
time.sleep(pause)
return results
handles = ["NASA", "SpaceX", "Tesla", "OpenAI", "stripe"]
id_map = batch_username_to_id(handles)
for handle, uid in id_map.items():
print(f"@{handle:<10} -> {uid or '(not found)'}")
反方向以同样的方式工作。把 URL 换成 /id-to-username/{user_id}、从响应里读 handle 字段。
规范化混合输入 {#normalizing-mixed-input}
一个常见情形:你的应用从终端用户或上游系统接收账号引用、格式随源头恰好产出的样子而定。有些条目是裸用户名,有些带 @ 前缀,有些是完整 URL,有些已经是数字 ID。下面的规范化器把全部四种形状收拢成一个稳定的用户 ID,并在输入已经是一个 ID 时完全跳过 API 调用。
def normalize_to_id(value: str) -> str:
"""
接受以下任一:
- "elonmusk"
- "@elonmusk"
- "https://x.com/elonmusk"
- "https://twitter.com/elonmusk"
- "44196397"
以字符串返回数字用户 ID。
"""
value = value.strip().lstrip("@")
# 已经是一个数字 ID,无需 API 调用
if value.isdigit():
return value
# 资料 URL
if "x.com/" in value or "twitter.com/" in value:
return link_to_id(value)
# 裸用户名
return username_to_id(value)
# 四个都返回同一个 ID
for source in ["elonmusk", "@elonmusk", "https://x.com/elonmusk", "44196397"]:
print(f"{source:<35} -> {normalize_to_id(source)}")
把这个用作任何摄入管道里的第一步。这保证下游逻辑总是用稳定标识符工作、不管输入多乱。
检测用户名变更 {#detecting-handle-changes}
如果你在收集时既存了用户 ID 又存了用户名,你可以定期重新解析这些 ID、检测哪些账号已改名。这是刷新一个可能持有过期显示名的数据库的工作流。
def detect_renames(records: list[dict]) -> list[dict]:
"""
records: 一列 {"user_id": str, "stored_handle": str}
返回: 已改名账号的一列 {"user_id", "old_handle", "new_handle"}。
"""
changes = []
for record in records:
try:
current = id_to_username(record["user_id"])
except requests.HTTPError:
continue # 账号已删除、被封或暂时性错误
if current and current.lower() != record["stored_handle"].lower():
changes.append({
"user_id": record["user_id"],
"old_handle": record["stored_handle"],
"new_handle": current,
})
time.sleep(0.05)
return changes
db_records = [
{"user_id": "44196397", "stored_handle": "elonmusk"},
{"user_id": "1234567890", "stored_handle": "old_brand_name"},
]
for change in detect_renames(db_records):
print(f"Renamed: @{change['old_handle']} -> @{change['new_handle']} (ID {change['user_id']})")
要做更丰富的改名审计,/about 接口返回 username_change_count 和 last_username_change_at。那不只告诉你当前用户名,还告诉你一个账号改名过多少次、以及最近一次变更何时发生。
实战中 {#in-practice}
一个大约 12 人的社交分析团队,在他们追踪的一个竞品改名后找到我们。他们的看板在有人注意到之前,悄悄摄入了一个陌生人的推文近三周。根本原因是经典的那个:他们把用户名存作了键。修法是结构性的、而非取巧。在摄入时把每个用户名解析成对应的用户 ID、把 ID 存作键、把用户名保留为仅供展示,并跑一个排定的重新解析来抓改名(上面的 detect_renames 模式)。
成本上也要紧。他们每月的用户名解析量一直跑在接近 $1,000 的官方 X API 用户读取额度。在 Pro 套餐上、$199 换 10 万次请求,这变成一个可预测的支出项、在那份工作负载上约减 5 倍。还有额外余量,因为通过 /info-batch 的批量资料查找把最多 100 个账号塞进一次请求。可靠性上的胜利(不再有静默的跨账号损坏)是他们最在乎的部分。
开始上手 {#getting-started}
试这个的三种方式:
- 免代码、免注册。 打开 ID 转换器 playground、粘任意用户名、ID 或资料 URL。对一次性查找、以及在你写任何代码之前确认一个账号存在很有用。
- 带 100 次免费请求的 API 密钥。 创建一个免费账号,每个密钥都附带 100 次免费请求(一次性、无需绑卡、永不过期、全部 40 个接口)。把密钥放进
ApiKey请求头,上面的 Python 示例就照原样跑。付费套餐从 Starter 每次转换 $0.0049 起,在 Pro 上降到 $0.00199,而固定的每秒 20 次请求在每个套餐上适用。 - 已经在用官方 X API? 迁移指南把每个官方接口映射到对应的 Sorsa 等价物;我们的 Twitter API 替代方案概览覆盖那些取舍。
这里的三个转换接口是一个覆盖资料、推文、粉丝、搜索和核验的 40 接口只读 API 的一部分。
常见问题 {#frequently-asked-questions}
什么是 Twitter (X) 用户 ID?
一个 Twitter 用户 ID 是 X 账号创建时分配给它的唯一 64 位整数。用户 ID 是永久的:不能被改、转移或重新分配。不像可以随时编辑的用户名,用户 ID 在那个账号的整个存续期内黏在同一个账号上。这就是为什么开发者用它作为任何账号引用的稳定键。
你如何找到你自己的 Twitter 用户 ID?
X 界面不在任何地方显示你的用户 ID。要找到,把你的用户名或资料 URL 粘进一个转换器(如 Sorsa ID 转换器),或把你的用户名发给一个查找接口。你也可以从账号设置下载你的 X 数据存档,存档在账号元数据里包含你的用户 ID。
一个 Twitter 用户 ID 会改变吗?
不会。一个 Twitter 用户 ID 在账号创建时分配、在账号存续期内永久。给账号改名、更改显示名、切换邮箱地址,或被封然后又复权,都不改变用户 ID。唯一改变的是一个用户名和一个 ID 之间的链接,这个链接在用户名本身被更改、旧用户名变得可供别人认领时转移。
为什么有些 Twitter 用户 ID 短、另一些很长?
X 给早期账号分配了短的、顺序的整数 ID,这就是为什么 Jack Dorsey 的账号是 12,而只在 2016 年 2 月才把用户 ID 切到 64 位 Snowflake 格式。那次切换之前创建的账号有短的、低的 ID。其后创建的账号有长的 Snowflake ID、约 18 到 19 位,在高位里编码一个创建时间戳。
你能从一个用户 ID 解码出一个注册日期吗?
对 2016 年 2 月 Snowflake 切换之后创建的账号,能:把 ID 右移 22 位、加上 Twitter 纪元(1288834974657),你就得到以毫秒计的创建时间戳。对有 2016 年前顺序 ID 的较老账号,不能,因为那些 ID 不携带内嵌时间戳。那种情况下取资料、直接读 created_at 字段。
有不需要 API 密钥的免费 Twitter ID 转换器吗?
有。Sorsa ID 转换器 playground 在浏览器里处理全部三种操作(用户名转 ID、ID 转用户名、资料 URL 转 ID)、无需 API 密钥也无需注册,而且返回资料、这样你能确认账号。这个工具为一次性查找而建。对批量作业、周期性管道,或任何无人值守跑的东西,一个 API 密钥是更好的契合。每个新密钥都附带 100 次免费请求(一次性、无需绑卡),这样你能在付费前测试一批。
开发者如何大规模地把数千个用户名转成 ID?
对高流量工作,Sorsa API 按每次转换一次请求暴露三个转换接口(/username-to-id、/id-to-username、/link-to-id)。在 Pro 套餐上、$199/月换 10 万次请求,每次转换约 $0.002、折合比官方 API 的按资源费率便宜约 5 倍。吞吐封顶在每个密钥每秒 20 次请求,高到你自己的代码通常才是瓶颈、而非 API。
用户 ID 和推文 ID 有什么区别?
两者都是 64 位 Snowflake 风格整数,但住在不同的命名空间、标识不同的东西。一个用户 ID 标识一个账号;一个推文 ID(也叫状态 ID)标识一条单独的帖子。推文 ID 直接在推文 URL(x.com/user/status/{tweet_id})里可见,所以对推文 ID 不需要查找。用户 ID 不在 X 界面的任何地方暴露,这就是为什么存在专门的转换接口。
审校:Keksich(Sorsa 创始人,X API 研究者)
本指南是如何汇成的:取材于我们打造并运营一个替代性 Twitter/X API 的亲手工作、以及对着我们实时转换接口的直接测试,并对照 Sorsa API 文档核对接口行为、对照官方 X API 定价公告核对当前按量付费费率。Snowflake 结构取自 Twitter 最初的工程博文,2016 年 2 月的用户 ID 切换取自 X 的 64 位 ID 迁移公告,用户名流失数字取自 Jain 和 Kumaraguru 的纵向研究。定价和费率数字于 2026 年 7 月核验。关于我们是谁的更多信息在我们的关于页上。