YouTube動画統計の取得とグラフ化

YouTube Data APIを使って動画データを取得し、視覚化する方法を解説します。

この記事で学べること

  • YouTube Data API v3の使い方
  • 動画統計データの取得方法
  • Matplotlibでのグラフ作成
  • チャンネル分析の自動化

1. 準備:必要なライブラリ

# インストール
pip install google-api-python-client
pip install matplotlib
pip install pandas
pip install python-dotenv

プロジェクト構成

youtube_scraper/
├── .env                 # API キー保存
├── scraper.py          # スクレイピングコード
├── visualizer.py       # グラフ作成
└── requirements.txt    # 依存関係

2. YouTube Data API キーの取得

Google Cloud Consoleでの設定

1. Google Cloud Console にアクセス
   https://console.cloud.google.com/

2. 新しいプロジェクトを作成
   「プロジェクトの選択」→「新しいプロジェクト」

3. YouTube Data API v3 を有効化
   「APIとサービス」→「ライブラリ」
   → 「YouTube Data API v3」を検索して有効化

4. 認証情報を作成
   「認証情報」→「認証情報を作成」→「APIキー」

5. APIキーをコピーして保存

.envファイルに保存

# .env
YOUTUBE_API_KEY=YOUR_API_KEY_HERE

3. 基本的な動画情報取得

scraper.py

#!/usr/bin/env python3
"""YouTube動画統計スクレイパー"""

import os
from googleapiclient.discovery import build
from dotenv import load_dotenv
import pandas as pd
from datetime import datetime

# 環境変数読み込み
load_dotenv()
API_KEY = os.getenv('YOUTUBE_API_KEY')

# YouTube APIクライアント
youtube = build('youtube', 'v3', developerKey=API_KEY)

def get_video_stats(video_id):
    """動画IDから統計情報を取得"""
    try:
        request = youtube.videos().list(
            part='snippet,statistics,contentDetails',
            id=video_id
        )
        response = request.execute()
        
        if not response['items']:
            print(f"動画が見つかりません: {video_id}")
            return None
        
        video = response['items'][0]
        snippet = video['snippet']
        stats = video['statistics']
        
        data = {
            'video_id': video_id,
            'title': snippet['title'],
            'channel': snippet['channelTitle'],
            'published_at': snippet['publishedAt'],
            'view_count': int(stats.get('viewCount', 0)),
            'like_count': int(stats.get('likeCount', 0)),
            'comment_count': int(stats.get('commentCount', 0)),
            'duration': video['contentDetails']['duration']
        }
        
        return data
        
    except Exception as e:
        print(f"エラー: {e}")
        return None

def extract_video_id(url):
    """YouTubeのURLから動画IDを抽出"""
    if 'youtu.be/' in url:
        return url.split('youtu.be/')[1].split('?')[0]
    elif 'watch?v=' in url:
        return url.split('watch?v=')[1].split('&')[0]
    else:
        return url  # すでにIDの場合

# 使用例
if __name__ == '__main__':
    # 動画URL(例)
    video_url = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
    video_id = extract_video_id(video_url)
    
    # 統計取得
    stats = get_video_stats(video_id)
    if stats:
        print(f"タイトル: {stats['title']}")
        print(f"再生数: {stats['view_count']:,}")
        print(f"高評価数: {stats['like_count']:,}")
        print(f"コメント数: {stats['comment_count']:,}")

実行結果

タイトル: Rick Astley - Never Gonna Give You Up
再生数: 1,450,000,000
高評価数: 15,000,000
コメント数: 2,500,000

4. 複数動画の統計比較

def get_multiple_videos_stats(video_ids):
    """複数動画の統計を一括取得"""
    all_stats = []
    
    for video_id in video_ids:
        stats = get_video_stats(video_id)
        if stats:
            all_stats.append(stats)
    
    # DataFrameに変換
    df = pd.DataFrame(all_stats)
    return df

# 使用例
video_ids = [
    'dQw4w9WgXcQ',  # Rick Astley
    'kJQP7kiw5Fk',  # Despacito
    '9bZkp7q19f0'   # Gangnam Style
]

df = get_multiple_videos_stats(video_ids)
print(df[['title', 'view_count', 'like_count']])

出力例

                                  title  view_count  like_count
0  Rick Astley - Never Gonna Give You Up  1450000000    15000000
1  Luis Fonsi - Despacito                8100000000    52000000
2  PSY - GANGNAM STYLE                   4800000000    24000000

5. チャンネルの最新動画を取得

def get_channel_videos(channel_id, max_results=10):
    """チャンネルの最新動画を取得"""
    try:
        # チャンネルのアップロード再生リストIDを取得
        request = youtube.channels().list(
            part='contentDetails',
            id=channel_id
        )
        response = request.execute()
        
        uploads_id = response['items'][0]['contentDetails'][
            'relatedPlaylists']['uploads']
        
        # 再生リストから動画を取得
        request = youtube.playlistItems().list(
            part='snippet',
            playlistId=uploads_id,
            maxResults=max_results
        )
        response = request.execute()
        
        video_ids = [
            item['snippet']['resourceId']['videoId']
            for item in response['items']
        ]
        
        return get_multiple_videos_stats(video_ids)
        
    except Exception as e:
        print(f"エラー: {e}")
        return None

# 使用例
channel_id = 'UC_x5XG1OV2P6uZZ5FSM9Ttw'  # Google Developers
df = get_channel_videos(channel_id, max_results=20)
print(df.head())

6. データの可視化

visualizer.py

#!/usr/bin/env python3
"""YouTube統計データの可視化"""

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
import pandas as pd
import japanize_matplotlib  # 日本語フォント対応

# 日本語フォント設定(Windows)
plt.rcParams['font.sans-serif'] = ['MS Gothic', 'Yu Gothic', 'Arial']
plt.rcParams['axes.unicode_minus'] = False

def plot_video_comparison(df):
    """複数動画の統計比較グラフ"""
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
    # タイトルを短縮
    df['short_title'] = df['title'].str[:20] + '...'
    
    # 再生数
    axes[0].barh(df['short_title'], df['view_count'], color='#FF0000')
    axes[0].set_xlabel('再生数', fontsize=12)
    axes[0].set_title('動画別再生数', fontsize=14, fontweight='bold')
    axes[0].ticklabel_format(style='plain', axis='x')
    
    # 高評価数
    axes[1].barh(df['short_title'], df['like_count'], color='#065FD4')
    axes[1].set_xlabel('高評価数', fontsize=12)
    axes[1].set_title('動画別高評価数', fontsize=14, fontweight='bold')
    axes[1].ticklabel_format(style='plain', axis='x')
    
    # コメント数
    axes[2].barh(df['short_title'], df['comment_count'], color='#F9AB00')
    axes[2].set_xlabel('コメント数', fontsize=12)
    axes[2].set_title('動画別コメント数', fontsize=14, fontweight='bold')
    axes[2].ticklabel_format(style='plain', axis='x')
    
    plt.tight_layout()
    plt.savefig('youtube_comparison.png', dpi=300, bbox_inches='tight')
    plt.show()

def plot_engagement_rate(df):
    """エンゲージメント率のグラフ"""
    # エンゲージメント率を計算
    df['engagement_rate'] = (
        (df['like_count'] + df['comment_count']) / df['view_count'] * 100
    )
    
    fig, ax = plt.subplots(figsize=(10, 6))
    
    bars = ax.bar(
        range(len(df)), 
        df['engagement_rate'],
        color=['#FF0000', '#065FD4', '#F9AB00', '#00D924']
    )
    
    ax.set_xlabel('動画', fontsize=12)
    ax.set_ylabel('エンゲージメント率 (%)', fontsize=12)
    ax.set_title('動画別エンゲージメント率', fontsize=14, fontweight='bold')
    ax.set_xticks(range(len(df)))
    ax.set_xticklabels(df['title'].str[:15], rotation=45, ha='right')
    
    # 値をバーの上に表示
    for bar in bars:
        height = bar.get_height()
        ax.text(
            bar.get_x() + bar.get_width()/2., height,
            f'{height:.2f}%',
            ha='center', va='bottom', fontsize=10
        )
    
    plt.tight_layout()
    plt.savefig('engagement_rate.png', dpi=300, bbox_inches='tight')
    plt.show()

def plot_time_series(df):
    """時系列グラフ(投稿日ごとの再生数)"""
    df['published_date'] = pd.to_datetime(df['published_at'])
    df = df.sort_values('published_date')
    
    fig, ax = plt.subplots(figsize=(12, 6))
    
    ax.plot(
        df['published_date'], 
        df['view_count'],
        marker='o',
        linewidth=2,
        markersize=8,
        color='#FF0000'
    )
    
    ax.set_xlabel('投稿日', fontsize=12)
    ax.set_ylabel('再生数', fontsize=12)
    ax.set_title('投稿日別再生数の推移', fontsize=14, fontweight='bold')
    ax.grid(True, alpha=0.3)
    
    # 日付フォーマット
    ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
    plt.xticks(rotation=45)
    
    plt.tight_layout()
    plt.savefig('time_series.png', dpi=300, bbox_inches='tight')
    plt.show()

グラフ作成実行

# データ取得
from scraper import get_multiple_videos_stats
from visualizer import plot_video_comparison, plot_engagement_rate

video_ids = ['dQw4w9WgXcQ', 'kJQP7kiw5Fk', '9bZkp7q19f0']
df = get_multiple_videos_stats(video_ids)

# グラフ作成
plot_video_comparison(df)
plot_engagement_rate(df)

7. 検索結果から動画を取得

def search_videos(query, max_results=10):
    """キーワード検索で動画を取得"""
    try:
        request = youtube.search().list(
            part='snippet',
            q=query,
            type='video',
            maxResults=max_results,
            order='viewCount'  # 再生数順
        )
        response = request.execute()
        
        video_ids = [
            item['id']['videoId']
            for item in response['items']
        ]
        
        return get_multiple_videos_stats(video_ids)
        
    except Exception as e:
        print(f"エラー: {e}")
        return None

# 使用例
df = search_videos('Python tutorial', max_results=20)
print(df[['title', 'view_count']].head(10))

8. トレンド動画の取得

def get_trending_videos(region='JP', max_results=10):
    """トレンド動画を取得"""
    try:
        request = youtube.videos().list(
            part='snippet,statistics',
            chart='mostPopular',
            regionCode=region,
            maxResults=max_results
        )
        response = request.execute()
        
        videos = []
        for item in response['items']:
            video_data = {
                'video_id': item['id'],
                'title': item['snippet']['title'],
                'channel': item['snippet']['channelTitle'],
                'view_count': int(item['statistics'].get('viewCount', 0)),
                'like_count': int(item['statistics'].get('likeCount', 0)),
                'comment_count': int(item['statistics'].get('commentCount', 0))
            }
            videos.append(video_data)
        
        return pd.DataFrame(videos)
        
    except Exception as e:
        print(f"エラー: {e}")
        return None

# 日本のトレンド取得
df_trending = get_trending_videos('JP', max_results=50)
plot_video_comparison(df_trending.head(10))

9. レート制限への対応

APIクォータ管理

import time
from functools import wraps

def rate_limit(calls_per_minute=60):
    """レート制限デコレータ"""
    min_interval = 60.0 / calls_per_minute
    last_called = [0.0]
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            elapsed = time.time() - last_called[0]
            wait_time = min_interval - elapsed
            
            if wait_time > 0:
                time.sleep(wait_time)
            
            result = func(*args, **kwargs)
            last_called[0] = time.time()
            return result
        
        return wrapper
    return decorator

@rate_limit(calls_per_minute=30)
def get_video_stats_limited(video_id):
    """レート制限付き統計取得"""
    return get_video_stats(video_id)

クォータ使用量の確認

YouTube Data API v3のクォータ:
- 1日のクォータ: 10,000ユニット
- videos().list: 1コール = 1ユニット
- search().list: 1コール = 100ユニット

節約のコツ:
✅ 必要なpartだけ指定(snippet, statisticsなど)
✅ キャッシュを活用
✅ バッチ処理で複数IDを一度に取得

10. 完全な実装例

#!/usr/bin/env python3
"""YouTubeチャンネル分析ツール"""

import os
from googleapiclient.discovery import build
from dotenv import load_dotenv
import pandas as pd
import matplotlib.pyplot as plt

load_dotenv()
API_KEY = os.getenv('YOUTUBE_API_KEY')
youtube = build('youtube', 'v3', developerKey=API_KEY)

def analyze_channel(channel_id, max_videos=50):
    """チャンネルの詳細分析"""
    print(f"チャンネル分析中... (最新{max_videos}本)")
    
    # チャンネル情報取得
    channel_data = youtube.channels().list(
        part='snippet,statistics',
        id=channel_id
    ).execute()
    
    channel_info = channel_data['items'][0]
    stats = channel_info['statistics']
    
    print(f"\nチャンネル名: {channel_info['snippet']['title']}")
    print(f"登録者数: {int(stats['subscriberCount']):,}")
    print(f"総視聴回数: {int(stats['viewCount']):,}")
    print(f"動画数: {stats['videoCount']}\n")
    
    # 動画データ取得
    df = get_channel_videos(channel_id, max_results=max_videos)
    
    if df is not None and len(df) > 0:
        # 統計サマリー
        print("=== 動画統計サマリー ===")
        print(f"平均再生数: {df['view_count'].mean():,.0f}")
        print(f"平均高評価数: {df['like_count'].mean():,.0f}")
        print(f"平均コメント数: {df['comment_count'].mean():,.0f}")
        print(f"最高再生数: {df['view_count'].max():,.0f}")
        
        # グラフ作成
        plot_video_comparison(df.head(10))
        plot_engagement_rate(df.head(10))
        
        # CSV保存
        output_file = f'channel_{channel_id}_analysis.csv'
        df.to_csv(output_file, index=False, encoding='utf-8-sig')
        print(f"\n結果を保存: {output_file}")
        
        return df
    else:
        print("動画データを取得できませんでした")
        return None

if __name__ == '__main__':
    # 分析したいチャンネルID
    channel_id = 'UC_x5XG1OV2P6uZZ5FSM9Ttw'  # Google Developers
    analyze_channel(channel_id, max_videos=30)

11. エラーハンドリング

from googleapiclient.errors import HttpError

def safe_api_call(func):
    """API呼び出しのエラーハンドリング"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except HttpError as e:
            if e.resp.status == 403:
                print("❌ クォータ超過またはAPI無効")
            elif e.resp.status == 404:
                print("❌ リソースが見つかりません")
            else:
                print(f"❌ APIエラー: {e}")
            return None
        except Exception as e:
            print(f"❌ 予期しないエラー: {e}")
            return None
    return wrapper

@safe_api_call
def get_video_stats_safe(video_id):
    return get_video_stats(video_id)

まとめ

YouTube Data APIで動画統計を取得・分析できます!

重要ポイント

  • ✅ Google Cloud ConsoleでAPI有効化必須
  • ✅ APIキーは.envファイルで管理
  • ✅ クォータ制限に注意(1日10,000ユニット)
  • ✅ Matplotlibで効果的に可視化
  • ✅ pandasでデータ分析を効率化

応用例

  • 競合チャンネルの分析
  • 動画パフォーマンス予測
  • 最適な投稿時間の分析
  • タイトル・タグの効果測定
  • トレンド追跡ダッシュボード

注意事項

  • ⚠️ 利用規約を遵守
  • ⚠️ 個人情報の取り扱いに注意
  • ⚠️ 商用利用は制限を確認
  • ⚠️ APIキーは公開しない