跳到正文

为机器学习构建 Twitter 数据集

机器学习用的 Twitter 数据集,是一组带有文本、日期和作者的推文,要么从 Sentiment140(2009 年的 160 万条推文)这类公开存档下载,要么通过 API 新采集。

  • 更新于
  • 发布于
  • 阅读约 11 分钟

简短回答: 公开数据集免费,但已有多年历史,而且多数只提供推文 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,其中有几个如今已不再提供推文文本。

数据集规模采集时间如今能拿到什么
Sentiment140160 万条推文,其中 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 账号。

1 收集 主题推文、回复串或 ID 列表 POST /search-tweets ID:POST /tweet-info-bulk 2 清理 去掉转发、副本和近似重复 lang, made_with_ai 把账号改为 @user,链接改为 http 3 标注 手工做一个黄金集,其余预标注 label_source human、model 或 query 4 拆分 整串回复放一起,最新几周作测试集 conversation_id_str 然后按 created_at 切分 5 保存 每行一条记录,附数据集说明 train.jsonl, test.jsonl 查询、日期、恢复率
采集只是循环里的一次 API 调用。决定模型质量的大部分工作发生在采集之后:清洗、标签和公平的划分。

按数据集的定义方式选择接口:

  1. 获取密钥。 注册后,在控制台复制你的 API 密钥。前 100 次请求免费,无需绑卡。

  2. 写查询。 搜索运算符能在你付费之前就塑造数据集:lang:en 只保留一种语言,-filter:retweets 去掉转推的副本,since: 和 until: 限定时间窗口。先在免费的推特高级搜索工具里手动测试查询。

  3. 发送一次测试请求。 一次请求返回约 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"
    }
    
  4. 翻页并保存原始 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。

Python
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
  1. 保留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!!
  2. 保留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
  3. 保留Battery life on the beta is noticeably better this week

    battery life on the beta is noticeably better this week

  4. 保留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 和清洗后的文本并排保存,以后改清洗规则时就不用重新采集。

Python
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”采集的推文偏向负面。只能用它来冷启动,因为查询标签教给模型的是查询里的词,而不是情感。

#划分训练集和测试集,避免泄漏

划分推文数据集要按对话和时间,绝不逐行划分:把最新的几周留作测试集,并让每个对话串都在分界线的同一侧。随机划分会把一条帖子和它的回复,或同一段文本的两份副本,同时放进训练集和测试集,测试分数衡量的就成了记忆而不是能力。

训练集 测试集 推文按日期排列,从旧到新 随机拆分 每一行随机进训练集或测试集 同一回复串落在两边:测试集已被见过 按回复串分组,按日期切分 整串回复放一起,最新几周作测试集 该回复串留在训练集 最新几周
先按 conversation_id_str 分组,再按日期切分。这样测试集就像下个月的推文,正是模型将要遇到的数据。
Python
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 次请求的限制算出来的。

Sorsa API 团队撰写。 我们开发和运营 Sorsa API,这是只读的 Twitter/X API 与爬虫,2022 年起运行于生产环境(前身为 TweetScout API),累计处理请求超 60 亿次。 关于 Sorsa API

更新内容
  • 全文重写:公开数据集及其现状、构建新数据集、补全 ID、用近似重复检测清洗、无泄漏的划分、格式、成本,以及 X 当前对分享和训练的规则。
  • 首次发布。