X API

X(Twitter)のAPIを使って投稿を読んだり書いたりする方法を説明します。

X API のページを見ると、無料でも月に100件読め、500件書けるようです。100件読めてもあまりうれしくありませんし、連続して試そうとするとすぐエラー(HTTPError: 429 Client Error: Too Many Requests for url)になるので使い勝手が悪いのですが、アプリには表示されない情報(秒単位の時刻、投票結果の票数など)を見たいときなどは役に立ちます(これらは別の手段でも取得できますが)。500件書ける方は、自動投稿に役に立ちます。

まずは先ほどのページから登録し、一連のAPIキーを発行してもらう必要があります。使用状況はダッシュボードで確認できます。

公式のパッケージ xdk が公開されており、コード例が X API v2 - Python Examples にいろいろ載っています。しかし、いまいち動作がわからないところがあるので、以下では汎用のパッケージを使う方法を示しました。あらかじめ pip install requests requests-oauthlib しておきます。

書く方は簡単です。課金していれば長文投稿もできます。

import requests
from requests_oauthlib import OAuth1

API_KEY = "..."
API_SECRET = "..."
ACCESS_TOKEN = "..."
ACCESS_TOKEN_SECRET = "..."

auth = OAuth1(API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

text = "こんにちは!"

r = requests.post(
    "https://api.x.com/2/tweets",
    auth=auth,
    json={"text": text},
    timeout=30,
)
print(r.status_code, r.text)
r.raise_for_status()
tweet_id = r.json()["data"]["id"]
print("Posted:", tweet_id)

読む方は Get Post by ID または Get Posts by IDs に書いてありますが、Bearer token を使う方法(OAuth 2.0 App-only)、API key/secret + access token/secret を使う方法(OAuth 1.0a)があります(ほかにもありますが省略)。前者はログインなしで読む方法、後者はログインして読む方法に対応します。まず前者(params については後述):

import json
import requests

BEARER_TOKEN = "..."

TWEET_ID = 2009093270044069950

url = f"https://api.x.com/2/tweets/{TWEET_ID}"

r = requests.get(
    url,
    headers={"Authorization": f"Bearer {BEARER_TOKEN}"},
    # params=params,
    timeout=30
)

print("Status:", r.status_code)
r.raise_for_status()
payload = r.json()
print(json.dumps(payload, indent=2, ensure_ascii=False))

後者:

import json
import requests
from requests_oauthlib import OAuth1

API_KEY = "..."
API_SECRET = "..."
ACCESS_TOKEN = "..."
ACCESS_TOKEN_SECRET = "..."

TWEET_ID = 2009093270044069950

url = f"https://api.x.com/2/tweets/{TWEET_ID}"
auth = OAuth1(API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

r = requests.get(
    url,
    auth=auth,
    # params=params,
    timeout=30,
)

print("Status:", r.status_code)
r.raise_for_status()
payload = r.json()
print(json.dumps(payload, indent=2, ensure_ascii=False))

パラメータをいろいろ指定できます。# params=params の頭の # を取って、パラメータをあらかじめ次のような感じで指定します。不要な項目は # で消してください。

params = {
    "tweet.fields": ",".join([
        "article",
        "attachments",
        "author_id",
        "card_uri",
        "community_id",
        "context_annotations",
        "conversation_id",
        "created_at",
        "display_text_range",
        "edit_controls",
        "edit_history_tweet_ids", # default
        "entities",
        "geo",
        "id",  # default
        "in_reply_to_user_id",
        "lang",
        "media_metadata",
        # "non_public_metrics",  # 指定するとエラーになることが多い
        "note_tweet",  # long-form post
        # "organic_metrics",  # 指定するとエラーになることが多い
        "possibly_sensitive",
        # "promoted_metrics",  # 指定するとエラーになることが多い
        "public_metrics",
        "referenced_tweets",
        "reply_settings",
        "scopes",
        "source",
        "suggested_source_links",
        "suggested_source_links_with_counts",
        "text",  # default
        "withheld"
    ]),
    "expansions": ",".join([
        "author_id",
        "referenced_tweets.id",
        "edit_history_tweet_ids",
        "in_reply_to_user_id",
        "attachments.media_keys",
        "attachments.poll_ids",
        "geo.place_id",
        "entities.mentions.username",
        "referenced_tweets.id.author_id",
    ]),
    "user.fields": ",".join([
        "id", "name", "username",  # defaults
        # "affiliation", "confirmed_email",
        # "connection_status", "created_at", "description", "entities",
        # "is_identity_verified", "location", "most_recent_tweet_id",
        # "parody", "pinned_tweet_id", "profile_banner_url",
        # "profile_image_url", "protected", "public_metrics",
        # "receives_your_dm", "subscription", "subscription_type",
        # "url", "verified", "verified_followers_count",
        # "verified_type", "withheld",
    ]),
    "media.fields": ",".join([
        "media_key", "type",  # defaults
        # "url", "duration_ms", "height",
        # "preview_image_url", "public_metrics",
        # "width", "alt_text", "variants"
    ]),
    "poll.fields": ",".join([
        "id", "options",  # defaults
        "duration_minutes", "end_datetime", "voting_status",
    ]),
    "place.fields": ",".join([
        "full_name", "id",  # defaults
        "contained_within",
        "country", "country_code", "geo", "name", "place_type",
    ]),
}

パラメータを指定しても取得できないものもあり、よくわかりません。