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件ずつ個別にスコアリングします。報告する数値は、ラベルを足し合わせて得られます。
ブランドは新製品の発売や障害の後にメンションへ、サポートチームは自社の投稿へのリプライへ、アナリストはティッカーやトークンへ、研究者は議論や選挙へこの分析を使います。測れるのは群衆の傾きであって、1人の意図ではありません。モデルは言葉を読むので、皮肉や、「カメラは最高、バッテリーは最悪」のような混在した内容のツイートで、最もよく失敗します。
結果を最も左右する判断は2つあります。どのツイートを集めるか、そしてどのスコアリング手法を信頼するかです。次の節から、この順に説明します。
#Sorsa APIでツイートを集める
収集のステップには、Twitter(X)API兼スクレイピングツールであるSorsa APIが使えます。検索リクエスト1回で、Xの検索構文をフルに使って約20ツイートが返り、1,000ツイートあたり$0.07から、全プラン毎秒20リクエストです。必要なのはAPIキー1つで、Xの開発者アカウントは不要です。Sorsa APIはXのログイン情報を求めないため、あなたのアカウントが危険にさらされることもありません。
知りたいことに合わせて、エンドポイントを選びます。
- 話題、製品、ブランドへの意見:
POST /search-tweets(新しいタブで開きます) - 1つのアカウント宛て、またはそのアカウントについての発言(いいねやリプライの最小数を指定):
POST /mentions(新しいタブで開きます)。TwitterメンションAPIのガイドで解説しています。 - 1つの投稿に付いた反応:
POST /comments(新しいタブで開きます)。Xでコメントを見る方法のガイドが読み取るのと同じリプライです。 - すでに持っているツイートID:
POST /tweet-info-bulk(新しいタブで開きます)。1リクエストで100ツイート、1,000件あたり$0.014からです。
-
キーを取得します。 登録して、ダッシュボードからAPIキーをコピーします。最初の100リクエストは無料で、カードは不要です。
-
クエリを書きます。 料金を払う前に、サンプルの形を決めます。英語モデル用に1言語へ絞るなら
lang:en、コピーを除くなら-filter:retweets、ブランド自身の投稿を外すなら-from:、期間を固定するならsince:とuntil:です。残りはTwitterの検索演算子の一覧にあり、無料のTwitter高度な検索ツールなら、コードを書く前にクエリの結果を確認できます。 -
テストリクエストを送ります。 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" } -
ページを集めて表にします。
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も、大文字、感嘆符、絵文字を、より強い感情として読み取ります。
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
正解率とは、人が付けたラベルと同じラベルになったツイートの割合です。目安として、すべてのツイートをニュートラルとすると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で実行してください。
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倍です。
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 days | positive (+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%でした。数千件のラベルがあり、汎用モデルが知らない語彙を扱うときに効果を発揮します。
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%に上がります。スライダーを動かして、すべての段階を確認してください。
棒: VADERによる各ラベルの割合。線: 人間の評価者による割合。TweetEvalのセンチメントテストセット、12.3Kツイート、2026年11月2日に採点。
同じ12.3KツイートをVADERで1回採点した結果です。変わるのは中立帯だけです。
公表する数値には、必ず手法とそのしきい値を添え、異なるスコアリング手法で出した割合を比べてはいけません。VADERを使うなら、自分の話題から手作業でラベル付けした数百件のツイートで、帯を調整してください。
#ラベルを追跡できるスコアに変える
多くのチームが追跡する数値は、純センチメントです。ポジティブなツイートの割合からネガティブなツイートの割合を引いたもので、-100から+100の範囲になります。日ごとに計算し、ツイートが約100件に満たない日は除外します。そうした日は、数件の投稿でスコアが大きく振れるからです。
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によります。
出典
- TweetEval benchmark and data, Cardiff NLP(新しいタブで開きます)
- twitter-roberta-base-sentiment-latest model card(新しいタブで開きます)
- twitter-xlm-roberta-base-sentiment model card(新しいタブで開きます)
- VADER sentiment README(新しいタブで開きます)
- X API pricing(新しいタブで開きます)
- X notice on billing post expansions, Oct 2026(新しいタブで開きます)
- Sorsa API reference: Search Tweets(新しいタブで開きます)