用 API 把 Twitter 用户名转换为用户 ID
X 从不在个人主页上显示用户 ID,所以代码要把用户名发给用户查询接口来取得它,接口会返回账号永久不变的 ID,是一个最多 19 位数字的字符串;批量查询每次请求处理 100 个用户名。
简短回答: 官方 X API 的 GET /2/users/by 每次调用接收 100 个用户名,每个用户 $0.010,并且需要开发者账号和 bearer 令牌;同样的查询也能把 ID 转回用户名。Sorsa API 通过 GET /info-batch 转换列表,每次请求 100 个用户名,每 1,000 个 $0.014 起,100 次免费请求可以覆盖前 1 万个。
- 接口
GET /info-batch- 每页数量
- 每次请求 100 个用户名
- 价格
- 每 1,000 个 $0.014 起
#Twitter 用户 ID 是什么,怎么找到它
Twitter 用户 ID 是 X 在账号创建的那一刻赋予它的永久数字。@ 后面的用户名只是一个由 1 到 15 个字母、数字或 _ 组成的标签,主人可以随时更改,一旦空出来,任何人都可以注册它。ID 永远不变,也不会转移到另一个账号,这就是应用、导出文件和数据库都用 ID 来指向账号的原因。开发者也把它叫作账号 ID、数字 ID 或 UID。
查询单个账号,免费的 Twitter ID 查询工具能显示任意用户名或个人主页链接背后的 ID,也能把 ID 转回账号当前的用户名。无需注册。
提交后会打开免费的推特 ID 查询工具,并填好你的用户名、链接或 ID。
早期的账号拿到的是很短的 ID:Jack Dorsey 的 @jack 是 12。近几年开通的账号,ID 是 18 或 19 位。X 在应用里哪里都不显示这个数字,但你自己的 ID 在你的数据存档里:在“设置和隐私”下的“你的账号”中选择“下载你的数据存档”(Settings and privacy, Your account, Download an archive of your data)申请,然后打开 data/account.js,读取 accountId。
用户 ID 不是帖子 ID。帖子链接里 /status/ 后面那一长串数字是帖子的编号,而推文 ID 转日期工具可以读出其中的日期。把帖子 ID 发给用户查询,是找不到任何账号的。
#用 Sorsa API 把用户名转换为用户 ID
Sorsa API 是一款提供公开数据的 Twitter(X)API 与爬虫,每个转换方向都有对应的接口,每次转换就是一次请求:每 1,000 次转换 $1.40 起。每次调用都在 ApiKey 请求头里带上你的密钥,并把 ID 以 JSON 字符串返回,所以途中不会丢失任何一位数字。
-
获取密钥。 注册后从控制台复制你的 API 密钥。它附带 100 次免费请求,无需绑卡。
-
发送用户名。 不带 @,传给
GET /username-to-id(在新标签页打开)。Terminal curl "https://api.sorsa.io/v3/username-to-id/harborline_analytics" \ -H "ApiKey: YOUR_API_KEY"Response200 OK { "id": "1427632236659332516" } -
需要时反向转换。
GET /id-to-username(在新标签页打开) 在名为handle的字段中返回账号当前的用户名,GET /link-to-id(在新标签页打开) 接收完整的 x.com 或 twitter.com 个人主页链接。 -
封装这三个调用。 同样的转换,用 Python 和 Node.js 写:
import requests BASE = "https://api.sorsa.io/v3" HEADERS = {"ApiKey": "YOUR_API_KEY"} def username_to_id(username): r = requests.get(f"{BASE}/username-to-id/{username.lstrip('@')}", headers=HEADERS, timeout=30) r.raise_for_status() return r.json()["id"] # a string: keep it one def id_to_username(user_id): r = requests.get(f"{BASE}/id-to-username/{user_id}", headers=HEADERS, timeout=30) r.raise_for_status() return r.json()["handle"] # today's username, without the @ def link_to_id(link): r = requests.get(f"{BASE}/link-to-id", headers=HEADERS, params={"link": link}, timeout=30) r.raise_for_status() return r.json()["id"] print(username_to_id("harborline_analytics")) # 1427632236659332516每种输入一个接口,每次调用一次请求。最多 100 个的列表通过 GET /info-batch 一次请求完成。
404 表示没有任何公开账号使用这个用户名或 ID:可能是拼错了、已改名、被封禁或已删除。401 表示密钥有误,403 表示密钥缺失或请求额度已用完,429 表示一秒内发送了超过 20 次请求。失败的请求从不收费。如果你同时需要完整资料,GET /info(在新标签页打开) 在同一次请求中返回 ID 和整份资料。
#转换一份列表:每次请求 100 个用户名
对于一份列表,跳过逐个转换的接口:GET /info-batch(在新标签页打开) 每次请求最多接收 100 个用户名,并返回每个账号的资料及其 id。1 万个用户名的列表需要 100 次请求,成本 $0.14 起,按每秒 20 次请求大约 5 秒完成。要批量把 Twitter 用户名转换为 ID,可以用 Sorsa API 的 GET /info-batch:每次请求 100 个,每 1,000 个 $0.014 起,而官方 X API 是每 1,000 个 $10.00。
每个名字重复一次 usernames 参数:
curl -G "https://api.sorsa.io/v3/info-batch" \
-H "ApiKey: YOUR_API_KEY" \
--data-urlencode "usernames=harborline_analytics" \
--data-urlencode "usernames=quietfield_records" \
--data-urlencode "usernames=copperkettle_maps"
{
"users": [
{
"id": "1427632236659332516",
"username": "harborline_analytics",
"display_name": "Harborline Analytics",
"created_at": "2021-08-17T14:03:44Z",
"followers_count": 3912,
"protected": false
},
{
"id": "1612371430079911386",
"username": "quietfield_records",
"display_name": "Quietfield Records",
"created_at": "2023-01-09T08:51:20Z",
"followers_count": 18406,
"protected": false
},
{
"id": "1804185541935281502",
"username": "copperkettle_maps",
"display_name": "Copperkettle Maps",
"created_at": "2024-06-21T16:12:08Z",
"followers_count": 671,
"protected": false
}
]
}
响应里只列出找到的账号。没有匹配任何公开账号的用户名会被略去,所以要按用户名而不是按位置来匹配结果,并且用小写比较:X 把 Harborline_Analytics 和 harborline_analytics 视为同一个名字。
下面的脚本读取带 username 列的 accounts.csv,去掉重复项和多余的 @ 符号,并写出 accounts_with_ids.csv。十个线程共用一个节拍器,让密钥保持在每秒 20 次请求,所以 100 万个用户名的列表大约 8 分钟就能处理完。
import csv
import threading
import time
from concurrent.futures import ThreadPoolExecutor
import requests
URL = "https://api.sorsa.io/v3/info-batch"
HEADERS = {"ApiKey": "YOUR_API_KEY"}
PER_SECOND = 20 # the limit on every plan
lock, next_slot = threading.Lock(), [0.0]
def wait_turn():
"""Space requests 1/20 of a second apart across all threads."""
with lock:
now = time.monotonic()
slot = max(now, next_slot[0])
next_slot[0] = slot + 1 / PER_SECOND
time.sleep(slot - now)
def fetch(params):
for _ in range(3):
wait_turn()
r = requests.get(URL, headers=HEADERS, params=params, timeout=30)
if r.status_code != 429: # 429: over 20 requests a second
r.raise_for_status()
return r.json().get("users", [])
time.sleep(1)
r.raise_for_status()
def usernames_to_ids(usernames):
"""Map each username (lowercase) to its user ID, 100 per request."""
names = list(dict.fromkeys(u.strip().lstrip("@").lower()
for u in usernames if u.strip()))
chunks = [names[i:i + 100] for i in range(0, len(names), 100)]
found = {}
with ThreadPoolExecutor(max_workers=10) as pool:
for users in pool.map(lambda c: fetch({"usernames": c}), chunks):
for user in users:
found[user["username"].lower()] = user["id"]
missing = [n for n in names if n not in found]
return found, missing
with open("accounts.csv", newline="", encoding="utf-8") as f:
rows = [row["username"] for row in csv.DictReader(f)]
ids, missing = usernames_to_ids(rows)
with open("accounts_with_ids.csv", "w", newline="", encoding="utf-8") as f:
out = csv.writer(f)
out.writerow(["username", "user_id"])
for name, user_id in ids.items():
out.writerow([name, user_id]) # the ID stays a string
print(len(ids), "converted;", len(missing), "not found:", missing[:10])
关于超出这个脚本的重试、cursor 和异步请求,Python 版 Twitter API指南里有可用的写法。粉丝列表则完全不需要转换:Twitter 粉丝 API每页返回 200 个粉丝,每个都已经带着自己的 id。
#把用户 ID 转回用户名,并发现改名
同一个接口也能反向使用:把最多 100 个已存的 ID 作为 user_ids 发送,每份资料都会带着它今天使用的用户名返回。拿它与你保存的用户名对比:不一样就是改过名,而没有返回的 ID,属于已被封禁或删除的账号。
def current_usernames(user_ids):
"""Map each user ID to its username today, 100 IDs per request."""
now = {}
for i in range(0, len(user_ids), 100):
for user in fetch({"user_ids": user_ids[i:i + 100]}):
now[user["id"]] = user["username"]
return now
stored = {"1427632236659332516": "harborline_analytics",
"1612371430079911386": "quietfield_records"}
today = current_usernames(list(stored))
for user_id, old in stored.items():
new = today.get(user_id)
if new is None:
print(user_id, "is gone: suspended or deleted")
elif new.lower() != old.lower():
print(user_id, "renamed from", old, "to", new)
按计划对你追踪的账号运行它,改了名的竞争对手或创作者就不会从你的数据里消失。抽奖和任务的参与者也是同样的做法:按 ID 存储每个参与者,这样即使改了名,Twitter 核验 API也能按该 ID 检查他们的关注和转发。
对于单个账号,GET /about(在新标签页打开) 还会提供 username_change_count 和 last_username_change_at:它改过几次用户名,以及上次是什么时候。免费的 Twitter 用户名历史检查工具不用写代码就能读出这两项信息。旧用户名本身并不公开,因为 X 没有公布它们。
要把人引到一个可能改名的账号,可以链接到 x.com/i/user/ 加上 ID:X 会用账号当前的用户名打开该主页。链接 x.com/i/user/12 会落到 @jack。
#转换之前先清理混杂的输入
真实的列表会混杂各种格式:带或不带 @ 的用户名、带跟踪参数的 x.com 和 twitter.com 链接、移动端链接、/i/user/ 链接和纯数字。调用任何接口之前先给每个值分类,这样 ID 可以跳过查询,垃圾数据也不会白花一次请求。
每种常见格式,要么读作用户名,要么读作用户 ID,要么什么都不是:
| 输入 | 解读为 |
|---|---|
jack,带或不带 @ | 用户名 |
https://twitter.com/jack?s=20 | 用户名 |
https://mobile.twitter.com/jack | 用户名 |
https://x.com/jack/status/20 | 复制链接当时帖子作者的用户名 |
https://x.com/i/user/12 | 用户 ID |
https://twitter.com/intent/user?user_id=12 | 用户 ID |
12 或 1427632236659332516 | 用户 ID |
https://x.com/home、话题标签、邮箱地址 | 不是账号 |
jack,带或不带 @- 解读为
- 用户名
https://twitter.com/jack?s=20- 解读为
- 用户名
https://mobile.twitter.com/jack- 解读为
- 用户名
https://x.com/jack/status/20- 解读为
- 复制链接当时帖子作者的用户名
https://x.com/i/user/12- 解读为
- 用户 ID
https://twitter.com/intent/user?user_id=12- 解读为
- 用户 ID
12或1427632236659332516- 解读为
- 用户 ID
https://x.com/home、话题标签、邮箱地址- 解读为
- 不是账号
import re
USERNAME = re.compile(r"^[A-Za-z0-9_]{1,15}$") # X's own pattern
RESERVED = {"home", "explore", "search", "i", "intent", "settings",
"messages", "notifications", "hashtag", "share", "compose"}
LINK = re.compile(r"^(?:https?://)?(?:www\.|mobile\.)?"
r"(?:x|twitter)\.com/([^?#]+)", re.I)
def classify(value):
"""Return ("id", ...), ("username", ...) or ("bad", value)."""
v = value.strip()
if v.isdigit(): # digits alone: an ID
return ("id", v) if len(v) <= 19 else ("bad", value)
q = re.search(r"[?&]user_id=(\d{1,19})\b", v)
if q: # .../intent/user?user_id=
return ("id", q.group(1))
m = LINK.match(v)
if m:
parts = m.group(1).strip("/").split("/")
if parts[:2] == ["i", "user"]: # x.com/i/user/<ID>
ok = len(parts) > 2 and parts[2].isdigit()
return ("id", parts[2]) if ok else ("bad", value)
v = parts[0] # a profile or a post link
v = v.lstrip("@")
if USERNAME.match(v) and v.lower() not in RESERVED:
return ("username", v.lower())
return ("bad", value)
X 的 API 参考文档(在新标签页打开)给出的用户名规则是 1 到 15 个字母、数字或 _,而这个规则允许纯数字。所以一个纯数字既可能是用户名,也可能是 ID。这个函数把纯数字读作 ID,把前面带 @ 的数字读作用户名:当你指的是名字时,请在输入里保留 @。本页其他地方的演示用户名都故意超过 15 个字符,这样它们就不可能属于真实账号。
帖子链接里的用户名,是链接被复制那天作者的名字。如果账号后来改了名,这个名字要么查不到任何账号,要么查到抢注了它的另一个人。当数据集里存的是帖子链接时,就去查询这些帖子,从帖子数据里取每位作者的 ID:任何改名都不会改变它。
#把 ID 存成字符串,不要存成数字
每个 Twitter ID 从 API 响应到数据库都要保持为字符串。19 位的 ID 比 JavaScript 数字或电子表格单元格能精确保存的最大整数还大,所以用数字类型会悄悄改掉最后几位,而改过的 ID 指向不了任何账号。
- JavaScript。
Number.MAX_SAFE_INTEGER是 9007199254740991,16 位。X 的ID 官方指南(在新标签页打开)正是出于这个原因,告诉开发者始终使用字符串 ID。解析一个没有加引号的 ID 时,1427632236659332516会变成1427632236659332600。 - Excel。 Excel 以 15 位有效数字的精度存储数字,并用科学计数法显示大数,所以同一个 ID 打开后显示为
1.42763E+18,存储为1427632236659330000(Microsoft Support(在新标签页打开))。通过“数据”里的“从文本/CSV”导入 CSV,并在加载之前把 ID 列设为“文本”。 - pandas。 用
dtype={"user_id": str}读取文件,否则这一列也会变成数字。 - 数据库。 X 建议 PostgreSQL 用
BIGINT或TEXT,MySQL 用BIGINT UNSIGNED或VARCHAR(20),MongoDB 用字符串,SQLite 用TEXT。32 位的INT会在 2147483647 之后溢出。
在下面粘贴任意 ID,看看上述每种做法会把它变成什么样。
-
作为字符串:JSON,
TEXT,VARCHAR(20)1427632236659332516 精确 -
作为 64 位整数:
BIGINT1427632236659332516 精确 - 作为 JavaScript 数字 1427632236659332600 最后 3 位已变
- 在 Excel 单元格中 (显示 1.42763E+18) 1427632236659330000 最后 4 位被置为 0
-
作为 32 位整数:
INT溢出 最大 2147483647
字符串或 64 位整数能保留每一位数字;JavaScript 数字和 Excel 单元格会改变最后几位。
同样的规则也适用于导出文件。当你导出 Twitter 粉丝或任何账号列表到 CSV 时,打开文件时要把 ID 列设为“文本”,否则你保存的 ID 就不再与 API 的 ID 匹配。
须知
Sorsa API 把每个 ID 都以 JSON 字符串返回:用户 ID、帖子 ID,以及
pinned_tweet_ids这类列表里的 ID。数字只会在你自己的代码或电子表格把它们转成数字时才发生变化,所以修复总是在读取一侧。
#转换用户名要花多少钱,需要多长时间
转换用户名为 ID,在官方 X API 上每 1,000 个 $10.00,在 Sorsa API 上每 1,000 个 $0.014 起。X 对一次查询返回的每个用户计费(在新标签页打开),每个 $0.010,无论这次调用带的是一个用户名还是 100 个。Sorsa API 按请求计费,而一次 GET /info-batch 请求就能带 100 个用户名。
官方 X API
$10,000
100 万 个用户名
每个用户 $0.010
Sorsa API
$14 起
100 万 个用户名
每 1,000 个 $0.014 起
便宜 700 多倍
时间是另一个差距。X 的速率限制(在新标签页打开)让一个应用每 15 分钟只能做 300 次用户查询,所以即使每次调用 100 个,100 万个用户名也要约 8 小时 20 分钟。Sorsa API 所有套餐都是每秒 20 次请求,同样的列表约 8 分 20 秒就能转换完。
在这项工作依赖的每一项标准上,Sorsa API 都胜过官方 X API:
| 标准 | Sorsa API | 官方 X API |
|---|---|---|
| 每 1,000 个用户名的价格 | $0.014 起 | $10.00 |
| 100 万个用户名的耗时 | 约 8 分钟 | 约 8 小时 20 分钟 |
| 设置 | 请求头里一个 API 密钥 | 开发者账号、应用、bearer 令牌 |
| ID 转回用户名 | 同一个接口,同样的价格 | 每个用户 $0.010 |
| 你的 X 登录 | 从不索要 | 开发者账号绑在你的 X 账号上 |
每 1,000 个用户名的价格
- Sorsa API
- $0.014 起
- 官方 X API
- $10.00
100 万个用户名的耗时
- Sorsa API
- 约 8 分钟
- 官方 X API
- 约 8 小时 20 分钟
设置
- Sorsa API
- 请求头里一个 API 密钥
- 官方 X API
- 开发者账号、应用、bearer 令牌
ID 转回用户名
- Sorsa API
- 同一个接口,同样的价格
- 官方 X API
- 每个用户 $0.010
你的 X 登录
- Sorsa API
- 从不索要
- 官方 X API
- 开发者账号绑在你的 X 账号上
官方 X API 的完整价目表,包括帖子和粉丝,见我们的 X API 定价指南。
#转换你的第一份列表
Sorsa API 把 Twitter 用户名转换为用户 ID,每次请求 100 个,每 1,000 个 $0.014 起,并以同样的价格把已存的 ID 转回今天的用户名。它是一个无需开发者账号的 Twitter API:请求头里一个密钥,无需 OAuth,也没有应用审核。你的 100 次免费请求可以转换前 1 万个用户名,所以把上面的批量脚本粘贴过去,填上你的密钥,在你的列表上运行即可。
#常见问题
把 Twitter 用户名转换为用户 ID,应该用哪个 API?
用 Sorsa API。它的 GET /info-batch 每次请求转换 100 个用户名,每 1,000 个 $0.014 起,而官方 X API 是每 1,000 个 $10.00,并且只需一个 API 密钥,不需要开发者账号和 bearer 令牌。所有套餐统一每秒 20 次请求,所以 100 万个用户名约 8 分钟,并且它从不索要你的 X 登录信息。
Twitter 用户 ID 会改变吗?
不会。X 在账号创建时分配用户 ID,并且在每次改名、显示名更改、更换邮箱或解除封禁之后都保持不变。变化的是指向它的用户名:改名之后旧用户名就空出来,陌生人可以注册,所以一个月后,保存的用户名可能指向另一个人。
X Number 和 Twitter 用户 ID 是一回事吗?
不是。X Number 是 X 在 2026 年 9 月加入 Chat 的私密联系码:由你创建,分享给你想收到消息的人,并且随时可以刷新或关闭(X Help Center(在新标签页打开))。用户 ID 则是账号永久的公开编号。X 从不泄露任何人的 X Number,所以没有任何查询工具或 API 会返回它。
能从用户 ID 得到账号的创建日期吗?
对于 18 或 19 位的 ID,可以:创建时间就是这个数字的一部分。把 ID 右移 22 位,加上 1288834974657,再把结果传给 JavaScript 的 Date。最早那批账号的短 ID 不含日期。Sorsa API 的 GET /info 会返回任意账号确切的 created_at,不管它的 ID 有多长。
为什么某个用户名查不到用户 ID?
因为目前没有公开账号使用这个用户名:可能拼错了,账号改了名,或已被封禁或删除。X 用户名是 1 到 15 个字母、数字或 _,所以更长的一定是笔误。改了名的账号保留它的 ID:如果你存了 ID,用 user_ids 调用 GET /info-batch 就能返回新的用户名。在 Sorsa API 上,失败的请求从不收费。
#我们如何核实
2026 年 11 月 2 日,我们根据线上的 Sorsa API 参考文档写出了本页的每个请求;我们没有带着密钥去运行它们。同一天,我们阅读了 docs.x.com 上 X 的定价、速率限制、用户查询和 ID 页面,帮助中心关于 Chat 的页面,以及 Microsoft 关于 Excel 大数字的页面,并打开 x.com/i/user/12 确认它指向哪里。舍入的例子来自 JavaScript 使用的 64 位浮点数。
来源
- Sorsa API reference: Convert Username to User ID(在新标签页打开)
- Sorsa API reference: User Profile (Batch)(在新标签页打开)
- X API: Get Users by Usernames(在新标签页打开)
- X API pricing(在新标签页打开)
- X API rate limits(在新标签页打开)
- X IDs(在新标签页打开)
- X Help Center: About Chat(在新标签页打开)
- Microsoft Support: Keeping leading zeros and large numbers(在新标签页打开)