Twitter Search API v1.1 と v2の 違いメモ

## サマリ

Twitter の Search APIの仕様が、1.1 → 2 でかなり変わっていました。
仕様の全体像を掴むのに少し苦労したため、メモを残しておきます。


## v1.1 と v2 の違い

https://developer.twitter.com/en/docs/twitter-api/tweets/search/introduction

ざっくり言うと

* サービスレベルの変更
* ツイート取得のメソッドの変更

の2つに分けられる。2つをそれぞれ把握しないと面食らう。

## v2のサービスレベル

大きく分けて２レベル。

* 直近７日間のツイートを検索できる エンドポイント 「recent」
 *  https://api.twitter.com/2/tweets/search/recent
*  過去のツイート全てを対象として検索できるエンドポイント 「all」
    *  https://api.twitter.com/2/tweets/search/all

の2つに分かれる。しかし、「all」エンドポイント は、[Academic Research Production](https://developer.twitter.com/en/solutions/academic-research/application-info) のみ利用可能らしい。

v1.1 では「Standard」「Premium」など、契約によってSearchの範囲が変わったが、v２からは学術利用か、一般的な利用か、で検索可能なツイートの期間が変わるようだ。


## v2エンドポイントを利用するための設定

Twitterの開発者画面から[「Project」](https://developer.twitter.com/en/docs/projects/overview)を設定し、そこに紐づく形で「App」を作成する必要がある。

下記のドキュメントに詳しく書かれている。

https://developer.twitter.com/en/docs/apps/overview


## ツイートの取得方法

v1.1 までは、エンドポイントへのコールに対して全ての情報が含まれたJSONが返された。

v2では、基本的に「ツイート」「ツイートID」の二種類しか返ってこない。ツイートしたユーザーの詳細情報を取得するためには、オプションパラメータを設定する必要がある。

わかりづらいので、「Qiita公式アカウントのツイートを直近10件を取得」という条件で比較してみる。

### v1.1 のリクエストとレスポンス


v1.1 のリクエスト

```
https://api.twitter.com/1.1/search/tweets.json?q=from:Qiita&count=10
```

v1.1のレスポンス

```json
{
    "statuses": [
        {
            "created_at": "Thu Apr 29 06:30:07 +0000 2021",
            "id": 1387655418776899586,
            "id_str": "1387655418776899586",
            "text": "800LGTM！ | 低レイヤーを学ぶための技術書をまとめてみる by @hareku908 https://t.co/KvnBiq6ihY",
            "truncated": false,
            "entities": {
                "hashtags": [],
                "symbols": [],
                "user_mentions": [
                    {
                        "screen_name": "hareku908",
                        "name": "hareku",
                        "id": 4765763846,
                        "id_str": "4765763846",
                        "indices": [
                            36,
                            46
                        ]
                    }
                ],
                "urls": [
                    {
                        "url": "https://t.co/KvnBiq6ihY",
                        "expanded_url": "https://bit.ly/3gMGLJt",
                        "display_url": "bit.ly/3gMGLJt",
                        "indices": [
                            47,
                            70
                        ]
                    }
                ]
            },
            "metadata": {
                "iso_language_code": "ja",
                "result_type": "recent"
            },
            "source": "<a href=\"https://buffer.com\" rel=\"nofollow\">Buffer</a>",
            "in_reply_to_status_id": null,
            "in_reply_to_status_id_str": null,
            "in_reply_to_user_id": null,
            "in_reply_to_user_id_str": null,
            "in_reply_to_screen_name": null,
            "user": {
                "id": 341374118,
                "id_str": "341374118",
                "name": "Qiita (キータ) 公式",
                "screen_name": "Qiita",
                "location": "Tokyo, Japan",
                "description": "Qiita公式アカウントです。何かありましたら support@qiita.com までご連絡ください :) / Qiita Team @QiitaTeam / Qiita Jobs @QiitaJobs / 人気の投稿 @qiitapoi / Qiita Zine https://t.co/6dzvjAhEcx /",
                "url": "https://t.co/kT1Qi08l6z",
                "entities": {
                    "url": {
                        "urls": [
                            {
                                "url": "https://t.co/kT1Qi08l6z",
                                "expanded_url": "http://qiita.com",
                                "display_url": "qiita.com",
                                "indices": [
                                    0,
                                    23
                                ]
                            }
                        ]
                    },
                    "description": {
                        "urls": [
                            {
                                "url": "https://t.co/6dzvjAhEcx",
                                "expanded_url": "http://zine.qiita.com",
                                "display_url": "zine.qiita.com",
                                "indices": [
                                    134,
                                    157
                                ]
                            }
                        ]
                    }
                },
                "protected": false,
                "followers_count": 33742,
                "friends_count": 4,
                "listed_count": 787,
                "created_at": "Sun Jul 24 07:54:14 +0000 2011",
                "favourites_count": 800,
                "utc_offset": null,
                "time_zone": null,
                "geo_enabled": false,
                "verified": false,
                "statuses_count": 31376,
                "lang": null,
                "contributors_enabled": false,
                "is_translator": false,
                "is_translation_enabled": false,
                "profile_background_color": "FCFCFC",
                "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png",
                "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png",
                "profile_background_tile": false,
                "profile_image_url": "http://pbs.twimg.com/profile_images/1201406146822557696/ewFFvnAa_normal.jpg",
                "profile_image_url_https": "https://pbs.twimg.com/profile_images/1201406146822557696/ewFFvnAa_normal.jpg",
                "profile_banner_url": "https://pbs.twimg.com/profile_banners/341374118/1608287649",
                "profile_link_color": "55C500",
                "profile_sidebar_border_color": "BDDCAD",
                "profile_sidebar_fill_color": "DDFFCC",
                "profile_text_color": "272E23",
                "profile_use_background_image": false,
                "has_extended_profile": false,
                "default_profile": false,
                "default_profile_image": false,
                "following": null,
                "follow_request_sent": null,
                "notifications": null,
                "translator_type": "none",
                "withheld_in_countries": []
            },
            "geo": null,
            "coordinates": null,
            "place": null,
            "contributors": null,
            "is_quote_status": false,
            "retweet_count": 1,
            "favorite_count": 8,
            "favorited": false,
            "retweeted": false,
            "possibly_sensitive": false,
            "lang": "ja"
        },
        {
            "created_at": "Thu Apr 29 06:20:01 +0000 2021",
            "id": 1387652875547987969,
            "id_str": "1387652875547987969",
            "text": "100LGTM！ | Hasuraで既存のPostgreSQLから爆速でGraphQL APIサーバーを構築する by @KawamataRyo https://t.co/2hSLn4XpxA",
            "truncated": false,
            "entities": {
                "hashtags": [],
                "symbols": [],
                "user_mentions": [
                    {
                        "screen_name": "KawamataRyo",
                        "name": "Kawamata Ryo",
                        "id": 927515451457806337,
                        "id_str": "927515451457806337",
                        "indices": [
                            60,
                            72
                        ]
                    }
                ],
                "urls": [
                    {
                        "url": "https://t.co/2hSLn4XpxA",
                        "expanded_url": "https://bit.ly/3nAgEqj",
                        "display_url": "bit.ly/3nAgEqj",
                        "indices": [
                            73,
                            96
                        ]
                    }
                ]
            },
            "metadata": {
                "iso_language_code": "ja",
                "result_type": "recent"
            },
            "source": "<a href=\"https://buffer.com\" rel=\"nofollow\">Buffer</a>",
            "in_reply_to_status_id": null,
            "in_reply_to_status_id_str": null,
            "in_reply_to_user_id": null,
            "in_reply_to_user_id_str": null,
            "in_reply_to_screen_name": null,
            "user": {
                "id": 341374118,
                "id_str": "341374118",
                "name": "Qiita (キータ) 公式",
                "screen_name": "Qiita",
                "location": "Tokyo, Japan",
                "description": "Qiita公式アカウントです。何かありましたら support@qiita.com までご連絡ください :) / Qiita Team @QiitaTeam / Qiita Jobs @QiitaJobs / 人気の投稿 @qiitapoi / Qiita Zine https://t.co/6dzvjAhEcx /",
                "url": "https://t.co/kT1Qi08l6z",
                "entities": {
                    "url": {
                        "urls": [
                            {
                                "url": "https://t.co/kT1Qi08l6z",
                                "expanded_url": "http://qiita.com",
                                "display_url": "qiita.com",
                                "indices": [
                                    0,
                                    23
                                ]
                            }
                        ]
                    },
                    "description": {
                        "urls": [
                            {
                                "url": "https://t.co/6dzvjAhEcx",
                                "expanded_url": "http://zine.qiita.com",
                                "display_url": "zine.qiita.com",
                                "indices": [
                                    134,
                                    157
                                ]
                            }
                        ]
                    }
                },
                "protected": false,
                "followers_count": 33742,
                "friends_count": 4,
                "listed_count": 787,
                "created_at": "Sun Jul 24 07:54:14 +0000 2011",
                "favourites_count": 800,
                "utc_offset": null,
                "time_zone": null,
                "geo_enabled": false,
                "verified": false,
                "statuses_count": 31376,
                "lang": null,
                "contributors_enabled": false,
                "is_translator": false,
                "is_translation_enabled": false,
                "profile_background_color": "FCFCFC",
                "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png",
                "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png",
                "profile_background_tile": false,
                "profile_image_url": "http://pbs.twimg.com/profile_images/1201406146822557696/ewFFvnAa_normal.jpg",
                "profile_image_url_https": "https://pbs.twimg.com/profile_images/1201406146822557696/ewFFvnAa_normal.jpg",
                "profile_banner_url": "https://pbs.twimg.com/profile_banners/341374118/1608287649",
                "profile_link_color": "55C500",
                "profile_sidebar_border_color": "BDDCAD",
                "profile_sidebar_fill_color": "DDFFCC",
                "profile_text_color": "272E23",
                "profile_use_background_image": false,
                "has_extended_profile": false,
                "default_profile": false,
                "default_profile_image": false,
                "following": null,
                "follow_request_sent": null,
                "notifications": null,
                "translator_type": "none",
                "withheld_in_countries": []
            },
            "geo": null,
            "coordinates": null,
            "place": null,
            "contributors": null,
            "is_quote_status": false,
            "retweet_count": 0,
            "favorite_count": 4,
            "favorited": false,
            "retweeted": false,
            "possibly_sensitive": false,
            "lang": "ja"
        },
 
/*   以下、取得件数分のデータが表示される */


    ],
    "search_metadata": {
        "completed_in": 0.07,
        "max_id": 1387655418776899586,
        "max_id_str": "1387655418776899586",
        "next_results": "?max_id=1387547179829575679&q=from%3AQiita&count=10&include_entities=1",
        "query": "from%3AQiita",
        "refresh_url": "?since_id=1387655418776899586&q=from%3AQiita&include_entities=1",
        "count": 10,
        "since_id": 0,
        "since_id_str": "0"
    }
}
```


### v2 のリクエストとレスポンス



v2のリクエスト

```
https://api.twitter.com/2/tweets/search/recent?max_results=10&query=from:Qiita
```

v2のレスポンス

```json
{
    "data": [
        {
            "id": "1387652875547987969",
            "text": "100LGTM！ | Hasuraで既存のPostgreSQLから爆速でGraphQL APIサーバーを構築する by @KawamataRyo https://t.co/2hSLn4XpxA"
        },
        {
            "id": "1387647837983412224",
            "text": "300LGTM！ | 機械学習×投資の無料ツールまとめ ～Awesome Algorithmic Trading～ by @cryptrader2020 https://t.co/UO3ZA8mRkj"
        },
        {
            "id": "1387642808182452225",
            "text": "4000LGTM！ | 使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 by @jnchito https://t.co/3b255fR01l"
        },
        {
            "id": "1387637775919419393",
            "text": "50LGTM！ | [Pythonによる科学・技術計算]　やさしい分子動力学シミュレーション, 2次元系, NVEアンサンブル，熱・統計物理学 https://t.co/ELvBHrXVnx"
        },
        {
            "id": "1387627710198030337",
            "text": "50LGTM！ | 【個人開発】ポモドーロテクニックとTodoリストを組み合わせた時間管理アプリを作りました by @miii01220 https://t.co/Y5u1rgHZAf"
        },
        {
            "id": "1387625188330704897",
            "text": "50LGTM！ | ペアプロ・モブプロでメンバーが驚くほど成長した話 by @kojimadev https://t.co/aq8VwBL40h"
        },
        {
            "id": "1387607576032985094",
            "text": "1100LGTM！ | SREやクラウドエンジニアが読むと良さげな本まとめ by @tmknom https://t.co/eihzWpp9H9"
        },
        {
            "id": "1387589960555974657",
            "text": "200LGTM！ | iOS14でのIDFA取得 https://t.co/RD6dfMlbpZ"
        },
        {
            "id": "1387547179829575680",
            "text": "50LGTM！ | ポストエフェクトクエスト - 波長方向の積分マジ大事という話 - https://t.co/v2CW7HqEhH"
        },
        {
            "id": "1387544662286733313",
            "text": "1600LGTM！ | Linuxディレクトリ構造 https://t.co/xHjpczl4hd"
        }
    ],
    "meta": {
        "newest_id": "1387652875547987969",
        "oldest_id": "1387544662286733313",
        "result_count": 10,
        "next_token": "b26v89c19zqg8o3fosttqjg5j32790ogxv7mnb19xcghp"
    }
}
```


v2 のレスポンスはかなりスリムになっていることが分かる。
そして、ツイート以外のデータは含まれていない。

v2で、ユーザー情報や、そのほかのデータを取得する場合は以下のようにリクエストを行う。

```
https://api.twitter.com/2/tweets/search/recent?max_results=10&tweet.fields=created_at&expansions=author_id&user.fields=name,username,url,description&query=from:Qiita
```

レスポンスはこうなる

```json
{
    "data": [
        {
            "text": "800LGTM！ | 低レイヤーを学ぶための技術書をまとめてみる by @hareku908 https://t.co/KvnBiq6ihY",
            "author_id": "341374118",
            "created_at": "2021-04-29T06:30:07.000Z",
            "id": "1387655418776899586"
        },
        {
            "text": "100LGTM！ | Hasuraで既存のPostgreSQLから爆速でGraphQL APIサーバーを構築する by @KawamataRyo https://t.co/2hSLn4XpxA",
            "author_id": "341374118",
            "created_at": "2021-04-29T06:20:01.000Z",
            "id": "1387652875547987969"
        },
        {
            "text": "300LGTM！ | 機械学習×投資の無料ツールまとめ ～Awesome Algorithmic Trading～ by @cryptrader2020 https://t.co/UO3ZA8mRkj",
            "author_id": "341374118",
            "created_at": "2021-04-29T06:00:00.000Z",
            "id": "1387647837983412224"
        },
        {
            "text": "4000LGTM！ | 使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 by @jnchito https://t.co/3b255fR01l",
            "author_id": "341374118",
            "created_at": "2021-04-29T05:40:01.000Z",
            "id": "1387642808182452225"
        },
        {
            "text": "50LGTM！ | [Pythonによる科学・技術計算]　やさしい分子動力学シミュレーション, 2次元系, NVEアンサンブル，熱・統計物理学 https://t.co/ELvBHrXVnx",
            "author_id": "341374118",
            "created_at": "2021-04-29T05:20:01.000Z",
            "id": "1387637775919419393"
        },
        {
            "text": "50LGTM！ | 【個人開発】ポモドーロテクニックとTodoリストを組み合わせた時間管理アプリを作りました by @miii01220 https://t.co/Y5u1rgHZAf",
            "author_id": "341374118",
            "created_at": "2021-04-29T04:40:01.000Z",
            "id": "1387627710198030337"
        },
        {
            "text": "50LGTM！ | ペアプロ・モブプロでメンバーが驚くほど成長した話 by @kojimadev https://t.co/aq8VwBL40h",
            "author_id": "341374118",
            "created_at": "2021-04-29T04:30:00.000Z",
            "id": "1387625188330704897"
        },
        {
            "text": "1100LGTM！ | SREやクラウドエンジニアが読むと良さげな本まとめ by @tmknom https://t.co/eihzWpp9H9",
            "author_id": "341374118",
            "created_at": "2021-04-29T03:20:01.000Z",
            "id": "1387607576032985094"
        },
        {
            "text": "200LGTM！ | iOS14でのIDFA取得 https://t.co/RD6dfMlbpZ",
            "author_id": "341374118",
            "created_at": "2021-04-29T02:10:01.000Z",
            "id": "1387589960555974657"
        },
        {
            "text": "50LGTM！ | ポストエフェクトクエスト - 波長方向の積分マジ大事という話 - https://t.co/v2CW7HqEhH",
            "author_id": "341374118",
            "created_at": "2021-04-28T23:20:01.000Z",
            "id": "1387547179829575680"
        }
    ],
    "includes": {
        "users": [
            {
                "username": "Qiita",
                "id": "341374118",
                "name": "Qiita (キータ) 公式",
                "url": "https://t.co/kT1Qi08l6z",
                "description": "Qiita公式アカウントです。何かありましたら support@qiita.com までご連絡ください :) / Qiita Team @QiitaTeam / Qiita Jobs @QiitaJobs / 人気の投稿 @qiitapoi / Qiita Zine https://t.co/6dzvjAhEcx /"
            }
        ]
    },
    "meta": {
        "newest_id": "1387655418776899586",
        "oldest_id": "1387547179829575680",
        "result_count": 10,
        "next_token": "b26v89c19zqg8o3fosttqjg5nlv28d3xdrvv4rx3cqk59"
    }
}
```


レスポンスデータの情報が厚くなっていることが分かる。



## v1.1 から v2 への移行

v1.1 ユーザー向けに、移行ガイドが公開されている。

https://developer.twitter.com/en/docs/twitter-api/tweets/search/migrate

…が、普通にAPIリファレンス見ながら書き直した方が早いかも。

APIリファレンスはこちら。

https://developer.twitter.com/en/docs/twitter-api/tweets/search/api-reference



## そのほか

Twitter API v2 開発者のために、Postman用のコレクションが公開・頒布されていました。

説明ページ
https://developer.twitter.com/en/docs/tools-and-libraries/using-postman

Postman側のコレクション配布ページ
https://documenter.getpostman.com/view/9956214/T1LMiT5U

Postmanを使っている人は、上記のコレクションを使うのがおすすめ。パラメータの設定が楽で、v2の仕様を手っ取り早く把握できます。


以上、参考になれば幸いです。