X APIで動画付き投稿をPythonで自動化する手順|Tweepyで分割アップロードし処理完了を待って投稿、日本語140字の上限はリプライ連結で補う
X APIで動画付き投稿をPythonで自動化する手順|Tweepyで分割アップロードし処理完了を待って投稿、日本語140字の上限はリプライ連結で補う
自社で作ったツールや、店舗の施術の流れをXで紹介するとき、文章だけでは伝わりません。当社でも「ツールの機能や設定の話は、画面のスクリーンショットか動画を必ず添える」というルールにしています。文章だけの投稿は、読んだ人が画面を想像できないからです。
ただ、X APIで動画を付けて投稿しようとすると、テキスト投稿とは手順が変わります。先に結論を書きます。
- 動画は「分割アップロード」で送り、X側の処理が終わるのを待ってから投稿します。 アップロードが終わった直後のメディアIDをすぐ投稿に渡すと、処理中のまま投稿しようとして失敗することがあります。
- PythonのライブラリTweepyを使う場合、アップロードは
tweepy.API、投稿はtweepy.Clientと使い分けます。 当社が使った Tweepy 4.17 では、Client側に動画をアップロードする機能がありませんでした。 - X Premium(有料プラン)に加入していないアカウントは、日本語だと1投稿あたり約140字が上限です。 長い説明は、最初の投稿へのリプライとしてつなげます(いわゆるスレッド形式)。
- 投稿の直前に「いま誰のアカウントで投稿しようとしているか」をAPIで確かめます。 複数アカウントのトークンを同じ場所に置いていると、別アカウントへの誤投稿が起こりえます。
以下、2026年9月に当社代表のXアカウントで動画付き投稿を実際に行った手順と、当社のキャラクターアカウントで2026年7月から運用している自動投稿の設計をもとに説明します。
実例:15秒・約20MBの動画が1回のアップロードで通った
検証に使ったのは、15秒の動画です。
| 項目 | 内容 |
|---|---|
| 形式 | MP4 |
| 解像度・フレームレート | 1080p・60fps |
| ファイルサイズ | 約20.8MB |
| 使ったライブラリ | Tweepy 4.17 |
| 結果 | 1回のアップロードで処理状態が「succeeded(完了)」になり、そのまま投稿できた |
続けて、動画の解説文を8本、最初の投稿へのリプライとして4秒間隔でつなげて投稿しました。8本ともエラーは出ていません。
それまで当社の自動投稿スクリプトは「画像・動画は将来の課題」としてテキストだけを扱っていました。この手順で動画が通ったことで、画像(メディアの種類を画像用に変えるだけ)も含めて添付できる見通しが立ちました(当社の検証は動画のみです)。
手順1:認証情報を用意し、投稿先のアカウントを確かめる
X APIで個人のアカウントとして投稿するには、開発者ポータルで発行する4つの値が必要です。
- APIキー(API Key)
- APIシークレット(API Secret)
- アクセストークン(Access Token)
- アクセストークンシークレット(Access Token Secret)
前の2つはアプリ(開発者ポータルに登録した自動化の単位)の鍵、後の2つは「どのアカウントとして投稿するか」の鍵です。
自社アカウントと別のアカウントを1つのアプリで扱う場合、後者の2つはアカウントごとに取得します。取得方法の1つに、表示された認証URLを本人が開いて承認し、画面に出た数字(PIN)を入力する方式があります。この方式は、認証URLを発行してから数分で無効になります。 先方に頼むときは、URLを送ったらすぐ開いて承認してもらう段取りにしておくと、取り直しの手間が減ります。
鍵がそろったら、投稿の前に必ず「このトークンは誰のアカウントか」を確かめます。
import tweepy
client = tweepy.Client(
consumer_key=API_KEY,
consumer_secret=API_SECRET,
access_token=ACCESS_TOKEN,
access_token_secret=ACCESS_TOKEN_SECRET,
)
me = client.get_me()
if me.data.username != "投稿したいアカウントのユーザー名":
raise SystemExit("想定と違うアカウントのトークンです。投稿を中止します")
複数アカウントの鍵を同じ設定ファイルに並べていると、変数名を1文字間違えるだけで別アカウントに投稿してしまいます。公開した投稿は取り消しても、見た人の記憶には残ります。ユーザー名の照合は、手動の試験投稿でも自動投稿でも毎回入れておくのが安全です。
手順2:動画を分割アップロードし、処理完了を待つ
動画は画像と違い、1回の通信でまとめて送らず、小さな塊に分けて送ります。流れは次の4段階です。
- 開始の申告:ファイルの種類と大きさを伝え、メディアIDを受け取る
- 分割送信:ファイルを塊ごとに送る
- 完了の申告:送り終わったことを伝える
- 処理状態の確認:X側の変換処理が終わるまで、状態を問い合わせて待つ
ポイントは4つ目です。送り終わった直後は、X側でまだ動画の変換が続いています。この段階のメディアIDで投稿すると、処理が終わっていない状態で投稿しようとして失敗する場合があります。
Tweepyでは、この4段階を1つの呼び出しで済ませられます。
auth = tweepy.OAuth1UserHandler(
API_KEY, API_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET
)
api = tweepy.API(auth)
media = api.media_upload(
"clip.mp4",
chunked=True, # 分割アップロードにする
media_category="tweet_video", # 投稿に付ける動画として扱う
wait_for_async_finalize=True, # 処理完了まで待つ
)
設定の意味は次のとおりです。
chunked=True:分割アップロードにします。動画ではこれが前提です。media_category="tweet_video":投稿に添付する動画であることを伝えます。画像ならtweet_imageに変えます。wait_for_async_finalize=True:処理状態が完了になるまで待ってから戻ってきます。
当社の検証では、15秒・約20.8MBの動画がこの呼び出し1回で完了状態になりました。
なお、tweepy.API はXの従来からある窓口を使う方法です。Xの仕様変更で使えなくなる可能性もあるため、うまくいかない場合はTweepyとX公式ドキュメントの最新情報を確認してください。
手順3:メディアIDを付けて投稿し、解説はリプライでつなげる
アップロードで受け取ったメディアIDを、Client の投稿に渡します。
import time
res = client.create_tweet(text="1本目の本文", media_ids=[media.media_id])
prev_id = res.data["id"]
for text in replies: # 解説文のリスト(1本ずつ文字数上限内)
time.sleep(4)
r = client.create_tweet(text=text, in_reply_to_tweet_id=prev_id)
prev_id = r.data["id"]
in_reply_to_tweet_id に直前の投稿IDを入れると、自分の投稿へのリプライとして1本の流れにつながります。間隔を数秒あけるのは、短時間の連続投稿による制限や機械的な見え方を避けるためです。当社の常設スクリプトは3秒、手動の検証は4秒にしています。手動の検証(4秒間隔で8本)ではエラーは出ていません。
判断基準1:文字数は「見た目の字数」ではなく重み付きで数える
X Premiumに加入していないアカウントでは、1投稿の上限は「重み付きで280」です。英数字は1文字1、日本語(漢字・ひらがな・カタカナ)や絵文字は1文字2として数えます。日本語だけの文章なら約140字が上限になります。
この数え方を知らずに、日本語の文字数だけで「280字まで書ける」と考えると、投稿の段階でエラーになります。自動化するなら、投稿前に重み付きの字数を計算して判定します。
def weighted_length(text):
total = 0
for ch in text:
# 英数字・一般的な記号は1、それ以外(日本語など)は2
total += 1 if ord(ch) <= 4351 else 2
return total
これは公式のカウント方法を簡略化した近似です。一部の記号を実際より多めに数えるほか、URLは公式では長さに関係なく23字として数えられるため、短いURLを含む文では少なめにずれることがあります。URLを含めない前提(判断基準4参照)なら、ずれは多めに数える方向だけになり、上限超えで失敗することはありません。
判断基準2:長文を自動で分けるときは「切れない文章」を想定しておく
上限を超える文章を自動で分割するときは、句点(。!?)で区切るだけでは足りません。「・」を改行で並べた箇条書きには句点がほとんど無く、全体が1つの長い文として扱われて、どこでも切れなくなるからです。当社の運用データでは、承認済みの投稿95件のうち36件がこの形で、句点だけの分割では投稿できない文章でした。
そこで、次の順に区切ります。
- まず句点の位置で区切る
- それでも上限を超える塊があれば、その塊だけ改行の位置で区切り直す(箇条書きは改行で切っても意味が崩れない)
- それでも1つの塊が上限を超えるなら、その投稿は無理に切らず、投稿しないで記録に残す
この順で区切ると、上の36件もすべて分割できます。3つ目の「無理に切らない」も重要です。文の途中で機械的に切ると、意味の通らない投稿が公開されてしまいます。
あわせて、分けた各投稿の長さがなるべく均等になるように区切り位置を選ぶと、「1本目が長く、最後が数文字だけ」という読みにくい形を避けられます。
判断基準3:途中で失敗したときに二重投稿しない
リプライ連結の途中、たとえば8本中5本目で通信エラーが起きたとします。ここで「失敗したから最初からやり直す」と、1本目から4本目が2回公開されます。
当社のスクリプトでは、次のように扱いを分けています。
| 状態 | 扱い |
|---|---|
| 1本目の投稿自体が失敗 | 何も公開されていないので「未投稿」のまま。次回に再試行 |
| 1本目は成功、途中で失敗 | 1本目は公開済みなので「投稿済み」として記録し、自動では再試行しない。失敗した位置はログに残す |
「一部でも公開されたら投稿済みとして記録する」のが、二重投稿を防ぐ基本です。続きの補完が必要なら、ログを見て人が判断します。
判断基準4:費用と添付の扱いを先に決めておく
X APIは、投稿の本数に応じて費用がかかる従量課金で利用しています。当社が実装した時点の単価は、テキストの投稿が1本あたり0.015ドル、URLを含む投稿は1本あたり0.20ドルでした。動画や画像を付けた投稿の単価は当社では記録していません。単価は変わることがあるため、利用前に開発者ポータルで確認してください。
この差があるため、当社のスクリプトは本文にURLが含まれていたら、明示的に許可しない限り投稿しない設定にしています。
また、リプライ連結は本数の分だけ費用がかかります。当社のキャラクターアカウントはX Premiumに加入しており、長文を1投稿で出せます。そこで、1回あたり5本のつながった投稿として出していたものを、1本の長文投稿にまとめる方式に切り替えました。APIの呼び出しは5回から1回、1回分の概算費用は0.075ドルから0.015ドルに下がっています。Premiumに加入しているかどうかで、どちらの方式が得かが変わります。
添付については、冒頭のルールをスクリプト側でも守らせています。
- ツールの機能・設定を扱う題材は、画像か動画が無い限り、承認済みでも投稿しない
- 素材の無い題材は、そもそも投稿文を作らない
文章だけを先に作って「画像は後で」とすると、画像の無いまま公開されがちです。止める仕組みを投稿処理の側に置いておくと確実です。
補足:定期実行で動かない場合に確かめること
手動で試すと動くのに、Macの定期実行(launchd)に登録すると失敗する場合は、スクリプトや設定ファイルの置き場所を確かめてください。iCloud Driveの中に置いたファイルは、定期実行のように画面を持たない処理からは「Operation not permitted(許可されていません)」で読めないことがあります。
当社では、自動投稿のフォルダをiCloud Driveの外(ホームフォルダ直下)へ移して解決しました。Pythonにディスクへのアクセス権を付ける方法もありますが、開発ツールの更新で権限が外れて再発しやすいため、置き場所を変えるほうが安定します。
まとめ
- 動画は
tweepy.APIのmedia_uploadで、分割アップロード(chunked=True)・動画の種類(tweet_video)・処理完了まで待つ(wait_for_async_finalize=True)の3点を指定して送る - 受け取ったメディアIDを
tweepy.Clientのcreate_tweetに渡して投稿する - 投稿前に
get_me()でアカウント名を照合し、別アカウントへの誤投稿を防ぐ - X Premiumに加入していないアカウントは日本語で約140字が上限。長い解説はリプライでつなげ、文字数は重み付きで数える
- 自動分割は句点→改行の順に区切り、どうしても切れない投稿は公開しない
- 途中で失敗したら「一部でも公開されたら投稿済み」として扱い、二重投稿を避ける
- URL付き投稿の単価や、画像の無い投稿の扱いは、スクリプト側で先に止める
ツールや施術の紹介は、短い動画が1本あるだけで伝わり方が大きく変わります。手順そのものは数十行のコードで組めるので、まずは手動で1本投稿して処理完了まで通ることを確かめ、そのあと定期実行に載せる順番で進めるのがおすすめです。