跳到正文

用 Python 做 Twitter 情感分析

Twitter 情感分析用打分模型给每条推文标上正面、负面或中性。在 1.23 万条人工标注的推文上,用推文训练的 RoBERTa 模型准确率是 72.3%,常用的 VADER 规则只有 53.0%。

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

简短回答: 先用搜索查询采集一批推文样本,再给每条打分:Transformer 模型是最准的打分器,VADER 是最快的。采集这一步,官方 X API 每 1,000 条帖子收费 $5.00。用 Sorsa API 的话,POST /search-tweets 每次请求返回约 20 条推文,每 1,000 条 $0.07 起,100 次免费请求就够跑完第一轮 2,000 条推文。

接口
POST /search-tweets
每页条数
每次请求约 20 条推文
价格
每 1,000 条 $0.07 起

#Twitter 情感分析是怎么工作的

情感分析读取一条推文的文本,返回三个标签之一,通常还附带一个置信度分数,再对几百条或几百万条推文的标签计数,以衡量人们对某个话题的感受。每个项目都经历同样的五步:

  1. 采集提到该话题的推文,用搜索查询加上日期窗口。

  2. 清洗文本:替换 @提及和链接,去掉转发和重复内容。

  3. 打分:用模型给每条推文判为正面、中性或负面。

  4. 检验:在你自己标注的几百条推文上检查模型。

  5. 汇总:把标签汇总成按天、按产品或按账号计的数字。

    1 收集到的推文 @nadia_reviews_gear oh great, the update broke Bluetooth AGAIN https://t.co/k3v9q2 2 送入模型前清理 @user oh great, the update broke Bluetooth AGAIN http 大写和标点保留。 3 RoBERTa 打分 负面 86.0% 中性 12.5% 正面 1.5% 4 打标签,再计数 负面 “oh great” 被读成讽刺 当天净情绪: 正面占比减去负面占比
    模型给每条推文单独打分;你最终报告的数字来自把标签加总。

品牌在新品发布或服务中断之后对提及运行它,客服团队对自己帖子下的回复运行它,分析师对某个股票代码或代币运行它,研究者则对一场辩论或选举运行它。它衡量的是人群的倾向,而不是某个人的本意:模型读的是字面,所以讽刺和褒贬混杂的推文(“相机很棒,电池很糟”)是它最容易出错的地方。

有两个决定对结果的影响超过其他一切:你采集哪些推文,以及你信任哪种打分器。接下来的几节按这个顺序讲。

#用 Sorsa API 获取推文

采集这一步可以用 Sorsa API,一款 Twitter(X)API 与爬虫:一次搜索请求返回约 20 条推文,支持 X 完整的搜索语法,每 1,000 条推文 $0.07 起,所有套餐都是每秒 20 次请求。你只需要一个 API 密钥,不需要 X 开发者账号,而且 Sorsa API 从不要求 X 登录信息,所以你的任何账号都不会有风险。

按你要回答的问题选择接口:

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

  2. 写好查询。 在花钱之前先定好样本的形状:lang:en 为英文模型只保留一种语言,-filter:retweets 去掉重复,-from: 排除品牌自己的帖子,since: 加 until: 固定时间窗口。其余运算符见我们的 Twitter 搜索运算符列表,免费的推特高级搜索工具能在你写代码之前显示一条查询会返回什么。

  3. 发送一次测试请求。 一次请求返回约 20 条推文和一个指向下一页的 cursor。cursor 是指向下一页结果的令牌。

    Terminal
    curl -X POST "https://api.sorsa.io/v3/search-tweets" \
      -H "ApiKey: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"order": "latest",
           "query": "\"battery life\" headphones lang:en -filter:retweets"}'
    
    Response200 OK
    {
      "tweets": [
        {
          "id": "2116943508275302411",
          "full_text": "Two full days of battery on these headphones",
          "created_at": "2026-11-01T18:42:09Z",
          "lang": "en",
          "is_reply": false,
          "likes_count": 31,
          "reply_count": 4,
          "retweet_count": 2,
          "user": {
            "id": "1834470112963551232",
            "username": "nadia_reviews_gear",
            "followers_count": 2318
          }
        }
      ],
      "next_cursor": "DAADDAABCgABHWAk1xBVYAoKAAIdYCTSHxWQ"
    }
    
  4. 把各页收集进一张表。 把 next_cursor 传回去,直到它返回为空,每个推文 ID 只保留一行。返回 429 表示一秒内超过了 20 次请求,等一会儿再重试;返回 403 表示密钥缺失或请求已用完。

    Python
    import time
    
    import pandas as pd
    import requests
    
    URL = "https://api.sorsa.io/v3/search-tweets"
    HEADERS = {"ApiKey": "YOUR_API_KEY"}
    
    
    def collect(query, limit=2000):
        body = {"query": query, "order": "latest"}
        rows = {}
        while len(rows) < limit:
            r = requests.post(URL, headers=HEADERS, json=body, timeout=30)
            if r.status_code == 429:
                time.sleep(1)
                continue
            r.raise_for_status()
            page = r.json()
            for t in page.get("tweets", []):
                rows[t["id"]] = {
                    "id": t["id"],
                    "created_at": t["created_at"],
                    "text": t["full_text"],
                    "lang": t.get("lang"),
                    "is_reply": t.get("is_reply", False),
                    "likes": t.get("likes_count", 0),
                    "author": t["user"]["username"],
                }
            if not page.get("next_cursor"):
                break
            body["next_cursor"] = page["next_cursor"]
        return pd.DataFrame(rows.values())
    
    
    df = collect('"battery life" headphones lang:en -filter:retweets')
    print(len(df), "tweets")
    

采集这些推文要花多少钱

在 Sorsa API 上,一份情感分析样本通过搜索采集,每 1,000 条推文 $0.07 起;官方 X API 每读取 1,000 条帖子收费 $5.00。

要分析的推文数

官方 X API

$500

10 万条推文

每条帖子 $0.005

Sorsa API

$7 起

10 万条推文

每 1,000 条 $0.07 起

在官方 X API 上,账单还会随作者数量增长:从 2026 年 11 月 22 日起,X 把随每条帖子返回的用户资料按一次用户读取另行计费,每条帖子多收 $0.010(见 X 的通知(在新标签页打开)),而且它的按量付费方案每月最多读取 300 万条帖子(见 X 的价格表(在新标签页打开))。在 Sorsa API 上,每条推文都带有作者的完整资料,失败的请求永远不计费。

同一条查询的各页是一页接一页拿到的,因为每一页都需要上一页的 cursor。样本很大时,把时间窗口拆成一天一天,让各天并行运行,一个密钥最多每秒 20 次请求。同样的搜索可以回溯到 2006 年,长时间序列的做法见我们的历史 Twitter 数据指南。

#按模型期望的方式清洗推文

清洗要比大多数教程做得少:把 @提及换成 @user,把链接换成 http,去掉转发和完全重复的内容,并保留表情符号、大写字母和标点。RoBERTa 模型正是在以这种方式处理过的文本上训练的,而 VADER 会把大写字母、感叹号和表情符号读成更强烈的感受。

Python
import re


def prepare(text):
    text = re.sub(r"@\w+", "@user", text)
    return re.sub(r"https?://\S+", "http", text)


df = df[df["lang"] == "en"]
df = df[~df["text"].str.startswith("RT @")]
df["clean"] = df["text"].map(prepare)
df = df.drop_duplicates(subset="clean")

完全重复的内容比看上去更要紧:抽奖和机器人活动会把同一句话发上几百遍,一场活动就能让一天的得分大幅摆动。至于近似重复和一份手工标注的标准集,我们的构建用于机器学习的 Twitter 数据集指南里有代码。

须知

许多教程照抄的清洗正则 [^a-zA-Z0-9\s],会删掉每个表情符号和每个感叹号。这两样都带有情感:在 VADER 自己的示例里,一行有三个表情符号、没有其他情感词的文字,得分是 +0.70。

#四种情感打分方法,在 1.23 万条推文上实测

在 TweetEval 情感测试集(1.23 万条由人工标成正面、中性或负面的推文)上,RoBERTa 模型的准确率是 72.3%,用 4.56 万条标注推文训练的逻辑回归是 59.6%,VADER 是 53.0%,TextBlob 是 48.3%。我们在 2026 年 11 月 2 日用 4 个 CPU 线程把四种方法都跑了一遍;完整得分见下表。

方法准确率宏平均召回率判为正面的比例(人工标注:19.3%)每秒处理推文数
RoBERTa(cardiffnlp,latest)72.3%73.4%20.0%7
TF-IDF + 逻辑回归59.6%60.1%21.1%3.4 万
VADER,±0.05 区间53.0%57.0%38.9%2.76 万
TextBlob,按极性正负48.3%49.4%37.2%6,867
  • RoBERTa(cardiffnlp,latest)

    准确率
    72.3%
    宏平均召回率
    73.4%
    判为正面的比例(人工标注:19.3%)
    20.0%
    每秒处理推文数
    7
  • TF-IDF + 逻辑回归

    准确率
    59.6%
    宏平均召回率
    60.1%
    判为正面的比例(人工标注:19.3%)
    21.1%
    每秒处理推文数
    3.4 万
  • VADER,±0.05 区间

    准确率
    53.0%
    宏平均召回率
    57.0%
    判为正面的比例(人工标注:19.3%)
    38.9%
    每秒处理推文数
    2.76 万
  • TextBlob,按极性正负

    准确率
    48.3%
    宏平均召回率
    49.4%
    判为正面的比例(人工标注:19.3%)
    37.2%
    每秒处理推文数
    6,867
标签正确率,基于人工标注的 1.23 万条推文 RoBERTa(Cardiff NLP) 基于 1.24 亿条推文训练的模型 72.3% TF-IDF + 逻辑回归 基于 4.56 万条标注推文训练 59.6% VADER 词表和规则,±0.05 区间 53.0% TextBlob 形容词词典,取极性符号 48.3% 把每条推文都判为中性:48.3%
用推文训练的模型遥遥领先。VADER 和 TextBlob 是即时出结果,但会错判大约一半的标签。

准确率是指与人工标注结果一致的推文占比。作为参照,把每条推文都判成中性也能得到 48.3%,因为测试推文里将近一半是中性的,所以 VADER 比什么都不做只多了不到 5 个百分点。宏平均召回率是 TweetEval 自己的得分:每种方法分别找出了多少负面、中性和正面推文,取平均,因此一种方法不能靠把所有推文都判成中性来取胜。有两点要注意:RoBERTa 模型在 TweetEval 的训练集上做过微调,所以这次测试是它的主场,逻辑回归也是从同一个训练集学来的。在你自己的话题上,预计两者都会掉几个点,并按第 4 步所说的做检验。速度来自一台共用的 4 核服务器,当时还在忙别的任务,所以请把它当作下限。

RoBERTa:最准确

Cardiff NLP 的 twitter-roberta-base-sentiment-latest(在新标签页打开) 模型是一个 RoBERTa 模型,在 2018 年到 2021 年的约 1.24 亿条推文上训练,再针对三种标签微调。它在普通 CPU 上就能运行,不需要 API 密钥;按我们在忙碌的服务器上测得的速度,1 万条推文大约需要 23 分钟,所以样本很大时,要么分批通宵跑,要么用 GPU。

Python
from transformers import pipeline

scorer = pipeline(
    "sentiment-analysis",
    model="cardiffnlp/twitter-roberta-base-sentiment-latest",
)
out = scorer(df["clean"].tolist(), batch_size=32, truncation=True)
df["label"] = [o["label"] for o in out]
df["confidence"] = [round(o["score"], 3) for o in out]
输出
positive 0.978  Battery life on these headphones is great, two full days
positive 0.859  Oh great, another update that breaks Bluetooth. Love ...
neutral  0.789  The firmware update rolls out to all devices on Monday.
positive 0.960  not bad at all tbh, @user was right http
negative 0.860  @user oh great, the update broke Bluetooth AGAIN http

对于其他语言的推文,同一个团队的 twitter-xlm-roberta-base-sentiment(在新标签页打开) 模型在约 1.98 亿条推文上训练,并针对 8 种语言做了微调,其中包括西班牙语、葡萄牙语、法语和阿拉伯语。如果要的是情绪而不是正负极性,他们的情绪模型会返回 11 种标签,比如愤怒、恐惧和喜悦。

VADER:即时的规则,中性的界线较弱

VADER 是一份带分数的词表(约 7,500 个词),再加上针对大写字母、感叹号、“but” 和否定的规则,是为社交媒体设计的。它在单个核心上每秒约处理 2.76 万条推文,用来初步看一眼一百万条推文很合适。它的弱点是中性区间:它把 38.9% 的测试推文判成正面,是人工标注 19.3% 的两倍。

Python
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer

vader = SentimentIntensityAnalyzer()


def vader_label(text, band=0.05):
    c = vader.polarity_scores(text)["compound"]
    if c >= band:
        return "positive"
    return "negative" if c <= -band else "neutral"


df["vader"] = df["text"].map(vader_label)
推文VADER(compound)RoBERTa
Battery life on these headphones is great, two full dayspositive (+0.62)positive (0.98)
Oh great, another update that breaks Bluetooth. Love that for me.positive (+0.85)positive (0.86)
The firmware update rolls out to all devices on Monday.neutral (+0.00)neutral (0.79)
not bad at all tbh, @kai_builds_tools was right https://t.co/x1y2z3(在新标签页打开)positive (+0.43)positive (0.96)
@nadia_reviews_gear oh great, the update broke Bluetooth AGAIN https://t.co/k3v9q2(在新标签页打开)positive (+0.32)negative (0.86)
  • Battery life on these headphones is great, two full days

    VADER(compound)
    positive (+0.62)
    RoBERTa
    positive (0.98)
  • Oh great, another update that breaks Bluetooth. Love that for me.

    VADER(compound)
    positive (+0.85)
    RoBERTa
    positive (0.86)
  • The firmware update rolls out to all devices on Monday.

    VADER(compound)
    neutral (+0.00)
    RoBERTa
    neutral (0.79)
  • not bad at all tbh, @kai_builds_tools was right https://t.co/x1y2z3(在新标签页打开)

    VADER(compound)
    positive (+0.43)
    RoBERTa
    positive (0.96)
  • @nadia_reviews_gear oh great, the update broke Bluetooth AGAIN https://t.co/k3v9q2(在新标签页打开)

    VADER(compound)
    positive (+0.32)
    RoBERTa
    negative (0.86)

讽刺有时能难倒两种打分器。两者都把 “Love that for me” 读成了称赞;最后一条推文里,RoBERTa 识破了讽刺,而 VADER 看到 “great” 就判成了正面。

逻辑回归:用你自己的标注来训练

TF-IDF 加逻辑回归的模型,从你自己标注的推文中学习,所以能贴合你这个话题的用词:球鞋话题里的 “sick”,加密货币话题里的 “bullish”。它在 4.56 万条 TweetEval 推文上训练了 13 秒,达到 59.6%。当你有几千条标注,而词汇又是通用模型不认识的,它就值得用。

Python
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline

model = make_pipeline(
    TfidfVectorizer(ngram_range=(1, 2), min_df=2,
                    sublinear_tf=True),
    LogisticRegression(max_iter=2000,
                       class_weight="balanced"),
)
model.fit(train_texts, train_labels)
df["lr"] = model.predict(df["clean"])

第四种方法 TextBlob,给一份英文形容词词表打分,这份词表是作者在影评上测试过的,只有当极性恰好为零时才把推文判为中性。它的得分是 48.3%,与把每条推文都判成中性相同,所以在推文上毫无增益。大语言模型是第五种选择:把推文和三个标签作为提示给它,它能识破词表漏掉的讽刺,但每条推文都要花钱,而且重跑时可能给出不同的答案。在你把它用于整个数据集之前,先用同一份手工标注的样本检验。

#中性区间决定你的结论

对 VADER 来说,你选的阈值比推文本身更能左右结果。按 VADER 文档里的 ±0.05 区间,它把 38.9% 的测试推文判成正面;把区间放宽到 ±0.45,正面占比降到 21.4%,准确率升到 58.1%。拖动滑块,就能看到每一步的变化。

-0.05 至 +0.05
53.0% 的 12.3K 条推文标注正确
38.9% 被判为正面,人工标注则为 19.3%
正面 38.9%
中性 31.3%
负面 29.8%

柱状:VADER 对各标签的占比。折线:人工标注者给出的占比。TweetEval 情感测试集,12.3K 条推文,评分日期 2026 年 11 月 2 日。

同样的 12.3K 条推文,用 VADER 评分一次;只改变中性区间。

发布的每个数字,都要报告所用的方法和阈值,也不要拿两种不同打分器得出的占比做比较。如果用 VADER,就在你自己话题的几百条手工标注推文上调好这个区间。

#把标签变成可以追踪的分数

大多数团队追踪的数字是净情感:正面推文占比减去负面推文占比,范围从 -100 到 +100。按天计算,并剔除推文少于约 100 条的日子,因为在那样的日子里,几条帖子就能让分数大幅摆动。

Python
df["day"] = pd.to_datetime(df["created_at"]).dt.date
shares = pd.crosstab(df["day"], df["label"], normalize="index")
daily = pd.DataFrame({
    "tweets": df.groupby("day").size(),
    "net": ((shares["positive"] - shares["negative"]) * 100).round(1),
})
print(daily[daily["tweets"] >= 100])

回复和原创帖子要分开(用 is_reply 字段),因为抱怨往往集中在品牌自己帖子下的回复里。只有在有意为之时才按点赞数给推文加权,并在图表旁注明。要让分数保持最新,就按计划运行采集,把 since: 设为上次运行的时间;大规模轮询见我们的 Twitter 实时监测指南。

#今天就做你的第一次情感分析

用 Sorsa API 的 POST /search-tweets 采集几千条你的话题的推文,每次请求约 20 条,每 1,000 条 $0.07 起,用 RoBERTa 模型打分,再在你自己的 200 条推文上检验标签。新账号的 100 次免费请求可以覆盖前 2,000 条推文。

#常见问题

Twitter 情感分析有多准?

这取决于模型。在 1.23 万条人工标注的推文(TweetEval 测试集)上,Cardiff NLP 的 RoBERTa 模型标对了 72.3%,用 4.56 万条推文训练的逻辑回归是 59.6%,VADER 是 53.0%,TextBlob 是 48.3%。讽刺、混杂的观点和话题黑话造成了大部分错误,所以任何模型都要在你自己话题的几百条推文上检验。

怎么获取用于情感分析的推文?

通过 API,用查询加日期窗口去搜索。Sorsa API 的 POST /search-tweets 支持 X 的搜索运算符,每次请求返回约 20 条带完整正文和作者的推文,每 1,000 条推文 $0.07 起。官方 X API 需要开发者账号,每读取 1,000 条帖子收费 $5.00。如果想要现成的标注集来训练,可以看 TweetEval 这样的公开数据集。

做情感分析该用哪个 Twitter API?

用 Sorsa API 来采集推文:它每次搜索请求返回约 20 条推文,每 1,000 条 $0.07 起,官方 X API 是 $5.00,支持 X 完整的搜索语法并可回溯到 2006 年,只需一个 API 密钥,不需要开发者账号,所有套餐都是每秒 20 次请求。打分则在你自己的代码里运行,用你选的模型。

能免费做 Twitter 情感分析吗?

第一个项目可以。VADER、TextBlob 和 Cardiff NLP 的 RoBERTa 模型都免费,在笔记本上就能运行。至于推文,每个新 Sorsa API 账号都有 100 次免费请求:通过搜索大约能得到 2,000 条推文,无需绑卡,永不过期。TweetEval 这样的标注基准数据集可以免费下载,用来测试模型。

能分析其他语言的推文吗?

可以,用多语言模型。Cardiff NLP 的 twitter-xlm-roberta-base-sentiment 在约 1.98 亿条推文上训练,并针对 8 种语言的情感做了微调,其中包括西班牙语、葡萄牙语、法语和阿拉伯语。VADER 和英文版 RoBERTa 模型只读英文,所以要用查询里的 lang: 运算符,以及每条推文里的 lang 字段来按语言筛选。

Sorsa API 会返回情感分数吗?

不会:Sorsa API 返回的是推文,带完整正文、语言、互动数量和作者,打分在你的代码里运行。这样模型由你来选,出现更好的模型时,你也可以给旧推文重新打分。Sorsa API 在 2025 年之前叫 TweetScout API;推文和搜索的接口用法不变。

#我们如何核实

2026 年 11 月 2 日,我们从 Cardiff NLP 的仓库下载了 TweetEval 情感数据,并用 VADER 3.3.2、TextBlob 0.20.1、在 4.56 万条训练推文上训练的 TF-IDF 加逻辑回归模型(scikit-learn 1.9.1),以及 twitter-roberta-base-sentiment-latest(transformers 5.22.1,PyTorch 2.15,4 个 CPU 线程)给 1.23 万条推文的测试集打了分;本页的示例输出来自同一次运行。请求和响应的形态遵循线上的 Sorsa API 参考,官方 X API 的价格来自 docs.x.com,也是当天阅读的。

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

更新内容
  • 重写:在 1.23 万条人工标注的推文上给四种方法打分,新增 VADER 阈值滑块、2026 年采集推文的方式,以及现行的 X API 价格。
  • 首次发布。