Featured image of post X APIを使ってエゴサBotを作ろうとしたら失敗した話

X APIを使ってエゴサBotを作ろうとしたら失敗した話

2025年5月の話です。規約が変われば話は変わってくるかもしれません。

無課金を前提として話を進めています。

X(旧 Twitter)のAPIを使って特定のワードを含むツイートを収集するBotを作ろうとしたら失敗した話をまとめます

動機

アルバイト先で製品に対するフィードバックの収集を目的として、自社製品に関するワードを含むツイートを収集したい、という動機がありました。

そこで、スケジューラーとX APIを組み合わせて、1日に1回、特定の単語を含むツイートを収集するBotを作ることになりました。

お試しなので、無料プランを使うことにしました。というか有料プラン全部高くない…?

開発の流れ

おおむね、以下の手順で開発を進めました。だいたい1日くらいで完了。

  1. Xのアカウントを開設する
  2. X 開発者プラットフォームに登録する
  3. TokenとかKeyを発行する
  4. Botのプログラムを作る

本来であれば、この作業に加えてBotのサーバを立てたり、スケジューラの設定があるのですが、Botのプログラムの時点で詰んだので割愛します。

Xのアカウントを作る

何も悩むことはないです。割愛します。

開発者プラットフォームに登録する。

これも何も悩むことはないです。サイトに言われた通りに登録してください。

APIの使用用途を、英語250字以上で書く必要があり、そこだけ面倒でした。

chatGPTに適当に書かせましょう。

Tokenを発行してもらう

「X API Token 発行」とでも調べればいっぱい記事が転がっているので参考にしてください。

ここでは割愛します。

Botのプログラムを作る

BotのプログラムはPythonで構築しました。X APIはv2を使用しています。

プログラムが実行されたタイミングの1時間前から24時間分のツイートを検索して取得するプログラムを作成しました。

以下にコードを載せます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import requests
import datetime
import pytz
import json
import time

# --- 設定 ---
BEARER_TOKEN = "YOUR_BEARER_TOKEN_HERE" # 取得したBearer Tokenをここに貼り付けます
SEARCH_QUERY = "" # 検索したい単語またはフレーズ

# タイムゾーンの設定
JST = pytz.timezone('Asia/Tokyo')
UTC = pytz.utc

# --- 検索期間の設定 ---
# 現在時刻を取得し、UTCに変換
now_utc = datetime.datetime.now(UTC)

# end_time を現在時刻より1時間前に設定
adjusted_end_time_utc = now_utc - datetime.timedelta(hours=1)

# start_time は end_time の24時間前を基準にする
yesterday_utc = adjusted_end_time_utc - datetime.timedelta(days=1)


# APIクエリ用のISO 8601形式にフォーマット (RFC3339準拠)
start_time = yesterday_utc.strftime('%Y-%m-%dT%H:%M:%SZ')
end_time = adjusted_end_time_utc.strftime('%Y-%m-%dT%H:%M:%SZ') # 修正箇所

# --- APIエンドポイントとヘッダー ---
SEARCH_URL = "https://api.twitter.com/2/tweets/search/recent"
HEADERS = {
    "Authorization": f"Bearer {BEARER_TOKEN}"
}

# --- パラメータの設定 ---
PARAMS = {
    "query": SEARCH_QUERY,
    "start_time": start_time,
    "end_time": end_time,
    "tweet.fields": "created_at,author_id,public_metrics,lang",
    "expansions": "author_id",
    "user.fields": "name,username",
    "max_results": 100
}

# --- APIリクエストの実行 ---
def get_tweets():
    print(f"Searching for '{SEARCH_QUERY}' from {start_time} to {end_time}")
    response = requests.get(SEARCH_URL, headers=HEADERS, params=PARAMS)

    if response.status_code == 429:
        print("Rate limit exceeded. Waiting for 15 minutes...")
        time.sleep(15 * 60 + 10) # 15分と10秒待機
        # 待機後に再度リクエストを試みる場合は、この関数を再帰的に呼び出すか、ループで囲む
        return []
    elif response.status_code != 200:
        print(f"Error: {response.status_code}")
        print(response.json())
        return []

    data = response.json()
    # print(json.dumps(data, indent=2, ensure_ascii=False)) # デバッグ用に全レスポンスを表示

    if 'data' not in data:
        print("No tweets found for the specified query and time range, or 'data' key is missing.")
        return []

    tweets = data['data']
    users = {user['id']: user for user in data.get('includes', {}).get('users', [])}

    results = []
    for tweet in tweets:
        author = users.get(tweet['author_id'], {})
        results.append({
            "id": tweet['id'],
            "text": tweet['text'],
            "created_at": tweet['created_at'],
            "author_username": author.get('username'),
            "author_name": author.get('name'),
            "retweet_count": tweet['public_metrics'].get('retweet_count', 0),
            "like_count": tweet['public_metrics'].get('like_count', 0),
            "lang": tweet.get('lang')
        })
    return results

if __name__ == "__main__":
    retrieved_tweets = get_tweets()
    print(f"\n--- 取得したツイートの例 ({len(retrieved_tweets)}件) ---")
    for i, tweet in enumerate(retrieved_tweets[:5]):
        print(f"--- Tweet {i+1} ---")
        print(f"ユーザー: @{tweet['author_username']} ({tweet['author_name']})")
        print(f"日時: {tweet['created_at']}")
        print(f"本文: {tweet['text']}")
        print(f"RT/いいね: {tweet['retweet_count']}/{tweet['like_count']}")
        print("-" * 20)

パラメータの解説

BEARER_TOKEN: 開発者プラットフォームで発行されたBearer Tokenを貼り付けてください。

SEARCH_QUERY: 検索したいワードを代入してください。

注意点

収集の開始時刻を設定する場合は、プログラムが実行される時間の20〜30秒前を指定しないとエラーになるので注意が必要です。

このコードは、取得したツイートのうち、新しい5件だけコンソールに表示するようになっていいます。 すべて確認したい場合や、ファイルに出力したい場合は適宜コードを追加してください。

いざ実行!だが…

コードが完成したので、とりあえず「apple」という単語で検索するように実行すると、

お、取得できている!

ということで、今度は自社製品の名前で検索するように実行してみると

1
2
Error: 429
{'title': 'Too Many Requests', 'detail': 'Too Many Requests', 'type': 'about:blank', 'status': 429}

ん?too many requests…?

どうやら、無料プランだと15分に1回しかAPIを投げられないらしい。

開発中のプロジェクトにとっては、かなり辛い。

まぁ、お試しなので気長に待つこと15分。再度実行してみると…

検索結果

んん?検索結果が0件??

あらかじめXのアプリで、24時間以内に複数のツイートを確認しているので、数件はヒットするはずなのに…

コードにミスがあるのかなと思い、ツイートの言語を日本語に指定してみたり、数日分のツイートを取得するようにとパラメータの調整を行なったのですが、 どれを試してみても検索結果は0件のままでした。

そこで、geminiくんにコトの詳細を説明して原因を教えてもらうと、以下の回答が帰ってきました。

geminiの回答

geminiくんの回答をまとめると、

  • そもそもFreeプランは、単語の検索に関するAPIは使えない
  • 何らかの理由で、頻繁にツイートされているだけは、検索ができる

といった感じです。

つまり、無料枠で検索Botを作ろうとしても、検索したいワードによっては使えないということがわかりました。

自分の検索したいワードは必ずしも有名とは言えないので、無料枠のAPIには引っかからなかったのだろうという推測が立ちます。

かといって、課金してまで検証するモチベもお金もなかった(特にお金)ので、 エゴサBotの開発はここで断念しました。

どなたか有料版と無料版で比較してほしいです。

まとめ

2025年5月現在自分が調べた感じだと、X APIの無料枠は

  • 15分に1回しかリクエストを投げることができない

  • 単語の検索を行うAPIは基本的には利用できない

  • 単語によっては、検索することができる

というような制約で動いているっぽいです。

公式のドキュメントをみてもこの辺が明記されていないので、かなり曖昧な情報ですが…

もしX APIを使ってエゴサBotを考えている方がいれば参考になればと思います。

あと誰か検証してほしい。

おわり

Hugo で構築されています。
テーマ StackJimmy によって設計されています。