本文へスキップ

PythonでTwitterの感情分析をする方法

Twitterの感情分析は、スコアリングモデルで各ツイートをポジティブ、ネガティブ、ニュートラルに分類します。人がラベル付けした1.23万件のツイートで、ツイートで学習したRoBERTaモデルの正解率は72.3%、定番のVADERルールは53.0%でした。

要点: 検索クエリでツイートのサンプルを集め、1件ずつスコアリングします。精度が最も高いのはトランスフォーマーモデル、最も速いのはVADERです。収集にかかる費用は、公式X APIでは1,000投稿あたり$5.00です。Sorsa APIなら、POST /search-tweets が1リクエストで約20ツイートを返し、1,000件あたり$0.07からです。100回分の無料リクエストで、最初の2,000ツイートを取得できます。

エンドポイント
POST /search-tweets
1ページの件数
1リクエストあたり約20ツイート
料金
1,000件あたり$0.07から

#Twitter感情分析の仕組み

感情分析は、ツイートの本文を読み、3つのラベルのいずれかを、たいていは確信度のスコアつきで返します。そのラベルを数百件から数百万件のツイートにわたって集計し、ある話題に対する人々の感じ方を測ります。どのプロジェクトも、同じ5つのステップで進みます。

  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」は皮肉と判定 その日のネットセンチメント: ポジティブ% − ネガティブ%
    モデルはツイートを1件ずつ個別にスコアリングします。報告する数値は、ラベルを足し合わせて得られます。

ブランドは新製品の発売や障害の後にメンションへ、サポートチームは自社の投稿へのリプライへ、アナリストはティッカーやトークンへ、研究者は議論や選挙へこの分析を使います。測れるのは群衆の傾きであって、1人の意図ではありません。モデルは言葉を読むので、皮肉や、「カメラは最高、バッテリーは最悪」のような混在した内容のツイートで、最もよく失敗します。

結果を最も左右する判断は2つあります。どのツイートを集めるか、そしてどのスコアリング手法を信頼するかです。次の節から、この順に説明します。

#Sorsa APIでツイートを集める

収集のステップには、Twitter(X)API兼スクレイピングツールであるSorsa APIが使えます。検索リクエスト1回で、Xの検索構文をフルに使って約20ツイートが返り、1,000ツイートあたり$0.07から、全プラン毎秒20リクエストです。必要なのはAPIキー1つで、Xの開発者アカウントは不要です。Sorsa APIはXのログイン情報を求めないため、あなたのアカウントが危険にさらされることもありません。

知りたいことに合わせて、エンドポイントを選びます。

  1. キーを取得します。 登録して、ダッシュボードからAPIキーをコピーします。最初の100リクエストは無料で、カードは不要です。

  2. クエリを書きます。 料金を払う前に、サンプルの形を決めます。英語モデル用に1言語へ絞るなら lang:en、コピーを除くなら -filter:retweets、ブランド自身の投稿を外すなら -from:、期間を固定するなら since: と until: です。残りはTwitterの検索演算子の一覧にあり、無料のTwitter高度な検索ツールなら、コードを書く前にクエリの結果を確認できます。

  3. テストリクエストを送ります。 1リクエストで約20ツイートと、次のページへのカーソルが返ります。カーソルとは、結果の次のページを指すトークンです。

    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ごとに1行を残します。429は1秒間に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万件の場合

1投稿$0.005

Sorsa API

$7から

ツイート10万件の場合

1,000件あたり$0.07から

公式X APIでは、投稿者の数に応じて請求額も増えます。2026年11月22日から、Xは各投稿に付いて返されるプロフィールをユーザー読み取りとして課金し、投稿1件につき$0.010が加算されます(Xのお知らせ(新しいタブで開きます))。従量課金プランは、月間300万件の投稿読み取りで止まります(Xの料金表(新しいタブで開きます))。Sorsa APIでは、どのツイートにも投稿者のプロフィール全体が含まれ、失敗したリクエストに課金されることはありません。

1つのクエリのページは、直前のページのカーソルが必要なので、順番にしか取得できません。大きなサンプルでは、期間を日ごとに分けて、その日ごとの取得を並行して走らせます。1つのキーで毎秒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")

完全なコピーは、見た目以上に重要です。プレゼント企画やボットのキャンペーンは、同じ文を何百回も投稿し、1つのキャンペーンがその日のスコアを大きく動かすことがあります。ほぼ同一の重複と、手作業のラベルによる正解セットについては、機械学習用のTwitterデータセットの作り方ガイドにコードがあります。

知っておきたいこと

多くのチュートリアルが写しているクリーニング用の正規表現 [^a-zA-Z0-9\s] は、すべての絵文字とすべての感嘆符を削除します。どちらも感情を伝えるものです。VADER自身の例では、絵文字3つだけで他に意見を表す語がない文が+0.70とスコアリングされます。

#感情を採点する4つの手法を1.23万件のツイートで検証

TweetEvalの感情テストセットは、人がポジティブ、ニュートラル、ネガティブとラベル付けした1.23万件のツイートです。これを使ったところ、RoBERTaモデルの正解率は72.3%、4.56万件のラベル付きツイートで学習したロジスティック回帰は59.6%、VADERは53.0%、TextBlobは48.3%でした。4つとも2026年11月2日に、CPU 4スレッドで実行しています。全スコアは表のとおりです。

手法正解率マクロ再現率ポジティブと判定した割合(人は19.3%)1秒あたりのツイート数
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%
    1秒あたりのツイート数
    7
  • TF-IDF + ロジスティック回帰

    正解率
    59.6%
    マクロ再現率
    60.1%
    ポジティブと判定した割合(人は19.3%)
    21.1%
    1秒あたりのツイート数
    3.4万
  • VADER、±0.05の帯

    正解率
    53.0%
    マクロ再現率
    57.0%
    ポジティブと判定した割合(人は19.3%)
    38.9%
    1秒あたりのツイート数
    2.76万
  • TextBlob、極性の符号

    正解率
    48.3%
    マクロ再現率
    49.4%
    ポジティブと判定した割合(人は19.3%)
    37.2%
    1秒あたりのツイート数
    6,867
人手ラベル付き12.3K件のツイートでの正解率 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独自のスコアで、各手法がネガティブ、ニュートラル、ポジティブの各ツイートをどれだけ見つけたかの平均です。そのため、すべてをニュートラルと判定する手法は高いスコアを取れません。注意点が2つあります。RoBERTaモデルはTweetEvalの学習用の分割でファインチューニングされているので、このテストはいわばホームグラウンドです。ロジスティック回帰も同じ分割から学習しました。自分の話題では、どちらも数ポイント下がると見込み、ステップ4のとおり確認してください。速度は、他の作業で混み合っていた共有の4コアサーバーで測ったので、下限の目安として扱ってください。

RoBERTa:最も精度が高い

Cardiff NLPのtwitter-roberta-base-sentiment-latest(新しいタブで開きます)モデルは、2018年から2021年の約1.24億件のツイートで学習したRoBERTaモデルを、3つのラベル向けにファインチューニングしたものです。通常の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」、否定のルールを加えたもので、ソーシャルメディア向けに作られています。1コアで毎秒約2.76万ツイートを処理したため、100万ツイートの最初の確認には十分です。弱点はニュートラルの帯で、テストのツイートの38.9%をポジティブと判定しました。人の判定である19.3%の2倍です。

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」など、その話題の語に合わせられます。TweetEvalの4.56万ツイートで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"])

4つ目の手法のTextBlobは、作者が映画レビューでテストした英語の形容詞辞書でスコアリングし、極性がちょうどゼロのときだけ、ツイートをニュートラルと判定します。スコアは48.3%で、すべてのツイートをニュートラルとした場合と同じなので、ツイートには何の上積みもありません。大規模言語モデルは5つ目の選択肢です。ツイートと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で1回採点した結果です。変わるのは中立帯だけです。

公表する数値には、必ず手法とそのしきい値を添え、異なるスコアリング手法で出した割合を比べてはいけません。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 で、話題に関するツイートを数千件集めます。1リクエストで約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の検索演算子を受け付け、1リクエストで約20ツイートを全文と投稿者つきで返し、1,000ツイートあたり$0.07からです。公式X APIは開発者アカウントが必要で、投稿の読み取り1,000件あたり$5.00を課金します。学習用のラベル付き済みデータが必要なら、TweetEvalなどの公開データセットがあります。

感情分析には、どのTwitter APIを使えばよいですか?

ツイートの収集には、Sorsa APIを使ってください。検索リクエスト1回で約20ツイートが返り、1,000件あたり$0.07からと、公式X APIの$5.00を下回ります。Xの検索構文を2006年までフルに使え、APIキー1つで開発者アカウントは不要、全プラン毎秒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の感情データをダウンロードし、1.23万件のテストセットを、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、CPU 4スレッド)でスコアリングしました。このページの出力例も、同じ実行から得たものです。リクエストとレスポンスの形は公開中のSorsa APIリファレンスに沿っており、公式X APIの料金は同日に読んだdocs.x.comによります。

Sorsa APIチーム 読み取り専用のX(Twitter)APIとスクレイピングツール、Sorsa APIを開発・運営しています。2022年から本番稼働し(旧TweetScout API)、累計60億リクエスト以上を処理しています。 Sorsa APIについて

変更点
  • 書き直しました。人がラベル付けした1.23万件のツイートで4つの手法を採点し、VADERのしきい値スライダーと、2026年時点のツイート収集方法、最新のX API料金を追加しています。2026-04-07: 初回公開。