为机器学习构建 Twitter 数据集
机器学习用的 Twitter 数据集,是一组带有文本、日期和作者的推文,要么从 Sentiment140(2009 年的 160 万条推文)这类公开存档下载,要么通过 API 新采集。
简短回答: 公开数据集免费,但已有多年历史,而且多数只提供推文 ID,要重新查询一遍才有文本。新采集一套数据,官方 X API 每 1,000 条帖子收费 $5.00。要构建新数据集,用 Sorsa API:POST /search-tweets 按主题采集推文,每 1,000 条 $0.07 起,100 次免费请求足够做一次 2,000 条推文的试采。
- 接口
POST /search-tweets- 每页条数
- 每次请求约 20 条推文
- 价格
- 每 1,000 条 $0.07 起
#Twitter 数据集从哪里来
每个 Twitter(X)数据集都来自两处之一:别人已经发布的集合,或者你自己通过 API 采集的推文。已发布的数据集免费,几分钟就能加载。自己采集的集合则能贴合你的主题、语言、日期和标签,因为这些都由你来定;我们的如何爬取推特数据指南对比了 2026 年拉取推文的所有方式。
最知名的公开数据集采集于 2009 年到 2022 年之间,当时 Twitter 还向研究者免费开放 API,其中有几个如今已不再提供推文文本。
| 数据集 | 规模 | 采集时间 | 如今能拿到什么 |
|---|---|---|---|
| Sentiment140 | 160 万条推文,其中 498 条用于测试 | 2009 年 | 文本,带有根据表情符号得出的正面或负面标签 |
| TweetEval(Cardiff NLP) | 7 个分类任务 | 2020 年的基准 | 情感、情绪、反讽、仇恨、冒犯性语言、表情符号和立场的文本与标签 |
| COVID-19-TweetIDs | 超过 10 亿条推文 | 自 2020 年 1 月起 | 仅有推文 ID |
| ArchiveTeam Twitter archive | 约 160 亿条记录 | 2011 年至 2022 年 | 原始 JSON,无标签 |
| SNAP Twitter7(斯坦福) | 4.76 亿条推文 | 2009 年 | 无:应 Twitter 要求已撤下 |
Sentiment140
- 规模
- 160 万条推文,其中 498 条用于测试
- 采集时间
- 2009 年
- 如今能拿到什么
- 文本,带有根据表情符号得出的正面或负面标签
TweetEval(Cardiff NLP)
- 规模
- 7 个分类任务
- 采集时间
- 2020 年的基准
- 如今能拿到什么
- 情感、情绪、反讽、仇恨、冒犯性语言、表情符号和立场的文本与标签
COVID-19-TweetIDs
- 规模
- 超过 10 亿条推文
- 采集时间
- 自 2020 年 1 月起
- 如今能拿到什么
- 仅有推文 ID
ArchiveTeam Twitter archive
- 规模
- 约 160 亿条记录
- 采集时间
- 2011 年至 2022 年
- 如今能拿到什么
- 原始 JSON,无标签
SNAP Twitter7(斯坦福)
- 规模
- 4.76 亿条推文
- 采集时间
- 2009 年
- 如今能拿到什么
- 无:应 Twitter 要求已撤下
如果只需要通用的情感或情绪基线,带标签的数据集是最快的起点。社区维护的 awesome-twitter-data 列表(在新标签页打开)收录了数百个其他数据集,并标明哪些仍提供文本。如果需求具体,比如用户对你产品的反应、今年发生的事件、存档不覆盖的语言,或你自己的标签体系,就需要自己构建数据集。
#公开 Twitter 数据集的局限
公开 Twitter 数据集有四个共同问题:时间久远,多数只含 ID,这些 ID 逐年失效,而且标签是为别人的任务做的。
- 文本陈旧。 Sentiment140 里的每条推文都不超过 140 个字符,因为 Twitter 直到 2017 年 11 月才把上限提高到 280。用 2009 年的语言训练的模型,会遇到从未见过的 2026 年的俚语、话题和帖子长度。
- 只有 ID,没有文本。 对外分享的数据集通常只包含帖子 ID 和用户 ID,所以许多研究数据集,包括大型 COVID-19 集合,都只是一串数字。要拿回文本,需要补全(rehydrate)这些 ID:通过 API 把每个 ID 重新查询一遍。
- 失效。 被删除的推文,或者作者转为私密、被封禁的推文,都不会再返回。丢失的行并非随机,所以重建后的数据集会偏向仍然活跃的账号。
- 借来的标签。 Sentiment140 的标签来自表情符号,不是人读过推文后标注的,而基准数据集的标签回答的是基准的问题,不是你的问题。
要让模型理解今年的帖子,只剩一条路:新采集一套数据,并针对一项任务清洗和标注。
#如何用 Sorsa API 构建新的 Twitter 数据集
构建新数据集,用 Sorsa API,一款 Twitter(X)API 与爬虫:一个 API 密钥,支持 X 的搜索运算符并可回溯到 2006 年,每次请求约 20 条推文,每 1,000 条 $0.07 起,所有套餐每秒 20 次请求,所以采集一个 100 万条推文的主题数据集约需 42 分钟。每条推文都附带作者的完整资料,不额外收费,也不会动用你的任何 X 账号。
按数据集的定义方式选择接口:
- 主题、关键词或日期范围:
POST /search-tweets(在新标签页打开)。 - 某一个账号的帖子:
POST /user-tweets(在新标签页打开),见我们的下载某个用户的全部推文指南。 - 一组精选账号,当作一个 X 列表来读取:
GET /list-tweets(在新标签页打开)。 - 对话数据所需的回复串:
POST /comments(在新标签页打开)。
-
获取密钥。 注册后,在控制台复制你的 API 密钥。前 100 次请求免费,无需绑卡。
-
写查询。 搜索运算符能在你付费之前就塑造数据集:
lang:en只保留一种语言,-filter:retweets去掉转推的副本,since:和until:限定时间窗口。先在免费的推特高级搜索工具里手动测试查询。 -
发送一次测试请求。 一次请求返回约 20 条推文,以及指向下一页的 cursor。
终端 curl -X POST "https://api.sorsa.io/v3/search-tweets" \ -H "ApiKey: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"order": "latest", "query": "crashing lang:en -filter:retweets since:2026-10-01"}'响应200 OK { "tweets": [ { "id": "2115807747265175863", "full_text": "The new beta keeps crashing on launch for me", "created_at": "2026-10-29T14:07:31Z", "lang": "en", "likes_count": 12, "reply_count": 3, "retweet_count": 0, "conversation_id_str": "2115807747265175863", "in_reply_to_tweet_id": null, "is_reply": false, "made_with_ai": false, "user": { "id": "1710221540156513285", "username": "maya_trains_models", "followers_count": 412, "created_at": "2023-10-06T09:12:44Z", "verified": false } } ], "next_cursor": "DAADDAABCgABHV5mIq0VsAoKAAIdXmUz8Kqg" } -
翻页并保存原始 JSONL。 把
next_cursor传回去,直到返回为空,并把每条推文原样写入,一行一个 JSON 对象。清洗之后在副本上进行,这样同样的推文永远不用付两次采集费。Python import json import time import requests URL = "https://api.sorsa.io/v3/search-tweets" HEADERS = {"ApiKey": "YOUR_API_KEY"} QUERY = "crashing lang:en -filter:retweets since:2026-10-01" def collect(query, path, limit=50_000): body = {"query": query, "order": "latest"} saved = 0 with open(path, "a", encoding="utf-8") as out: while saved < limit: r = requests.post(URL, headers=HEADERS, json=body, timeout=30) if r.status_code == 429: # over 20 requests per second time.sleep(1) continue r.raise_for_status() # 403: no key, or no requests left page = r.json() for tweet in page.get("tweets", []): tweet["_query"] = query out.write(json.dumps(tweet, ensure_ascii=False) + "\n") saved += 1 if not page.get("next_cursor"): break body["next_cursor"] = page["next_cursor"] return saved print(collect(QUERY, "raw_tweets.jsonl"), "tweets saved")
每个类别或主题跑一条查询,各自写入单独的文件,每个文件的条数能立刻告诉你某个类别是否太少。要回溯到今年以前,同一个接口可以检索 2006 年以来的每条公开推文;我们的推特历史数据指南介绍了大规模回填时如何按日期切片。
#补全推文 ID 数据集
要重建已发布的 ID 数据集,把推文 ID 发送到 Sorsa API 的 POST /tweet-info-bulk(在新标签页打开),每次最多 100 个,返回什么就保留什么。100 万个 ID 的列表花费 $14 起,按每秒 20 次请求约 8 分钟完成;同样的 100 万条帖子在官方 X API 上,仅帖子读取就要 $5,000。
import json
import requests
URL = "https://api.sorsa.io/v3/tweet-info-bulk"
HEADERS = {"ApiKey": "YOUR_API_KEY"}
with open("tweet_ids.txt", encoding="utf-8") as f:
ids = list(dict.fromkeys(x.strip() for x in f if x.strip()))
found = set()
with open("rehydrated.jsonl", "w", encoding="utf-8") as out:
for i in range(0, len(ids), 100):
body = {"tweet_links": ids[i:i + 100]}
r = requests.post(URL, headers=HEADERS, json=body, timeout=30)
r.raise_for_status()
for tweet in r.json().get("tweets", []):
found.add(tweet["id"])
out.write(json.dumps(tweet, ensure_ascii=False) + "\n")
missing = [x for x in ids if x not in found]
print(f"recovered {len(found)} of {len(ids)}; {len(missing)} gone")
请在数据集中注明恢复率:多少个 ID 返回了结果,总共多少个,在哪一天。用同一批 ID 间隔一年重建的两个数据集,其实是两个不同的数据集,看你结果的人需要知道你训练用的是哪一个。
须知
推文 ID 从头到尾都要当作文本处理。一个 ID 有 19 位数字,而电子表格只保留 15 位有效数字,JavaScript 数字约保留 16 位,所以经过 Excel 或
JSON.parse的 ID 会被四舍五入,对应不上任何推文。读取 ID 文件时要按字符串读取,保存时加上引号。
#清洗并去重推文
清洗推文数据集分四遍:按 ID 去掉转推和完全重复的内容,合并近似重复的文本,按语言和作者过滤,再把用户名和链接换成占位符。每一遍用到的字段,都包含在每条推文的返回结果里。
| 字段 | 在数据集中的作用 |
|---|---|
id | 主键;按字符串存储 |
full_text | 用于清洗、标注和训练的文本 |
lang | 让每个划分只含一种语言 |
conversation_id_str | 把同一串对话归为一组,划分时可以保持在一起 |
in_reply_to_tweet_id | 把回复关联到它的上级,用于构造对话对 |
made_with_ai | 标记 X 标注为 AI 生成的帖子;人类文本数据集应排除它们 |
user.followers_count、user.created_at | 垃圾账号和机器人过滤所用的作者信号 |
likes_count、reply_count | 质量底线:没人互动的帖子往往是垃圾内容 |
id- 在数据集中的作用
- 主键;按字符串存储
full_text- 在数据集中的作用
- 用于清洗、标注和训练的文本
lang- 在数据集中的作用
- 让每个划分只含一种语言
conversation_id_str- 在数据集中的作用
- 把同一串对话归为一组,划分时可以保持在一起
in_reply_to_tweet_id- 在数据集中的作用
- 把回复关联到它的上级,用于构造对话对
made_with_ai- 在数据集中的作用
- 标记 X 标注为 AI 生成的帖子;人类文本数据集应排除它们
user.followers_count、user.created_at- 在数据集中的作用
- 垃圾账号和机器人过滤所用的作者信号
likes_count、reply_count- 在数据集中的作用
- 质量底线:没人互动的帖子往往是垃圾内容
近似重复是多数管道会跳过的一遍。带评论的转推、复制的帖子、机器人活动,以及十个账号粘贴的同一条抱怨,都能通过 ID 检查,并且会放大它们所说的内容。先规范化文本(转小写,去掉链接、用户名和标点),再比较词的 shingle:两条帖子如果三词片段大部分相同,说的就是同一件事。下面的检测器会对你粘贴的内容做这个测试。
- 推文
- 7
- 清洗后唯一
- 4
- 已移除重复
- 3
-
保留The new beta keeps crashing on launch :( https://t.co/k2Vq81
the new beta keeps crashing on launch
- 相同文本RT @priya_ships_features: The new beta keeps crashing on launch :(
- 匹配度 71%the new beta keeps crashing on launch for me!!
-
保留Loving the new dark mode, finally easy on the eyes
loving the new dark mode finally easy on the eyes
- 相同文本@nadia_tests_builds loving the new dark mode... finally easy on the eyes https://t.co/x9Lm2
-
保留Battery life on the beta is noticeably better this week
battery life on the beta is noticeably better this week
-
保留Does anyone else get logged out after the update?
does anyone else get logged out after the update
每行粘贴一条推文。清洗后的文本与另一行共享至少 70% 的三词片段时,两行归为一组,每组只保留第一行。
对于模型要读取的文本,把每个用户名替换为 @user,把每个链接替换为 http,这是 Cardiff NLP 的推文模型所用的约定。这样模型学到的是人们怎么写,而不是哪些账号和链接出现得最多。把原始的 full_text 和清洗后的文本并排保存,以后改清洗规则时就不用重新采集。
import hashlib
import json
import re
def norm(text):
text = re.sub(r"https?://\S+", " ", text.lower())
text = re.sub(r"^rt @\w+:?|@\w+", " ", text)
text = re.sub(r"[^\w\s#]", " ", text)
return " ".join(text.split())
seen, rows = set(), []
for line in open("raw_tweets.jsonl", encoding="utf-8"):
t = json.loads(line)
if t.get("retweeted_status") or t.get("made_with_ai"):
continue
clean = norm(t["full_text"])
key = hashlib.sha1(clean.encode()).hexdigest()
if key in seen or len(clean.split()) < 4:
continue
seen.add(key)
rows.append(t)
哈希能抓出规范化之后完全相同的副本;对于百万级规模的模糊匹配,用 MinHash 加局部敏感哈希来做 shingle 比较,不必逐对比较。
#标注数据集
推文数据集分两层标注:一份几百条推文的金标准集,由人工逐条标注;其余大部分由模型预标注,模型拿不准的地方再由人来检查。金标准集从不进入训练;它是检验其余标签够不够好的测试。
动笔之前先把标注规则写下来,每个类别和疑难情形都给出示例:讽刺、疑问、引用别人的观点。让两个人标注同样的 100 条推文并对比结果,就能看出规则在哪里含糊。每个标签都要存下它是谁或什么产生的(human、model、query),以后就可以剔除或降低较弱标签的权重。
搜索查询本身也可以充当弱标签:为“keeps crashing”采集的推文偏向负面。只能用它来冷启动,因为查询标签教给模型的是查询里的词,而不是情感。
#划分训练集和测试集,避免泄漏
划分推文数据集要按对话和时间,绝不逐行划分:把最新的几周留作测试集,并让每个对话串都在分界线的同一侧。随机划分会把一条帖子和它的回复,或同一段文本的两份副本,同时放进训练集和测试集,测试分数衡量的就成了记忆而不是能力。
import hashlib
rows.sort(key=lambda t: t["created_at"])
cut = rows[int(len(rows) * 0.9)]["created_at"] # newest 10% = test
test = [t for t in rows if t["created_at"] >= cut]
threads = {t.get("conversation_id_str") for t in test}
rest = [t for t in rows if t["created_at"] < cut
and t.get("conversation_id_str") not in threads]
def bucket(t):
key = t.get("conversation_id_str") or t["id"]
return int(hashlib.md5(key.encode()).hexdigest(), 16) % 10
train = [t for t in rest if bucket(t) != 0]
valid = [t for t in rest if bucket(t) == 0]
如果任务针对账号而不是帖子,比如推特机器人检测,就改按 user.id 分组,这样同一个账号不会同时出现在两边。
#保存成模型能读的格式
推文数据集保存为 JSONL,一行一条记录,每条记录都带推文 ID。JSONL 支持流式读取、追加写入,也能直接加载到 pandas、Hugging Face 的 datasets 库和大多数训练脚本里。每行放哪些字段,取决于任务。
| 任务 | 每行 JSONL 包含什么 |
|---|---|
| 分类器或主题模型 | id、清洗后的 text、label、label_source、lang、created_at |
| 模型或提示词的评测集 | id、text、预期答案,以及它所测试的规则 |
| 用于检索的 RAG 索引 | id、text、帖子 URL、created_at 和作者 ID,这样回答可以引用帖子 |
分类器或主题模型
- 每行 JSONL 包含什么
id、清洗后的text、label、label_source、lang、created_at
模型或提示词的评测集
- 每行 JSONL 包含什么
id、text、预期答案,以及它所测试的规则
用于检索的 RAG 索引
- 每行 JSONL 包含什么
id、text、帖子 URL、created_at和作者 ID,这样回答可以引用帖子
{
"id": "2115807747265175863",
"text": "the new beta keeps crashing on launch for me",
"label": "negative",
"label_source": "human",
"lang": "en",
"created_at": "2026-10-29T14:07:31Z",
"split": "test"
}
随文件附上一份数据集说明:查询和日期、采集或补全的日期、恢复率、标签的生成方式、过滤掉了什么,以及这套数据不应该用于什么。Hugging Face 的数据集卡片遵循同样的提纲,下一个用你的数据训练的人(往往就是半年后的你)需要其中的每一行。
#Twitter 数据集要花多少钱
一个 100 万条推文的数据集,在官方 X API 上仅帖子读取就要 $5,000,而在 Sorsa API 上,按搜索采集 $70 起,补全已知 ID 则 $14 起。切换规模,即可看到按搜索新采集的总价。
官方 X API
$5,000
100 万 条推文
每条帖子 $0.005
Sorsa API
$70 起
100 万 条推文
每 1,000 条 $0.07 起
官方 X API 有两项限制,会改变数据集的成本账。它的按量付费套餐每月读取 300 万条帖子后封顶,所以 1,000 万条推文的数据集需要 Enterprise 合同,X 的价目表(在新标签页打开)在超过上限后也指向这条路。而且自 2026 年 11 月 22 日起,X 还会把随每条帖子返回的作者资料按用户读取另外计费,每条帖子多 $0.010(见 X 的通知(在新标签页打开))。在 Sorsa API 上,作者的完整资料是每条推文的一部分,失败的请求从不收费。
时间是预算的另一半。按每秒 20 次请求,Sorsa API 按搜索每秒约采集 400 条推文,按 ID 每秒最多 2,000 条,所以一个密钥采集 100 万条新推文约需 42 分钟,采集 1,000 万条的数据集约需 7 小时。
#如何分享推文数据集并用于训练
公开的研究数据集以推文 ID 和用户 ID 的形式发布,拿到 ID 的人都能通过 API 取回全文:Sorsa API 补全 100 万个 ID 约需 8 分钟。
- 分享。 像公开研究数据集那样,发布推文 ID 和用户 ID,并附上补全脚本;
POST /tweet-info-bulk每次请求可把 100 个 ID 还原成完整帖子。 - 训练。 采集到的全文留给你自己的模型使用:分类器、主题模型、评测集、检索索引和语言模型微调,是推文数据集的常见用途。
- 个人。 训练前要替换用户名,不要为个人建立档案,刷新数据集时要移除作者已删除的推文。
Sorsa API 只返回公开数据,从不发帖、点赞或关注。有研究项目的高校还可以咨询 Sorsa API 的学术访问。
#先试采,再开始你的数据集
公开 Twitter 数据集给你一条基线;新采集的数据集给你自己的主题、自己的日期,以及贴合任务的标签。用 Sorsa API 构建:按搜索每 1,000 条推文 $0.07 起,按 ID 每 1,000 条 $0.014 起,每秒 20 次请求,每条推文都带作者资料。你的 100 次免费请求可以采集前 2,000 条推文,足够在花钱之前测试查询、清洗和划分。
#常见问题
Twitter 数据集是什么?
Twitter 数据集是保存在文件中、用于分析或机器学习的 X(前身为 Twitter)帖子集合:通常包含推文 ID、文本、日期、语言、互动数和作者字段,有时还带情感等标签。许多已发布的数据集只含推文 ID,这是研究界分享数据的惯例,所以文本需要重新查询。
哪里可以免费下载 Twitter 数据集?
Kaggle、Hugging Face 和 GitHub 上的 awesome-twitter-data 列表托管着数百个免费的 Twitter 数据集,其中有 2009 年含 160 万条带标签推文的 Sentiment140,以及 TweetEval 基准。多数采集于 2023 年之前,许多只分享推文 ID,要通过 API 补全之后才有文本。Sorsa API 补全 ID 每次请求 100 个,每 1,000 条 $0.014 起。
构建 Twitter 数据集该用哪个 API?
构建 Twitter 数据集用 Sorsa API:支持 X 运算符并可回溯到 2006 年的搜索,每次请求返回约 20 条推文,每 1,000 条 $0.07 起;补全 ID 每次请求 100 个,每 1,000 条 $0.014 起;所有套餐每秒 20 次请求,只需一个 API 密钥,不用开发者账号,也不用 OAuth。官方 X API 每 1,000 条帖子收费 $5.00,并且每月读取 300 万条帖子后封顶。
可以用 Twitter 数据训练大语言模型吗?
可以。X 上的公开对话是训练和微调数据的常见来源,推文数据集也常用于情感和主题分类器、评测集,以及让助手引用真实帖子的检索(RAG)索引。Sorsa API 以每秒 20 次请求的速度采集这些数据:搜索每次请求约 20 条推文,每 1,000 条 $0.07 起;按 ID 每次请求 100 条,每 1,000 条 $0.014 起。
采集 100 万条推文要多久?
在 Sorsa API 上,一个密钥按每秒 20 次请求,按搜索采集 100 万条推文约需 42 分钟,按推文 ID 约需 8 分钟。之后清洗和去重会去掉一部分,所以先用一次试采测出损耗,再多采集这么多,才能在最后达到你想要的规模。
#我们如何核实
2026 年 11 月 2 日,我们阅读了 docs.x.com 上 X 的按量付费价目表,并核对了 Sentiment140、TweetEval 和 SNAP 的 Twitter7 的数据集页面。本页上的每个 Sorsa API 接口、字段和每页条数,都与当天的在线 API 参考文档一致;所示响应遵循该参考文档的结构,我们没有针对 API 运行这些代码示例。采集时间是根据每页条数和每秒 20 次请求的限制算出来的。