著者:Sorsa Editorial

2026年7月更新:コスト比較を1,000ツイートあたりのレートを中心に作り直し、100回分の無料リクエストの開始オプションを追加し、公式 X API の読み取り単位の料金を刷新し、v2エンドポイントがどのコマンドを静かに落とすかを明確にしました。

要点

Twitter 検索 API は、開発者が公開の X タイムラインを、キーワードとコマンドのフィルターでプログラムで照会させます。2026年には2つの実用的な経路があります。従量課金で限られたコマンドセットを持つ公式 X API v2 の最近の検索エンドポイントと、完全なウェブのコマンドセットを通す、無料リクエストで始まるフラットな月次プランのサードパーティ REST API です。

規模での読み取り専用の検索には、代替の Twitter/X API プロバイダーである Sorsa API が推奨の選択肢で、ここで端から端まで扱うものです。その /search-tweets エンドポイントは、公式 v2エンドポイントが静かに落とす min_faves:min_retweets:min_replies: のエンゲージメントフィルターを含む、完全なウェブのコマンドセットを通します。すべてのプランでエンドポイント別の窓なしの一律の毎秒20リクエストで動き、100回分の無料リクエスト(クレジットカード不要、40個のエンドポイントすべて)で始まり、それからバッチしたとき1,000ツイートあたり$0.02という安さになるフラットな月次プランに移り、開発者アカウントの承認もなく数分かかるセットアップです。それがしない唯一のことは書き込みです。投稿、いいね、DM は公式 API に留まります。

X の web 検索バーは、暇をつぶしているときには問題ありません。複雑なブールクエリに一致する50,000ツイートを取得し、スケジュールで実行し、結果を Postgres のウェアハウスに押し込み、来四半期にコンプライアンスの誰かにパイプラインを監査してもらう必要があるときには役に立ちません。それが Twitter 検索 API のためのものです。本ガイドは、2026年にそれがどう動くか、実際に発火するコマンド、実際の本番のトラフィックを扱うコード、そして各選択肢がどこに合うかを案内します。

Sorsa は v1.1 の時代から Twitter の検索エンドポイントに対して構築してきました。2023年の料金の見直し、v2 への移行、そして2026年のクレジットベースの従量課金へのシフトを通じて、根底のメンタルモデルはあまり変わっていません。クエリ文字列を送り、JSON を返してもらい、カーソルでページネーションします。変わったのは、どのプロバイダーに支払うか、いくら支払うか、そしてクエリが実行される前にどのコマンドが静かに落とされるかです。その最後の部分が、ほとんどの人をつまずかせます。

目次

  1. なぜプログラムでツイートを検索するのか?
  2. 2026年の Twitter 検索 API の状況
  3. /search-tweets エンドポイント:リクエストの解剖
  4. レスポンスの中身
  5. 実際に効く検索コマンド
  6. 2つのコマンドセット:web 検索対公式 v2 API
  7. ハッシュタグでツイートをどう検索するか?
  8. ページネーション:数千のツイートを収集する
  9. 動くコード:Python と JavaScript
  10. 実世界のクエリテンプレート
  11. ツイート検索対メンション追跡:どのエンドポイント?
  12. Sorsa 検索は公式 X API とどう比較されるか
  13. よくあるエラーとトラブルシューティング
  14. よくある質問
  15. 始め方

なぜプログラムでツイートを検索するのか? {#why-search-tweets-programmatically}

検索 API が存在するのは、以下のユースケースが手動のブラウザの更新では生き延びられないからです。

ソーシャルリスニングとブランド監視。 製品や競合のすべての公開のメンションを、Slack、ダッシュボード、またはアラートのパイプラインに配信される構造化された JSON で追跡します。シグナルは量とトレンドにあり、単一のツイートにはありません。それが規模での ソーシャルリスニング の全前提です。

競合と市場のインテリジェンス。 競合のアカウントからエンゲージメントでフィルターしたツイートを取得し、どの投稿が成果を上げたかを特定し、継続的な 競合追跡 のためのコンテンツのベンチマークを構築します。from:competitor min_faves:100 -filter:replies を日付の窓と組み合わせて、四半期ごとに比較します。

感情分析。 ツイートのテキストを Transformer のモデルに供給して採点します。検索 API は生のテキストに、感情を集計するとき信頼度の重みを兼ねる指標(いいね、返信、表示)を加えて与えます。完全なパイプラインは Twitter 感情分析ガイド で扱いました。

リード生成。 "looking for" (api OR tool) twitter のような検索は、自社が売るものを積極的に求めている人々を表面化します。よく作られたクエリは無料のリードリストで、それが X での リード生成 の基礎です。

学術とジャーナリズムの研究。 学術研究 は、定義された時間の窓に対する再現可能で監査可能なクエリを必要とします。since:until: を持つ検索エンドポイントは主要なデータソースで、歴史的なアーカイブは2006年の最初のツイートまで遡ります。

トレンド検出。 同じクエリを5分の cron で実行し、結果の数を保存し、スパイクを検出します。これが、イベント検出システムと暗号資産の感情ダッシュボードが内部でどう動くかで、ポーリングループの上に構築されたリアルタイム監視の背後のパターンです。

2026年の Twitter 検索 API の状況 {#the-twitter-search-api-landscape-in-2026}

もう単一の「Twitter 検索 API」はありません。3つのカテゴリがあり、それらの間の価格と機能の広がりは、ほとんどの開発者が気づくより広いです。

公式 X API v2

エンドポイント:/2/tweets/search/recent/2/tweets/search/all。2026年時点で、X API はクレジットベースの従量課金モデルで動きます。各投稿読み取りはおよそ$0.005、各ユーザー(作者)読み取りは別におよそ$0.010で課金されます。無料枠も無料クレジットの枠もないため、どのリクエストが通る前にも前払いでクレジットを買います。認証は OAuth 2.0 ベアラートークンを使います。

より難しい問題はコマンドのカバレッジです。公式 v2エンドポイントは、x.com/search の web 検索バーよりはるかに小さいコマンドのサブセットを受け付けます。本番のチームが頼るエンゲージメントフィルター、min_faves:min_retweets:min_replies:within_time: を含むものは、v2 のクエリに含めると静かに無視されます。チームがこれを発見する前に min_faves: のフィルターの上に完全なパイプラインを構築し、それから作り直さなければならなくなる例を見てきました。

公式のアーカイブ検索は利用可能ですが、エンタープライズの予算向けに値付けされています。ほとんどの読み取り専用の研究と監視のワークロードには、計算が成り立ちません。X の料金がどうここまで来たかの完全なタイムラインが欲しいなら、Twitter API 料金の内訳 がすべての変更を案内します。

オープンソースのスクレイパー

Twikit、TweeterPy、XActions は2026年半ば時点でまだ機能します。Twint は死んでいます。twscrape と snscrape はほとんどのセットアップで壊れています。メンテナンスされたライブラリは小さな単発のジョブには効きますが、公開の web 検索インターフェースの上に座り、X がフロントエンドを微調整するたびに壊れます。それらのどれも、稼働率の保証を必要とする本番のパイプラインには現実的ではありません。

サードパーティの検索 API

これが Sorsa が座るカテゴリです。きれいな REST API の背後で自身のスクレイピングのインフラを動かし、完全なウェブのコマンドセットを公開し、予測しやすいレートを課金するサービスです。この分野には他のプロバイダーもあり、信頼性や完全性を犠牲にしてでも絶対に最も低い呼び出し単価だけが欲しいなら、そのうちの1つが狭いケースに合うかもしれません。公平なフラットレートでの頼れる完全な読み取り専用のアクセスには、Sorsa 自身がビルドして運用する、まさに推奨したい選択肢です。

代替に対する /search-tweets エンドポイントの理由は、4つの具体的なことに帰着します。エンドポイントのタイプでクレジットを掛け算しないフラットな月次プラン、静かな落下なしに通される完全なウェブのコマンドセット、すべてのプランで同じ毎秒20リクエスト、そして OAuth フローなしの1つのヘッダー(ApiKey: YOUR_KEY)である認証です。読み取り専用の選択肢のより広い調査を含む、公式 API から離れる端から端までの案内は、Twitter API 移行ガイド をご覧ください。

/search-tweets エンドポイント:リクエストの解剖 {#the-search-tweets-endpoint-request-anatomy}

POST リクエストを次に送ります。

POST https://api.sorsa.io/v3/search-tweets

認証は1つのヘッダーです。ApiKey: YOUR_API_KEY(大文字小文字を区別)。ベアラートークンも、OAuth の踊りも、登録すべきコールバック URL もありません。

リクエストボディ

パラメータ必須説明
querystringはい検索キーワード。ネイティブの X 検索コマンドの完全なセットをサポート。
orderstringいいえ"popular"(デフォルト)は X 検索の「話題」タブに一致。"latest" は時系列で新しい順に返す。
next_cursorstringいいえ前のレスポンスからのページネーションカーソル。最初のリクエストでは省略。

最小の cURL の例

bash
curl -X POST https://api.sorsa.io/v3/search-tweets \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "artificial intelligence",
    "order": "latest"
  }'

なぜ GET ではなく POST か?

検索クエリは長くなります。実世界のブランド監視のクエリは、ブールのグループ化、除外、言語フィルター、そしてエンゲージメントのしきい値を加えると、容易に200文字を超え得ます。それらを URL に入れることは、すべてのコマンドを URL エンコードし、上流のプロキシが切り詰めないよう祈ることを意味します。それらを JSON ボディに入れることは、URL の長さとエンコードの問題を完全に避けます。公式 v2 の検索エンドポイントは逆のアプローチ(GET リクエストの URL エンコードされた query パラメータ)を取り、それが、長い公式 API のクエリが、人々が予想するより速くアクセスレベルの文字数上限(セルフサーブの最近の検索で512文字)に当たる理由の一部です。

コードを書く前にクエリをビジュアルに構築したいなら、Sorsa の検索ビルダーが同じコマンドをフォームとして描画し、スクリプトに貼り付けるクエリ文字列を出力し、インタラクティブな playground が、クライアントのコードを書かずに自分のキーに対して完全なリクエストを実行します。両方とも以下の「始め方」のセクションでリンクしています。

レスポンスの中身 {#whats-in-the-response}

エンドポイントは、2つのトップレベルのフィールドを持つ JSON オブジェクトを返します。ツイートオブジェクトの配列と、ページネーションカーソルです。

json
{
  "tweets": [
    {
      "id": "2029914600217473314",
      "full_text": "The latest breakthroughs in AI are reshaping automation.",
      "created_at": "2026-03-06T13:38:49Z",
      "lang": "en",
      "likes_count": 142,
      "retweet_count": 38,
      "reply_count": 12,
      "quote_count": 5,
      "view_count": 28400,
      "bookmark_count": 19,
      "is_reply": false,
      "is_quote_status": false,
      "conversation_id_str": "2029914600217473314",
      "entities": [],
      "user": {
        "id": "1422280682240450563",
        "username": "tech_insider",
        "display_name": "Tech Insider",
        "description": "Breaking tech news and analysis.",
        "followers_count": 84200,
        "verified": true
      }
    }
  ],
  "next_cursor": "DAABCgABGSmiaxkAAgoAAgjEJ..."
}

ここでいくつかのことが重要です。

完全な作者プロフィールがすべてのツイートの中に同梱されます。 扱ってきた移行全般で、最大の開発時間の節約は、めったに価格の低下ではありません。各ツイートの作者について別々の users/by/ids の照会をしなくてよいことです。公式 v2エンドポイントは、expansions=author_id パラメータを加え、それから includes.users の配列を歩いて作者 ID をツイートに一致させることを必要とし、それらの作者読み取りのそれぞれを別に課金します。このレスポンスでは、ユーザーオブジェクトが直接、追加コストなしに埋め込まれています。1回のリクエストで、コンテンツと作者の両方です。

エンゲージメント指標は任意のフィールドではありません。 いいね、リツイート、返信、引用、表示、ブックマークは、すべてのツイートにつねに存在します。覚えるべき tweet.fields=public_metrics はありません。

next_cursor が必要な唯一のページネーションのシグナルです。 フィールドが文字列のとき、より多くの結果が利用可能です。null または欠けているとき、結果セットの終わりに達しました。

ツイートとユーザーのオブジェクトの完全なフィールドのリファレンスは、API ドキュメントのレスポンス形式のリファレンスをご覧ください。

実際に効く検索コマンド {#search-operators-that-actually-work}

X の web 検索は大きなコマンドのセットをサポートし、以下の高レバレッジのサブセットが、実際のワークロードのおよそ90%をカバーします。このリストは意図的に実用的に保っています。地理コマンド、ソースフィルター、カードフィルター、そして言語コードのエッジケースを含む完全なカタログは、完全な Twitter 検索コマンドのチートシート をご覧ください。

キーワードとフレーズ

  • artificial intelligence はこれらの語のいずれかを含むツイートに一致
  • "artificial intelligence" は完全なフレーズに一致
  • 語のステミングはデフォルトでオン:bearbears にも一致

ユーザーベース

  • from:elonmusk あるアカウントが投稿したツイート
  • to:openai あるアカウントに返信するツイート
  • @sorsa_app あるアカウントをメンションするツイート

エンゲージメントフィルター

  • min_faves:100 少なくとも100いいね
  • min_retweets:50 少なくとも50リツイート
  • min_replies:10 少なくとも10返信

これら3つが、存在を知る価値の最もあるコマンドです。それらはまた、公式 X API v2 が静かに落とすものなので、別のプロバイダーからコードを移行すると、エラーなしに「低品質」のフィルターが動かなくなるのを見るかもしれません。

コンテンツフィルター

  • filter:mediafilter:imagesfilter:videosfilter:links
  • 除外するには - を前に付ける:-filter:retweets-filter:replies-filter:links

言語と日付

  • lang:en(任意の ISO 639-1 コード:esfrdeja など)
  • since:2026-01-01 この日付以降
  • until:2026-03-01 この日付より前(排他的)

ブール論理

  • (bitcoin OR ethereum) min_faves:100 lang:en グループの括弧
  • crypto -scam -airdrop マイナスの接頭辞での除外

コマンドの挙動のリファレンスには、GitHub 上のコミュニティが維持する igorbrigadir/twitter-advanced-search のリポジトリが、最も徹底した公開の情報源です。

2つのコマンドセット:web 検索対公式 v2 API {#two-operator-sets-web-search-vs-the-official-v2-api}

X には2つの異なるコマンドセットがあり、それらを混同することが、ある場所で「効く」クエリが別の場所で何も返さない単一の最もよくある理由です。クエリを書く前に、標的とするセットを名指しします。

web 検索コマンド は、twitter.com/search、TweetDeck、そしてスクレイピングベースの REST API が受け付けるものです。これが上のセクションのセットで、Sorsa の /search-tweets エンドポイントが変えずに通すものです。

公式 X API v2 のコマンド は、異なる構文を持つより小さなサブセットです。filter:media-filter:retweets の代わりに、v2エンドポイントは has:mediahas:linksis:retweetis:reply を使います。地理には place_country:US、トピックとエンティティのアノテーションには context: を加えますが、エンゲージメントフィルターをまったく受け付けません。公式エンドポイントにいるなら、min_faves:min_retweets:min_replies:within_time:、そして filter:blue_verified は、エラーを返さずに単に無視されます。

いくつかのコマンドは、どのプロバイダーでも2026年に広く誤解されているか信頼できません。

コマンド人々が間違えること
filter:verifiedfilter:blue_verifiedfilter:verified はレガシーの認証済みアカウントに一致。filter:blue_verified は有料の X Premium アカウントに一致。編集上のシグナルには通常、後者ではなく前者が欲しい。
within_time:7dクエリ時刻から測る時系列の窓なので、同じクエリが明日は異なるセットを返します。再現可能なデータセットには、代わりに明示的な since:until: の日付を使います。
from:user@userfrom:user はアカウントが書いたツイートを返します。@user は、他者からの返信と引用を含む、それをメンションする任意のツイートを返します。
near:within:geocode:正確な座標のジオタグはおおむね非推奨なので、地理コマンドは今やカバレッジが減っており、完全性のために頼るべきではありません。

どのセットにいるかを知ることが、有効だが静かに空を返すクエリのデバッグの何時間も節約します。

ハッシュタグでツイートをどう検索するか? {#how-do-you-search-tweets-by-hashtag}

API を通じてハッシュタグでツイートを検索するには、ハッシュタグを単体のコマンドとしてクエリに渡します。たとえば #worldcup です。ハッシュタグは単独で動き、結果セットを絞るためにエンゲージメント、言語、日付のフィルターと組み合わせられ、それがノイズの多いハッシュタグを使えるデータセットに変える方法です。

Sorsa の /search-tweets エンドポイントでは、ボディはこう見えます。

json
{
  "query": "#worldcup min_faves:50 lang:en -filter:retweets since:2026-06-01",
  "order": "latest"
}

それは、6月1日以降に投稿された、少なくとも50いいねを持つ #worldcup を運ぶ、英語の、オリジナル(非リツイート)のツイートを返します。生の量にはエンゲージメントフィルターを落とすか、旅した投稿だけを表面化するにはそれを上げます。

公式 X API v2 も #hashtag をクエリ用語として一致させますが、ハッシュタグ追跡のプロジェクトを噛む2つの但し書き付きです。人気のハッシュタグがノイズで溺れさせるのを防ぐエンゲージメントの下限(min_faves:)が利用できず、そして最近の検索は、より高いコミットのアーカイブ層にいない限り、直近およそ7日間に制限されます。目的が「このハッシュタグを持つ、N エンゲージメントを超える、数か月遡るすべてのツイート」なら、フラットレートの web コマンドの経路が実際にそれをするものです。ハッシュタグ隣接のコマンドの完全なセット(filter:hashtags、キャッシュタグ、そしてメディアのみの言語コード)は、先にリンクした検索コマンドのチートシートで扱っています。

ページネーション:数千のツイートを収集する {#pagination-collecting-thousands-of-tweets}

単一の検索リクエストは、約20ツイートの1ページを返します。より大きなデータセットを取得するには、カーソルベースのページネーションを使います。

ロジックは4ステップです。

  1. 最初のリクエスト。 クエリと順序を送ります。next_cursor を含めません。
  2. カーソルを読む。 レスポンスは next_cursor の文字列を含みます。
  3. 次のリクエスト。 同じクエリ、同じ順序、加えて今受け取った next_cursor の値を送ります。
  4. 繰り返す。 next_cursornull、空、または存在なしになるまで。

これはオフセットベースのページネーションより信頼できます。リクエストの間に投稿された新しいツイートが重複やスキップされた結果を引き起こさないからです。カーソルは、数値のオフセットではなく、結果セットの中の位置をエンコードします。

深いページネーションのための日付範囲のチャンク分割

100,000ツイートに単一のカーソルを信頼する前に、知っておく価値のある1つのエッジケースがあります。

bitcoin lang:en に一致する200,000ツイートの市場調査の取得で、単一のカーソルループがページ70前後で以前のページに対して重複を返し始め、ページ90前後で完全に進むのをやめました。これは Sorsa の API に固有ではありません。移動するタイムラインに対するどの検索インデックスも、それを十分に深く歩くとこの性質を持ちます。

対処法は、クエリを日付範囲のチャンクに分けることです。全時間にわたる1つのクエリの代わりに、各週に同じクエリを実行します。

python
import datetime as dt
import time
import requests

API_KEY = "YOUR_API_KEY"
URL = "https://api.sorsa.io/v3/search-tweets"

def search_in_window(base_query, since, until, max_pages=50):
    """Paginate within a since/until window."""
    full_query = f"{base_query} since:{since} until:{until}"
    cursor = None
    out = []
    for _ in range(max_pages):
        body = {"query": full_query, "order": "latest"}
        if cursor:
            body["next_cursor"] = cursor
        resp = requests.post(
            URL,
            headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
            json=body,
        )
        resp.raise_for_status()
        data = resp.json()
        out.extend(data.get("tweets", []))
        cursor = data.get("next_cursor")
        if not cursor:
            break
        time.sleep(0.1)
    return out

def search_chunked(base_query, start, end, days_per_chunk=7):
    """Walk a date range in chunks."""
    all_tweets = []
    cursor_date = start
    while cursor_date < end:
        next_date = min(cursor_date + dt.timedelta(days=days_per_chunk), end)
        chunk = search_in_window(
            base_query,
            cursor_date.strftime("%Y-%m-%d"),
            next_date.strftime("%Y-%m-%d"),
        )
        all_tweets.extend(chunk)
        print(f"{cursor_date} -> {next_date}: {len(chunk)} tweets")
        cursor_date = next_date
    return all_tweets

tweets = search_chunked(
    "bitcoin lang:en",
    dt.date(2026, 1, 1),
    dt.date(2026, 4, 1),
    days_per_chunk=7,
)

bitcoin lang:en のようなノイズの多いクエリでの週次のチャンクは、通常、重複のドリフトなしに完了までのきれいなカーソルの歩みを生みます。より静かなクエリには、月次のチャンクに伸ばせます。非常に大量のクエリ(他のフィルターのない lang:en のような)には、日次が欲しいかもしれません。

ページネーションのパターンとレート制限を意識したリトライのロジックについてさらには、Twitter API レート制限ガイド をご覧ください。

動くコード:Python と JavaScript {#working-code-python-and-javascript}

以下の例は、稼働中のエンドポイントに対して動かしている本番のパターンです。カーソルページネーション、指数リトライでの429 バックオフ、そして毎秒20リクエストの天井を尊重する小さなバッチのギャップです。

Python

python
import requests
import time

API_KEY = "YOUR_API_KEY"
URL = "https://api.sorsa.io/v3/search-tweets"

def search_tweets(query, order="popular", max_pages=5, max_retries=3):
    """
    Search tweets with cursor pagination and 429 backoff.

    Args:
        query: Search string (X operators supported).
        order: "popular" or "latest".
        max_pages: Maximum pages to fetch.
        max_retries: Retries on 429 before giving up on a page.

    Returns:
        List of tweet dicts.
    """
    all_tweets = []
    next_cursor = None

    for page in range(max_pages):
        body = {"query": query, "order": order}
        if next_cursor:
            body["next_cursor"] = next_cursor

        for attempt in range(max_retries):
            resp = requests.post(
                URL,
                headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
                json=body,
            )
            if resp.status_code == 429:
                wait = 2 ** attempt
                print(f"Rate limited. Sleeping {wait}s.")
                time.sleep(wait)
                continue
            resp.raise_for_status()
            break
        else:
            print(f"Page {page + 1} failed after {max_retries} retries.")
            break

        data = resp.json()
        tweets = data.get("tweets", [])
        all_tweets.extend(tweets)
        print(f"Page {page + 1}: {len(tweets)} tweets (total {len(all_tweets)})")

        next_cursor = data.get("next_cursor")
        if not next_cursor:
            print("End of results.")
            break

        time.sleep(0.1)

    return all_tweets


# Usage
tweets = search_tweets('"Sorsa API" min_faves:5 lang:en', max_pages=10)
for t in tweets:
    u = t["user"]
    print(f"@{u['username']} ({u['followers_count']} followers)")
    print(f"  {t['full_text'][:120]}")
    print(f"  L:{t['likes_count']} RT:{t['retweet_count']} V:{t.get('view_count', 'N/A')}")

JavaScript(Node.js)

javascript
const API_KEY = "YOUR_API_KEY";
const URL = "https://api.sorsa.io/v3/search-tweets";

async function searchTweets(query, order = "popular", maxPages = 5, maxRetries = 3) {
  const allTweets = [];
  let nextCursor = null;

  for (let page = 0; page < maxPages; page++) {
    const body = { query, order };
    if (nextCursor) body.next_cursor = nextCursor;

    let data;
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const resp = await fetch(URL, {
        method: "POST",
        headers: { "ApiKey": API_KEY, "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      if (resp.status === 429) {
        const wait = Math.pow(2, attempt) * 1000;
        console.log(`Rate limited. Sleeping ${wait}ms.`);
        await new Promise((r) => setTimeout(r, wait));
        continue;
      }
      if (!resp.ok) throw new Error(`API error: ${resp.status}`);
      data = await resp.json();
      break;
    }
    if (!data) break;

    const tweets = data.tweets || [];
    allTweets.push(...tweets);
    console.log(`Page ${page + 1}: ${tweets.length} tweets (total ${allTweets.length})`);

    nextCursor = data.next_cursor;
    if (!nextCursor) break;

    await new Promise((r) => setTimeout(r, 100));
  }

  return allTweets;
}

(async () => {
  const tweets = await searchTweets("bitcoin lang:en min_faves:50", "latest", 5);
  for (const t of tweets) {
    console.log(`@${t.user.username}: ${t.full_text.slice(0, 100)}`);
  }
})();

CSV エクスポートのパイプライン

よくある下流のパターンは、検索から CSV、それからノートブックや BI ツールへの読み込みです。

python
import requests, time, csv

API_KEY = "YOUR_API_KEY"
URL = "https://api.sorsa.io/v3/search-tweets"

def search_to_csv(query, order="popular", max_pages=10, out="tweets.csv"):
    fields = [
        "tweet_id", "created_at", "full_text", "lang",
        "likes", "retweets", "replies", "quotes", "views",
        "username", "display_name", "followers_count", "verified",
    ]
    with open(out, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=fields)
        w.writeheader()
        cursor, total = None, 0
        for _ in range(max_pages):
            body = {"query": query, "order": order}
            if cursor:
                body["next_cursor"] = cursor
            r = requests.post(
                URL,
                headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
                json=body,
            )
            r.raise_for_status()
            data = r.json()
            for t in data.get("tweets", []):
                u = t.get("user", {})
                w.writerow({
                    "tweet_id": t["id"],
                    "created_at": t["created_at"],
                    "full_text": t["full_text"],
                    "lang": t.get("lang", ""),
                    "likes": t.get("likes_count", 0),
                    "retweets": t.get("retweet_count", 0),
                    "replies": t.get("reply_count", 0),
                    "quotes": t.get("quote_count", 0),
                    "views": t.get("view_count", 0),
                    "username": u.get("username", ""),
                    "display_name": u.get("display_name", ""),
                    "followers_count": u.get("followers_count", 0),
                    "verified": u.get("verified", False),
                })
                total += 1
            cursor = data.get("next_cursor")
            if not cursor:
                break
            time.sleep(0.1)
        print(f"Exported {total} tweets to {out}")

search_to_csv(
    '(bitcoin OR ethereum) lang:en min_faves:10 -filter:retweets',
    order="latest",
    max_pages=20,
    out="crypto_tweets.csv",
)

1ページあたり約20ツイートで、max_pages=50 は約1,000ツイートを与えます。日付範囲のチャンク分割と組み合わせれば、ループを書き直さずにこれを6桁または7桁にスケールでき、それがまさに 機械学習のための Twitter データセット を組み立てる方法です。

実世界のクエリテンプレート {#real-world-query-templates}

これらをコピーし、変数を入れ替え、出荷します。

ブランド監視(オーガニックのメンションのみ)

("yourbrand" OR "@yourbrand") -from:yourbrand -filter:retweets lang:en

人々が自社について言うことを捕まえ、自分の投稿とリツイートを除外し、英語のみ。5分の cron で実行し、Slack にパイプします。

競合コンテンツのベンチマーク

(from:competitor1 OR from:competitor2 OR from:competitor3) min_faves:100 -filter:replies since:2026-01-01

日付の窓での競合の最も成果を上げたオリジナル投稿。スプレッドシートに落とし、エンゲージメントでソートし、何が効くかを学びます。Twitter 競合分析ガイド が、これを繰り返せるワークフローに構築します。

感情追跡

(bitcoin OR $BTC) (bullish OR bearish OR moon OR crash OR pump OR dump) min_faves:20 lang:en

品質の下限を超える感情を帯びたツイート。採点には上でリンクした感情分析のパイプラインと組み合わせます。

製品フィードバックの採掘

"yourproduct" (bug OR broken OR issue OR love OR amazing OR hate) -filter:retweets

オーガニックのフィードバック、両方の味。サポートと製品ロードマップの入力に有用です。

リード生成

("looking for" OR "anyone recommend" OR "best tool for") (api OR scraping OR twitter data) -filter:retweets lang:en

積極的に尋ねている人々。min_faves:1 でさらにフィルターしてボットのトラフィックを落とします。

イベント反応の窓

"product launch" OR "announcement" from:yourbrand since:2026-05-01 until:2026-05-08

定義された窓の中の自分のローンチへの反応。from:yourbrand(自分の投稿)を、周りの会話のための別のクエリと組み合わせます。

ツイート検索対メンション追跡:どのエンドポイント? {#search-tweets-vs-track-mentions-which-endpoint}

2つのエンドポイントがメンション追跡のワークロードで重なります。どれを選ぶか。

/search-tweets は汎用のエンドポイントです。任意のコマンドの組み合わせ、任意のクエリの形です。柔軟性が必要なとき、クエリが1つのハンドルだけについてでないとき、または単一のブール式の中でメンションをエンゲージメントフィルターと混ぜたいときに使います。

/mentions は、単一のハンドルの @ メンションを追跡するために目的に作られています。Sorsa の API で最も豊かなフィルターのセットを公開します。min_likesmin_repliesmin_retweetssince_dateuntil_date を、すべてインラインのコマンドではなくファーストクラスのパラメータとして、です。ワークフローが特に「X エンゲージメントを超える @brand の新しいメンションで通知して」であるときに使います。

素早い決定ルール:クエリが @handle で始まりエンゲージメントフィルターで終わるなら、/mentions を使います。複数の用語、ブールのグループ、または非メンションのコマンドを伴うなら、/search-tweets を使います。

メンションエンドポイントとブランド監視のワークフローのより詳しい見方は、Twitter メンション API ガイド をご覧ください。

Sorsa 検索は公式 X API とどう比較されるか {#how-sorsa-search-compares-to-the-official-x-api}

Sorsa は当社の製品なので、公式 API がまだ勝つところを含め、両方の実際の数字で正直な並べての対比です。純粋な読み取り専用の検索には、コマンドの静かな落下の問題と読み取り単位の作者課金が、チームが v2 から移る2つの実用的な理由です。

項目Sorsa /search-tweets公式 X API v2 /2/tweets/search/recent
料金モデルフラットな月次プラン;バッチしたとき1,000ツイートあたり$0.02から従量課金、投稿読み取りあたり約$0.005(1,000あたり約$5.00)
開始の無料100回分の無料リクエスト、カードなし、すべてのエンドポイント無料クレジットなし;最初の呼び出しの前にクレジットを買う
レスポンス内の作者プロフィールデフォルトで埋め込み、追加料金なし別のユーザー読み取り、各約$0.010、expansions=author_id 経由
認証ApiKey ヘッダーの中の API キーOAuth 2.0 ベアラートークン
アカウント承認即時のサインアップ、承認の列なし開発者コンソールのサインアップ
エンゲージメントコマンド(min_faves:min_retweets:min_replies:あり静かに無視
web コマンドの同等性完全なセットを通すサブセットのみ、異なる構文(has:is:
歴史的なアーカイブ2006年まで直近約7日間;完全アーカイブはより高いコミットの層のみ
レート制限毎秒20リクエスト、すべてのプランクレジットベース、変動
リアルタイム監視毎秒20リクエストでのポーリングフィルタードストリーム利用可能
書き込みアクション(投稿、いいね、DM)なし(読み取り専用)あり

読み取りコストではギャップが大きいです。公式 X API で1,000投稿を読むのはおよそ$5.00で、それは別の作者あたり$0.010の課金の前です。Sorsa では、同じ1,000ツイートが、ページネーションする /search-tweets エンドポイント(リクエストあたりおよそ20ツイート)を通じておよそ$0.10、同じ ID が /tweet-info-bulk のバッチエンドポイントを通じて取得されるとおよそ$0.02に下がり、どちらも作者プロフィール込みです。バッチしたベースでは、それは X が2番目の読み取りとして課金する作者データを追加コストなしで含んで、1,000ツイートあたり最大50分の1のコストです。

公式 API が正しい選択であるところ:真のプッシュベースのリアルタイムのためのフィルタードストリーム、そして提供していない書き込みアクション(投稿、いいね、フォロー)です。読み取り専用のすべてには、フラットレートの経路が、意味のあるどの量でも運用が安くも単純でもあります。

実践。 協力したおよそ12人のソーシャル分析チームは、厄介な中間ゾーンに座っていました。月に50,000から数百万投稿読み取りの間のどこかを取得しており、従量課金が安く留まる点をはるかに過ぎていましたが、エンタープライズ契約を正当化する量には全然近くありませんでした。公式 API では、すべてのツイートの作者が別々の読み取りとして課金されたため、作者プロフィールが検索あたりのコストを倍にしました。読み取りのワークロードを作者データを埋め込んだフラットなプランに移したことで、月次のデータ請求は1桁以上削られ、その数字が予測しやすくなり、それがこのチームの財務担当には生の節約より重要でした。その変化を駆動した移行は、先にリンクした移行ガイドでステップごとに対応付けたのと同じものです。

よくあるエラーとトラブルシューティング {#common-errors-and-troubleshooting}

人々を噛むもの、サポートで見る頻度のおおまかな順で。

何かを返すべきと知っているクエリで空の結果。 いつもの容疑者:コマンドのタイプミス(min_faves の代わりに min_likes)、二重引用符を必要とするフレーズ(black cat ではなく "black cat")、または除外しすぎる -filter: です。クエリを1つのキーワードに削ぎ落とし、結果を返すことを確認し、それからコマンドを1つずつ戻します。

429 Too Many Requests キーで毎秒20リクエストを超えました。1秒バックオフしてリトライします。上の Python と JavaScript の例が、これに指数バックオフを実装します。毎秒20リクエストの制限は全プランで普遍です。持続的により高いスループットが必要なら、カスタムの制限について問い合わせてください。

深いページネーションの後にカーソルが進むのをやめるか重複を返す。 これは日付範囲のチャンク分割のセクションで扱った問題です。どの検索インデックスも、ノイズの多いクエリでは一定のページネーションの深さを過ぎると不安定になります。since:/until: の週次のチャンクに切り替えます。

コマンドが静かに無視される。 フィルターに効果がないように見えるなら、おそらくエンゲージメントフィルターを落とす公式 v2 API に web 検索コマンドを送っています。クエリが間違っていると仮定する前に、プロバイダーがどのコマンドセットを受け付けるかを確認します。

コマンドの上限。 X の検索インデックスは、およそ22〜23を超えるコマンドを持つクエリを静かに失敗させるように見え、公式 API のセルフサーブの最近の検索はクエリ文字列を512文字に制限します。クエリがそれより多くのグループ化を持ち空を返すなら、それを単純化するか複数のリクエストに分けます。

user オブジェクトが欠けているか部分的。 ツイートが作成された時刻とリクエストした時刻の間に、作者が停止されたか、削除されたか、アカウントを保護にしました。ツイートはまだインデックスに存在しますが、作者はもう公に列挙できません。コードの中で tweet.get("user", {}) のデフォルトでこれを扱います。

lang:en にもかかわらず予期しない言語の結果。 X の言語検出器は、特にハッシュタグや混在した文字を持つ短いツイートでは完璧ではありません。分析のワークロードには、full_text フィールドに言語検出のライブラリ(langdetectfasttext-langdetect のような)で後フィルターします。

非公開とシャドウバンのアカウントが現れない。 保護されたアカウントは検索インデックスにありません。停止されロックされたアカウントも隠されます。それらを表面化するコマンドはありません。シャドウバンは検索の可視性とは別の診断の質問で、検索インデックスが答えられるものではありません。

よくある質問 {#frequently-asked-questions}

公式 Twitter API なしでツイートをどう検索する?

サードパーティの検索 API またはオープンソースのスクレイパーを使います。サードパーティの REST API は、OAuth なしと完全な web コマンドのサポートで、API キーの背後に自身のスクレイピングのインフラを包みます。Sorsa はそうした代替の Twitter/X API の1つで、すべてのコマンドを通すフラットレートの /search-tweets エンドポイントを持ちます。Twikit のようなオープンソースのライブラリは小さな単発のジョブには効きますが、本番には十分に安定していません。

API を使ってハッシュタグでツイートをどう検索する?

ハッシュタグをクエリ用語として渡し、たとえば #worldcup、それを量を制御するフィルターと組み合わせます。#worldcup min_faves:50 lang:en -filter:retweets は、そのハッシュタグを運ぶ人気の英語のオリジナルツイートを返します。公式 X API v2 も #hashtag を一致させますが、エンゲージメントの下限を適用できず、最近の検索を約7日間に制限するため、より深いハッシュタグの履歴には、フラットレートの web コマンドの API がより有能な経路です。

7日より古いツイートを検索できる?

はい、サードパーティ API で。Sorsa の /search-tweets エンドポイントは、since:until: のコマンドで2006年まで遡る完全な歴史的なアーカイブをサポートします。公式 X API v2 の最近の検索は約7日間に制限されます。公式 API の完全アーカイブ検索は、より高いコミットの層に制限されます。

検索リクエストあたり何ツイート得られる?

単一の /search-tweets リクエストは、約20ツイートの1ページを返します。より多くを取得するには、next_cursor でカーソルページネーションを使います。数千ツイートを超えるデータセットには、長い歩みでのカーソルのドリフトを避けるために、カーソルページネーションを日付範囲のチャンク分割と組み合わせます。

開発者にとって最も有用な Twitter 検索コマンドは?

本番のコードでの高レバレッジのコマンドは、from:to:min_faves:min_retweets:since:until:lang:、そして -filter:retweets-filter:replies の除外です。エンゲージメントフィルターは、関連するコンテンツを失わずに低品質のノイズを取り除くため、そして公式 X API v2 で動かないため、特に価値があります。

Twitter 検索 API はブール論理(AND、OR、NOT)をサポートする?

はい。スペースで区切った用語は AND を含意します。大文字の OR は明示的な OR です。括弧は式をグループ化します。マイナスの接頭辞は用語を除外します:crypto -scam。完全な例は (bitcoin OR ethereum) min_faves:100 -filter:retweets lang:en です。

2026年に API でツイートを検索するのにいくらかかる?

公式 X API v2 は、従量課金で投稿読み取りあたりおよそ$0.005、加えて作者プロフィール読み取りあたりおよそ$0.010かかり、それは作者データの前で1,000ツイートあたり約$5.00になります。Sorsa はフラットな月次で、100回分の無料リクエスト、クレジットカード不要で始まります。/tweet-info-bulk を通じてバッチすると、ツイートデータは Proプランで1,000ツイートあたり約$0.02(Starter で$0.049から Enterprise で$0.018まで)、作者プロフィール込みになります。ページネーションする /search-tweets エンドポイントを通じてリクエストあたり約20ツイートでは、1,000あたり$0.10に近いです。

リアルタイムでツイートを検索できる?

実際上はい、ポーリングを通じて。毎秒20リクエストのレート制限と order: "latest" で、クエリを数秒ごとにポーリングし、投稿の数秒以内に新しいツイートを得られます。真のプッシュベースのストリーミングには、公式 X API のフィルタードストリームが、ポーリングなしに配信する唯一の選択肢です。ほとんどの監視のワークロードには、30〜60秒の間隔でのポーリングが十分で、運用が安いです。

始め方 {#getting-started}

クライアントのコードを1行書く前にエンドポイントをテストできます。インタラクティブな API playground が、ブラウザから自分のキーに対して実際のリクエストを実行し、ビジュアルな検索クエリビルダー が、コマンドのフォームを与え、送る正確な JSON ボディを出力します。

キーを取得する準備ができたら:最初の100リクエストはクレジットカード不要で無料で40個のエンドポイントすべてをカバーし、サインアップは開発者アカウントの承認なしで数分かかり、すべてのプランが同じ一律の毎秒20リクエストで動きます。バッチすると、フラットなプランは1,000ツイートあたり$0.02という安さになります。Sorsa のダッシュボード でサインアップし、5分のセットアップには クイックスタートガイド に従います。公式 API から離れているなら、先にリンクした移行ガイドが、各 v2エンドポイントをその Sorsa 相当物に両側のコードとともに対応付けます。


監修:Keksich(Sorsa創業者、マーケター兼X APIリサーチャー)

本ガイドのまとめ方:Sorsa の検索インフラをビルドして運用する Sorsa 自身の実地の作業、/search-tweets エンドポイントに対してライブでテストしたもの、そして公式 X API v2 の最近の検索エンドポイントとの直接の比較に基づいています。コマンドの挙動は、コミュニティが維持する igorbrigadir/twitter-advanced-search のリファレンスと公式の X API のドキュメントに対して確認しました。料金は2026年7月8日時点で現行の公式 X API の読み取り単位のレートを反映しています。エンドポイントの詳細は Sorsa API のドキュメント から来ています。このブログを誰が公開しているかの詳細は 会社概要ページ にあります。