Quick Answer
TwexAPI で リアルタイム X 感情分析パイプライン を作るには、POST /twitter/advanced_search/page を searchTerms と sortBy: "Latest" でポーリングし、next_cursor でページングし、tweet_id で重複排除し、新規投稿だけをスコアリングします。raw tweet JSON、クエリ、取得時刻、言語、モデルバージョン、感情スコア、ラベル、レビュー状態を保存し、各ダッシュボード値を監査できるようにします。スコアはモデル出力として扱い、高影響または境界サンプルだけを重いモデルや人間レビューへ送ります。
FAQ
一括検索ではなく Advanced Search by Page を使う理由は?
リアルタイムダッシュボードには、再現できるポーリングとページングが必要です。/twitter/advanced_search/page は現在ページ、has_next_page、next_cursor を返すため、ワーカーは毎回取得ページ数を制限し、tweet_id で重複スコアリングを避けられます。
感情モデルを実行する前に何を保存しますか?
raw tweet JSON、正規化した tweet_id、クエリ、sortBy、取得時刻、created_at_datetime、言語、投稿者ハンドル、エンゲージメント数を保存します。モデルやしきい値が変わっても、同じ元データを再処理できます。
軽量モデルではなく LLM を使う場面は?
軽量モデルは広い範囲を速く処理するために使います。LLM に送るのは、高エンゲージメント、ニュートラル付近の境界スコア、皮肉の疑い、重要アカウント、業務対応につながる可能性があるアラートに絞ります。両方のモデルバージョンを保存します。
感情分析で困るのは、データが古いことと、スコアの根拠をたどれないことです。ダッシュボードに「市場感情 -0.42」とだけ表示されても、どの投稿、どのクエリ、どのモデルバージョン、いつ取得したデータなのか分からなければ、分析担当者は信頼できません。
より安定した方法は、感情分析をデータパイプラインとして扱うことです。TwexAPI Advanced Search by Page で最近の投稿をポーリングし、tweet_id で重複排除し、raw JSON を保存してから軽量モデルでスコアリングします。後で VADER を transformer や LLM に置き換えても、収集、重複排除、スコアリング、集計、レビューの層は変えない方が安全です。
sentiment_score は世論の最終結論ではなく、特定のモデル、バージョン、サンプル、クエリから出た出力として扱います。元投稿、クエリ、モデルバージョン、サンプル数、レビュー状態まで戻れる場合にだけ、スコアは説明可能になります。
パイプライン構成
Answer: パイプライン構成とは、この事例で api.twexapi.io の TwexAPI Bearer API を使う手順です。通常の読み取りは 10 Credits(約 $0.10/1K)、対象プランは 20+ QPS です。2026-08-20 時点の公式 Post/User read は $5/$10 per 1K で、rate limit は endpoint ごとに異なります。
このパイプラインは、X 投稿を検索結果から監査可能な感情指標へ変換します。通常は 5 つの部分に分かれます。
- クエリ設計:ブランド、イベント、言語、除外条件、時間窓をクエリ戦略に入れる。
- 収集:
POST /twitter/advanced_search/pageを呼び、sortBy: "Latest"で最新投稿を取得する。 - 保存:raw JSON を保存し、スコアリング前に
tweet_idで重複排除する。 - スコアリング:多くの投稿は軽量モデルで処理し、境界例や高影響投稿だけ重いモデルへ送る。
- 集計とレビュー:時間窓ごとに平均、件数、異常例、モデルバージョンを出力する。
ステップ 1:ポーリング収集
Answer: ステップ 1:ポーリング収集は本ガイドの TwexAPI エンドポイントを Bearer で呼び出して実装します。バッチ/ページングで約 10 Credits/回・20+ QPS です。
ポーリング収集では、ストリーミング接続があるかのように扱うのではなく、固定間隔で最新の検索結果を取得します。TwexAPI の POST /twitter/advanced_search/page は 1 ページ最大 20 件のツイートを返します。初回は next_cursor を省略し、次ページから前回レスポンスの next_cursor を渡します。
次の例では sortBy: "Latest" を使い、seen_ids で重複スコアリングを避けます。本番では seen_ids をメモリではなくデータベースやキャッシュに保存してください。
1import os
2from datetime import datetime, timezone
3
4import requests
5
6API_URL = "https://api.twexapi.io/twitter/advanced_search/page"
7TOKEN = os.environ["TWEXAPI_BEARER_TOKEN"]
8
9def search_page(query, next_cursor=None):
10 payload = {
11 "searchTerms": [query],
12 "sortBy": "Latest"
13 }
14
15 if next_cursor:
16 payload["next_cursor"] = next_cursor
17
18 response = requests.post(
19 API_URL,
20 headers={
21 "Authorization": f"Bearer {TOKEN}",
22 "Content-Type": "application/json",
23 "Accept": "application/json",
24 },
25 json=payload,
26 timeout=30,
27 )
28 response.raise_for_status()
29 data = response.json()
30
31 if data.get("code", 200) >= 400:
32 raise RuntimeError(data.get("msg", "TwexAPI returned an error"))
33
34 return data
35
36def collect_recent_tweets(query, seen_ids, max_pages=3):
37 next_cursor = None
38 collected = []
39
40 for page_number in range(max_pages):
41 page = search_page(query, next_cursor)
42
43 for tweet in page.get("data", []):
44 if not tweet:
45 continue
46
47 tweet_id = tweet.get("tweet_id") or tweet.get("id")
48 if not tweet_id or tweet_id in seen_ids:
49 continue
50
51 seen_ids.add(tweet_id)
52 collected.append({
53 "query": query,
54 "fetched_at": datetime.now(timezone.utc).isoformat(),
55 "raw": tweet,
56 })
57
58 if not page.get("has_next_page"):
59 break
60
61 next_cursor = page.get("next_cursor")
62 if not next_cursor:
63 break
64
65 return collected
66
67seen_ids = set()
68tweets = collect_recent_tweets(
69 query='"crypto regulation" lang:en -filter:retweets',
70 seen_ids=seen_ids,
71 max_pages=2,
72)
73
74print(f"New tweets ready for scoring: {len(tweets)}")ステップ 2:高速感情スコアリング
Answer: ステップ 2:高速感情スコアリングは本ガイドの TwexAPI エンドポイントを Bearer で呼び出して実装します。バッチ/ページングで約 10 Credits/回・20+ QPS です。
高速感情スコアリングでは、説明しやすく低コストなモデルで新規投稿全体に基礎スコアを付け、その後で一部のサンプルだけ重いモデルへ渡します。これにより、ダッシュボードの更新速度を保ちつつ、すべてのテキストを高価な LLM に送らずに済みます。
英語のソーシャルテキストでは、VADER を初期スコアリングに使えます。多言語では lang で分流し、英語は VADER、他言語は対応モデルまたは翻訳後の共通モデルを使います。
1from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
2
3analyzer = SentimentIntensityAnalyzer()
4MODEL_VERSION = "vader-3.3.2-en-baseline"
5
6def sentiment_label(score):
7 if score >= 0.2:
8 return "positive"
9 if score <= -0.2:
10 return "negative"
11 return "neutral"
12
13def text_for_model(tweet):
14 return tweet.get("full_text") or tweet.get("text") or ""
15
16def score_tweets(records):
17 scored = []
18
19 for record in records:
20 tweet = record["raw"]
21 text = text_for_model(tweet).strip()
22 if not text:
23 continue
24
25 compound = analyzer.polarity_scores(text)["compound"]
26 scored.append({
27 "tweet_id": tweet.get("tweet_id") or tweet.get("id"),
28 "query": record["query"],
29 "fetched_at": record["fetched_at"],
30 "created_at_datetime": tweet.get("created_at_datetime"),
31 "lang": tweet.get("lang"),
32 "text": text,
33 "sentiment_score": compound,
34 "sentiment_label": sentiment_label(compound),
35 "model_version": MODEL_VERSION,
36 "favorite_count": tweet.get("favorite_count"),
37 "retweet_count": tweet.get("retweet_count"),
38 "reply_count": tweet.get("reply_count"),
39 "raw": tweet,
40 })
41
42 return scored
43
44scored_tweets = score_tweets(tweets)
45print(f"Scored tweets: {len(scored_tweets)}")ステップ 3:時間窓の集計
Answer: ステップ 3:時間窓の集計は本ガイドの TwexAPI エンドポイントを Bearer で呼び出して実装します。バッチ/ページングで約 10 Credits/回・20+ QPS です。
時間窓の集計では、単一投稿のスコアを、傾向として見られる時間単位にまとめます。平均値だけでは不十分です。サンプル数、ポジティブ比率、ネガティブ比率、高エンゲージメントの異常例も保存します。
1def summarize_window(scored_rows):
2 if not scored_rows:
3 return {
4 "tweet_count": 0,
5 "avg_sentiment": None,
6 "positive_ratio": 0,
7 "negative_ratio": 0,
8 "top_negative_examples": [],
9 }
10
11 tweet_count = len(scored_rows)
12 avg_sentiment = sum(row["sentiment_score"] for row in scored_rows) / tweet_count
13 positive_count = sum(1 for row in scored_rows if row["sentiment_label"] == "positive")
14 negative_count = sum(1 for row in scored_rows if row["sentiment_label"] == "negative")
15
16 top_negative_examples = sorted(
17 scored_rows,
18 key=lambda row: (row["sentiment_score"], -int(row.get("reply_count") or 0)),
19 )[:5]
20
21 return {
22 "tweet_count": tweet_count,
23 "avg_sentiment": round(avg_sentiment, 4),
24 "positive_ratio": round(positive_count / tweet_count, 4),
25 "negative_ratio": round(negative_count / tweet_count, 4),
26 "top_negative_examples": [
27 {
28 "tweet_id": row["tweet_id"],
29 "score": row["sentiment_score"],
30 "text": row["text"][:180],
31 }
32 for row in top_negative_examples
33 ],
34 }
35
36summary = summarize_window(scored_tweets)
37print(summary)ステップ 4:本番環境への拡張
Answer: ステップ 4:本番環境への拡張は本ガイドの TwexAPI エンドポイントを Bearer で呼び出して実装します。バッチ/ページングで約 10 Credits/回・20+ QPS です。
本番環境では、収集、スコアリング、集計、アラートを復旧可能なジョブに分けます。複数のクエリ worker で収集層を広げられますが、書き込み経路は厳密に保ちます。先に重複排除し、次にスコアリングし、モデル出力を生成元クエリに紐づけます。
最低限、次の点を扱います。
- クエリ設定化:クエリ、言語、除外条件、更新間隔、最大ページ数を設定表に入れる。
- カーソルと重複排除を分ける:
next_cursorはページング用、tweet_idは重複排除キー。 - 空ウィンドウ保護:新規投稿がない場合は偽の平均値を計算せず、サンプル数 0 とする。
- モデルバージョン管理:モデルやしきい値が変わった後のスコアを旧スコアと同じものとして比較しない。
- 感情の減衰:活発なテーマは定期更新し、古い感情がダッシュボードを支配しないようにする。
- 人間レビューキュー:ネガティブスコアが高い投稿、高エンゲージメント投稿、重要アカウントの投稿はレビューへ回す。
保存すべきフィールド
Answer: 保存すべきフィールドとは、この事例で api.twexapi.io の TwexAPI Bearer API を使う手順です。通常の読み取りは 10 Credits(約 $0.10/1K)、対象プランは 20+ QPS です。2026-08-20 時点の公式 Post/User read は $5/$10 per 1K で、rate limit は endpoint ごとに異なります。
各スコアを元投稿、元クエリ、モデルバージョンへ戻せるように、保存するフィールドを先に決めておきます。
| フィールド | 用途 |
|---|---|
tweet_id | 重複排除と追跡 |
query と sortBy | 投稿がデータセットに入った理由 |
fetched_at と created_at_datetime | 取得時刻と投稿時刻を分ける |
lang | 適切なモデルへ分流する |
text と raw JSON | 解析やスコアリング変更後に再処理できる |
sentiment_score と sentiment_label | モデル出力 |
model_version | スコア変化を監査可能にする |
| エンゲージメント数 | レビューすべき外れ値の優先順位付け |
review_status | 高リスクサンプルを人が確認したかを示す |
アラートルール
Answer: アラートルールとは、この事例で api.twexapi.io の TwexAPI Bearer API を使う手順です。通常の読み取りは 10 Credits(約 $0.10/1K)、対象プランは 20+ QPS です。2026-08-20 時点の公式 Post/User read は $5/$10 per 1K で、rate limit は endpoint ごとに異なります。
アラートルールは、感情スコアをチームが動ける信号に変えるためのものです。ネガティブな変動があるたびに通知するのではなく、スコアの変化、サンプル数、エンゲージメント重み、クエリの重要度を一緒に見ます。
最初は次の 3 種類から始めると扱いやすくなります。
- ネガティブ比率の急上昇:最新ウィンドウのネガティブ比率が前回より上がり、最低サンプル数も満たしている。
- 高エンゲージメントのネガティブ投稿:1 件のネガティブ投稿が、同じ時間窓の中央値より明らかに多い返信、リポスト、閲覧を集めている。
- 重要クエリの命中:ブランド名、障害、ポリシー、価格、安全性、規制関連の語が新しいネガティブサンプルに出ている。
各アラートには、クエリ、時間窓、サンプル数、平均スコア、ネガティブ比率、代表的な tweet_id、model_version を含めます。受け手がすぐにエスカレーション要否を判断できるようにするためです。
まとめ
感情分析は追跡可能なデータパイプラインとして作ります。/twitter/advanced_search/page を sortBy: "Latest" でポーリングし、next_cursor でページングし、tweet_id で重複排除し、新規投稿だけをスコアリングし、時間窓で集計し、すべての数値の裏にある原始証拠を保存します。