Claude Code × Python でYouTube Shortsを全自動化!バズる動画を毎日自動投稿する方法

未分類

はじめに:なぜYouTube Shortsを自動化するのか?

YouTube Shortsは2025年現在、月間アクティブユーザー数が爆発的に増加しており、短尺動画でのマネタイズ・集客が最も注目されているコンテンツ戦略のひとつです。

しかし「毎日投稿しなければいけない」「トレンドを常にチェックしなければいけない」という負担が大きく、継続できずに挫折する人が多いのも事実。

そこで本記事では、Claude API × Python を使って、以下をすべて自動化するシステムの構築方法を解説します。

  • ✅ トレンドテーマのリサーチ(YouTube急上昇 + Google Trends)
  • ✅ Claude AIによるバズるテーマ分析
  • ✅ 台本・字幕の自動生成
  • ✅ 音声合成(VOICEVOX / ずんだもん)
  • ✅ 動画レンダリング(Pexels背景 + Remotion)
  • ✅ YouTube Data APIで自動投稿
  • ✅ 毎日09:00のスケジュール実行

プログラミング経験はあるが、AIやYouTube APIは初めてという方に向けて、ステップごとに丁寧に説明します。


システム全体像

全体の処理フローは以下の通りです。

[scheduler.py] 毎日09:00起動
      ↓
[research.py]  トレンドデータ収集
      ↓
[analyze.py]   Claude APIでバズテーマ選定
      ↓
[script.py]    Claude APIで台本 + 字幕タイムライン生成
      ↓
[audio.py]     VOICEVOXで音声合成
      ↓
[video.py]     Pexels動画取得 → Remotionでレンダリング
      ↓
[upload.py]    YouTube Data API v3で投稿完了

各モジュールが独立しているため、「音声だけ変えたい」「レンダラーを差し替えたい」といったカスタマイズも容易です。


必要な環境・ツール

必須ツール一覧

ツール 用途 料金
Python 3.10+ メイン処理言語 無料
Claude API(Anthropic) テーマ分析・台本生成 従量課金($3〜/100万トークン)
VOICEVOX 日本語音声合成 無料(ローカル実行)
Remotion 動画レンダリング 無料(個人利用)
Pexels API フリー背景動画の取得 無料
YouTube Data API v3 動画アップロード 無料(クォータ制限あり)
Node.js 18+ Remotion実行に必要 無料

ステップ1:プロジェクトのセットアップ

ディレクトリ構成

youtube-shorts-bot/
├── main.py          # エントリーポイント
├── scheduler.py     # 自動スケジューラ
├── research.py      # トレンドリサーチ
├── analyze.py       # Claude APIでテーマ分析
├── script.py        # 台本・字幕生成
├── audio.py         # VOICEVOX音声合成
├── video.py         # 動画生成
├── upload.py        # YouTube投稿
├── .env             # APIキー管理
├── requirements.txt
└── output/          # 生成ファイルの保存先
    ├── narration.wav
    ├── clips/
    └── data.json

パッケージのインストール

# 仮想環境の作成
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# 依存パッケージのインストール
pip install anthropic pytrends google-api-python-client \
            google-auth-oauthlib requests python-dotenv schedule

.env ファイルの設定

ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxx
PEXELS_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
YOUTUBE_CLIENT_SECRET_PATH=./client_secret.json

⚠️ .env ファイルは絶対に Git にコミットしないでください。 .gitignore に追加しましょう。


ステップ2:トレンドリサーチ(research.py)

まず、バズっているテーマを自動で収集します。YouTube急上昇動画のタイトルと、Google Trendsのキーワードを組み合わせることで精度を高めます。

import os
from pytrends.request import TrendReq
from googleapiclient.discovery import build
from dotenv import load_dotenv

load_dotenv()

def get_youtube_trending():
    """YouTube急上昇動画のタイトル一覧を取得"""
    youtube = build("youtube", "v3", developerKey=os.getenv("YOUTUBE_API_KEY"))
    response = youtube.videos().list(
        part="snippet",
        chart="mostPopular",
        regionCode="JP",
        maxResults=20
    ).execute()

    titles = [item["snippet"]["title"] for item in response["items"]]
    return titles

def get_google_trends():
    """Google Trendsの急上昇キーワードを取得"""
    pytrends = TrendReq(hl="ja-JP", tz=540)
    trending = pytrends.trending_searches(pn="japan")
    return trending[0].tolist()[:20]

def collect_trends():
    """トレンドデータをまとめて返す"""
    return {
        "youtube_trending": get_youtube_trending(),
        "google_trends": get_google_trends()
    }

ステップ3:Claude APIでバズテーマを分析(analyze.py)

収集したトレンドデータをClaude APIに渡し、「Shorts向けにバズりやすいテーマ」を選定してもらいます。ここがこのシステムの核心部分です。

import anthropic
import json
from dotenv import load_dotenv

load_dotenv()
client = anthropic.Anthropic()

def analyze_trends(trends: dict) -> dict:
    """トレンドデータを元にバズるテーマをClaudeに選定させる"""

    prompt = f"""
以下のトレンドデータを分析し、YouTube Shorts(60秒以内)として
最もバズる可能性が高いテーマを1つ選んでください。

## YouTube急上昇タイトル
{json.dumps(trends['youtube_trending'], ensure_ascii=False, indent=2)}

## Google Trendsキーワード
{json.dumps(trends['google_trends'], ensure_ascii=False, indent=2)}

## 選定基準
- 幅広い年齢層に刺さる話題であること
- 60秒以内で説明できる内容であること
- 「知らなかった!」「ためになった!」と思わせる内容であること
- 炎上リスクが低いこと

## 出力形式(JSON)
{{
  "theme": "選定したテーマ(例:日本人が知らない節約術TOP3)",
  "reason": "選定理由",
  "hook": "冒頭の掴みセリフ(視聴者を引き込む一文)",
  "keywords": ["SEOキーワード1", "キーワード2", "キーワード3"]
}}
"""

    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}]
    )

    # JSON部分を抽出してパース
    text = response.content[0].text
    start = text.find("{")
    end = text.rfind("}") + 1
    return json.loads(text[start:end])

💡 ポイント: プロンプトに「出力形式(JSON)」を明示することで、後続処理がパースしやすくなります。Claude は指示に忠実なので、構造化出力との相性が非常に良いです。


ステップ4:台本・字幕タイムラインの自動生成(script.py)

決定したテーマをもとに、読み上げ用の台本と字幕タイムライン(何秒にどの字幕を表示するか)を生成します。

def generate_script(theme_data: dict) -> dict:
    """台本と字幕タイムラインを生成"""

    prompt = f"""
YouTube Shortsの台本を作成してください。

## テーマ
{theme_data['theme']}

## 冒頭の掴み
{theme_data['hook']}

## 条件
- 合計60秒以内(日本語で約200〜250文字)
- 語りかけるような口語体
- 「〜です」「〜ます」調で統一
- 最後に「いいね・フォローお願いします!」で締める

## 出力形式(JSON)
{{
  "title": "動画タイトル(30文字以内)",
  "description": "動画説明文(100文字以内、ハッシュタグ3つ含む)",
  "narration": "読み上げ全文テキスト",
  "subtitles": [
    {{"start": 0, "end": 3, "text": "冒頭の字幕テキスト"}},
    {{"start": 3, "end": 7, "text": "次の字幕テキスト"}}
  ]
}}
"""

    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=2048,
        messages=[{"role": "user", "content": prompt}]
    )

    text = response.content[0].text
    start = text.find("{")
    end = text.rfind("}") + 1
    return json.loads(text[start:end])

ステップ5:VOICEVOXで音声合成(audio.py)

VOICEVOXはローカルで動作する高品質な音声合成エンジンです。ずんだもん(speaker_id=1)を使うことでキャラクター性を出せます。

事前準備として、VOICEVOX公式サイトからアプリをダウンロード・起動しておいてください。

import requests
import json

VOICEVOX_URL = "http://localhost:50021"
SPEAKER_ID = 1  # ずんだもん

def synthesize_voice(text: str, output_path: str):
    """テキストをVOICEVOXで音声合成してwavファイルに保存"""

    # 音声クエリの生成
    query_res = requests.post(
        f"{VOICEVOX_URL}/audio_query",
        params={"text": text, "speaker": SPEAKER_ID}
    )
    query_res.raise_for_status()
    audio_query = query_res.json()

    # 話速・音高の調整(お好みで)
    audio_query["speedScale"] = 1.1   # 少し速め
    audio_query["pitchScale"] = 0.0

    # 音声合成の実行
    synth_res = requests.post(
        f"{VOICEVOX_URL}/synthesis",
        params={"speaker": SPEAKER_ID},
        data=json.dumps(audio_query),
        headers={"Content-Type": "application/json"}
    )
    synth_res.raise_for_status()

    with open(output_path, "wb") as f:
        f.write(synth_res.content)

    print(f"音声を保存しました: {output_path}")

ステップ6:動画レンダリング(video.py)

Pexels APIから背景動画を取得し、Remotion(React/TSベースの動画生成ツール)でテロップ付き動画をレンダリングします。

Pexelsから背景動画を取得

import requests
import os

def fetch_background_videos(keyword: str, output_dir: str, count: int = 5):
    """Pexels APIから縦型動画(Shorts用)を取得"""
    headers = {"Authorization": os.getenv("PEXELS_API_KEY")}
    res = requests.get(
        "https://api.pexels.com/videos/search",
        headers=headers,
        params={
            "query": keyword,
            "orientation": "portrait",  # 縦型(Shorts用)
            "size": "medium",
            "per_page": count
        }
    )

    clip_paths = []
    for i, video in enumerate(res.json()["videos"]):
        video_url = video["video_files"][0]["link"]
        path = os.path.join(output_dir, "clips", f"clip_{i}.mp4")
        os.makedirs(os.path.dirname(path), exist_ok=True)

        video_data = requests.get(video_url).content
        with open(path, "wb") as f:
            f.write(video_data)
        clip_paths.append(f"clips/clip_{i}.mp4")  # 相対パスで保存!

    return clip_paths

Remotionでレンダリング

Remotionは data.json を参照して動画を生成します。重要なポイントはパスを相対パスで指定することです。

import subprocess
import json
import shutil

def render_video(script_data: dict, clip_paths: list, output_path: str):
    """Remotionで動画をレンダリングする"""

    # data.jsonを作成(パスはすべて相対パス!)
    data = {
        "narrationPath": "narration.wav",      # 相対パス
        "clips": clip_paths,                        # ["clips/clip_0.mp4", ...]
        "bgmPath": "bgm.mp3",                       # 相対パス
        "subtitles": script_data["subtitles"]
    }

    data_json_path = os.path.join(output_path, "data.json")
    with open(data_json_path, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False)

    # BGMをoutput_pathにコピー
    shutil.copy("assets/bgm.mp3", os.path.join(output_path, "bgm.mp3"))

    output_file = os.path.join(output_path, "output.mp4")

    # --public-dir でoutput_pathを指定(絶対パス問題を回避)
    result = subprocess.run([
        "npx", "remotion", "render", "ShortsVideo",
        f"--public-dir={output_path}",
        f"--props={data_json_path}",
        output_file
    ], capture_output=True, text=True)

    if result.returncode != 0:
        raise RuntimeError(f"Remotionレンダリング失敗:\n{result.stderr}")

    return output_file

⚠️ よくあるミスと解決策:

staticFile() に絶対パス(C:/Users/...形式)を渡すとレンダリングが失敗(exit code=1)します。必ず --public-dir オプションでoutputディレクトリを指定し、パスは相対パスで記述してください。


ステップ7:YouTube Data APIで自動投稿(upload.py)

Google Cloud ConsoleでOAuth2認証を設定し、client_secret.json を取得してから実行します。

import os
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import pickle

SCOPES = ["https://www.googleapis.com/auth/youtube.upload"]

def get_youtube_client():
    """OAuth2認証でYouTubeクライアントを取得"""
    creds = None

    if os.path.exists("token.pickle"):
        with open("token.pickle", "rb") as f:
            creds = pickle.load(f)

    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                os.getenv("YOUTUBE_CLIENT_SECRET_PATH"), SCOPES
            )
            creds = flow.run_local_server(port=0)
        with open("token.pickle", "wb") as f:
            pickle.dump(creds, f)

    return build("youtube", "v3", credentials=creds)

def upload_video(video_path: str, script_data: dict):
    """動画をYouTubeにアップロード"""
    youtube = get_youtube_client()

    request = youtube.videos().insert(
        part="snippet,status",
        body={
            "snippet": {
                "title": script_data["title"],
                "description": script_data["description"],
                "tags": script_data.get("keywords", []),
                "categoryId": "22"  # People & Blogs
            },
            "status": {
                "privacyStatus": "public",
                "selfDeclaredMadeForKids": False
            }
        },
        media_body=MediaFileUpload(video_path, chunksize=-1, resumable=True)
    )

    response = request.execute()
    video_id = response["id"]
    print(f"投稿完了! https://youtube.com/shorts/{video_id}")
    return video_id

ステップ8:すべてを繋ぐ main.py と自動スケジューラ

main.py(全工程を順番に実行)

import argparse
import os
from datetime import datetime
from research import collect_trends
from analyze import analyze_trends
from script import generate_script
from audio import synthesize_voice
from video import fetch_background_videos, render_video
from upload import upload_video

def run(dry_run: bool = False):
    """メイン処理"""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    output_path = os.path.join("output", timestamp)
    os.makedirs(output_path, exist_ok=True)

    print("[1/6] トレンドリサーチ中...")
    trends = collect_trends()

    print("[2/6] Claude APIでテーマ分析中...")
    theme = analyze_trends(trends)
    print(f"  → 選定テーマ: {theme['theme']}")

    print("[3/6] 台本・字幕生成中...")
    script = generate_script(theme)

    print("[4/6] 音声合成中...")
    narration_path = os.path.join(output_path, "narration.wav")
    synthesize_voice(script["narration"], narration_path)

    print("[5/6] 背景動画取得・レンダリング中...")
    clips = fetch_background_videos(theme["keywords"][0], output_path)
    video_path = render_video(script, clips, output_path)

    if dry_run:
        print(f"[DRY RUN] 動画生成完了: {video_path}")
        return

    print("[6/6] YouTubeに投稿中...")
    upload_video(video_path, {**script, **theme})
    print("✅ 全工程完了!")

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--dry-run", action="store_true")
    args = parser.parse_args()
    run(dry_run=args.dry_run)

scheduler.py(毎日09:00に自動実行)

import schedule
import time
from main import run

schedule.every().day.at("09:00").do(run)

print("スケジューラ起動。毎日09:00に動画を生成・投稿します。")

while True:
    schedule.run_pending()
    time.sleep(60)

実行方法

# 事前にVOICEVOXを起動しておく!

# テスト実行(投稿せずに動画だけ生成)
python main.py --dry-run

# 本番実行(生成〜投稿まで)
python main.py

# 自動スケジュール(毎日09:00に実行)
python scheduler.py

コスト試算(月間)

項目 月30日の概算
Claude API(analyze + script) 約 $3〜6
VOICEVOX 無料
Pexels API 無料
YouTube Data API 無料(クォータ内)
合計 約 500〜900円/月

1日1本・月30本の動画を月1,000円以下で全自動化できます。


まとめ

本記事では以下の7つのステップでYouTube Shorts全自動投稿システムを構築する方法を解説しました。

  1. プロジェクトセットアップ(Python環境・APIキー設定)
  2. トレンドリサーチ(YouTube急上昇 + Google Trends)
  3. Claude APIでバズテーマを自動選定
  4. 台本・字幕タイムラインの自動生成
  5. VOICEVOXによる音声合成
  6. Pexels + Remotionで動画レンダリング
  7. YouTube Data APIで自動投稿

一度セットアップすれば、毎朝9時に自動でバズテーマを分析し、動画を生成して投稿し続けてくれます。月1,000円以下・作業時間ほぼゼロでYouTube Shortsを継続投稿できるのは大きな強みです。

まずは --dry-run モードで動画生成だけ試してみて、クオリティを確認してから本番運用に移行することをおすすめします。

ぜひ試してみてください!

コメント

タイトルとURLをコピーしました