特定のチャンネルの配信予定日時を YouTube Data API から取得する
要約
YouTube Data API から
- Search: list で ChannelId にもとづく動画一覧( videoId の一覧)を取得
- 
Videos: list にid={先程のvideoId}とpart='liveStreamingDetails'を指定してitems[].liveStreamingDetails.scheduledStartTimeの値を取得
取得したscheduledStartTimeが配信予定日時(UTC)となる
サンプルコード
from apiclient.discovery import build
from datetime import datetime, timedelta
import os
DEVELOPER_KEY = os.environ.get('YOUTUBE_API_KEY', '')
youtube = build('youtube', 'v3', developerKey=DEVELOPER_KEY)
def youtube_search(channel_id: str, max_results: int = 10) -> list:
  # Search: list で channel_id から検索する
  search_response = youtube.search().list(channelId=channel_id, part='id', order='date').execute()
  return search_response.get('items', [])
def youtube_video_details(video_id: str) -> list:
  # Videos: list で video_id から検索する
  video_response = youtube.videos().list(id=video_id, part='liveStreamingDetails').execute()
  return video_response.get('items', [])
if __name__ == '__main__':
  for item in youtube_search('specific-channel-id'):
    video_id = item['id']['videoId']
    details = youtube_video_detail(video_id)
    if len(details) == 0:
      continue
    scheduled_start_time = datetime.strptime(details[0]['liveStreamingDetails']['scheduledStartTime'], '%Y-%m-%dT%H:%M:%SZ')
    scheduled_start_time_jst = scheduled_start_time + timedelta(hours=9)  # 日本時間(JST)にする
    print(video_id, scheduled_start_time_jst)
詳細
事前準備
予め YouTube Data API の API Key を取得する(参考)
また、サンプルコードの Python 環境は以下の通り
[requires]
python_version = "3.9"
[packages]
google-api-python-client = "2.33.0"
チャンネルの動画一覧を取得
特定のチャンネルの動画一覧を取得するには Search: list を使う
def youtube_search(channel_id: str, max_results: int = 10) -> list:
  # Search: list で channel_id から検索する
  search_response = youtube.search().list(channelId=channel_id, part='id', order='date').execute()
  return search_response.get('items', [])
order='date'を指定することで、最新の動画順になる(配信も動画というオブジェクトとして取り扱われる)
API のレスポンスは以下のようになる
{
  "kind": "youtube#searchListResponse",
  "etag": "3LsmrmmY376AFR4PJuk5OkUofT0",
  "nextPageToken": "CAEQAA",
  "regionCode": "JP",
  "pageInfo": {
    "totalResults": 223,
    "resultsPerPage": 1
  },
  "items": [
    {
      "kind": "youtube#searchResult",
      "etag": "x9fp9u9w-o4tJvgo1cvh1zT9QIE",
      "id": {
        "kind": "youtube#video",
        "videoId": "whTWQw7RdTk"
      }
    },
    ...
  ]
}
なので、itemsの中身を for ループして videoId を取り出してゆく
  for item in youtube_search('specific-channel-id'):
    video_id = item['id']['videoId']
動画詳細情報から配信予定時刻を取得
配信予定時刻を取得するには Videos: list を使い、part='liveStreamingDetails'を指定することで取得できる
def youtube_video_details(video_id: str) -> list:
  # Videos: list で video_id から検索する
  video_response = youtube.videos().list(id=video_id, part='liveStreamingDetails').execute()
  return video_response.get('items', [])
API のレスポンスは以下のようになる
{
  "kind": "youtube#videoListResponse",
  "etag": "-cbm6QZrnDdr18MIaJEQ80xCzq8",
  "items": [
    {
      "kind": "youtube#video",
      "etag": "8xuNa38mAiZKoS_Wiq6eEYDNhqE",
      "id": "whTWQw7RdTk",
      "liveStreamingDetails": {
        "actualStartTime": "2021-12-28T14:30:21Z",
        "actualEndTime": "2021-12-28T15:38:22Z",
        "scheduledStartTime": "2021-12-28T14:30:00Z"
      }
    }
  ],
  "pageInfo": {
    "totalResults": 1,
    "resultsPerPage": 1
  }
}
※この配信は既に終了したものなので、actualStartTimeとactualEndTimeが存在する。scheduledStartTimeは配信前・配信後に存在する
また、これが普通の公開された動画の場合は、liveStreamingDetailsという属性が存在しないので注意(プレミアム公開は未検証)
”動画が配信である”という前提で、items[].liveStreamingDetails.scheduledStartTimeの値を取得して、datetimeオブジェクトへ変換する
    details = youtube_video_detail(video_id)
    if len(details) == 0:
      continue
    scheduled_start_time = datetime.strptime(details[0]['liveStreamingDetails']['scheduledStartTime'], '%Y-%m-%dT%H:%M:%SZ')
このときの時刻は UTC なので、 JST に変換する(実際にこの時刻を計算で取り扱いたい場合、タイムゾーンも指定したほうが良い)
    scheduled_start_time_jst = scheduled_start_time + timedelta(hours=9)  # 日本時間(JST)にする
こうして得られたscheduled_start_time_jstが配信予定時刻となる
その他
Quota の制限について
一般的なデベロッパーの上限が10000なので、1時間に1回で数チャンネルに対して実行すると、API の上限に達してしまう
自分の観測している範囲では
- Search: list -> 100 Queries
- Videos: list -> 1 Queries
といった感じ。検索にコストがかかるが、1回のリクエストで指定できる channelId は1つまでらしい
そのため、10チャンネルあると10回APIを叩くので、1度の実行で 1000 Queries 消費する
逆に Videos API の方はほとんどコストが掛からないので、n + 1 な呼出し方でもそこまで問題はない
Quota の節約について(まとめてAPIを叩く方法)
Videos: list は1回のリクエストに複数の videoId を指定できるので、Quota を節約することができる
def youtube_video_details(video_ids: list[str]) -> list:
  video_response = youtube.videos().list(id=','.join(video_ids), part='liveStreamingDetails').execute()
  return video_response.get('items', [])
video_idsに videoId のリストを入れて、リクエスト時に,区切りで送信する
そうすることで items に複数の動画の結果が含まれる
Search: list は channelId を複数指定できない(2021/12/30確認)
Quota の節約について(RSSを使う方法)
この記事の公開後に RSS をつかう方法を教えてもらったので追記
YouTube はチャンネル単位でも RSS を提供しているらしい
https://www.youtube.com/feeds/videos.xml?channel_id={channelId}
取得した RSS の一つのアイテムは以下の通り
<entry>
  <id>yt:video:whTWQw7RdTk</id>
  <yt:videoId>whTWQw7RdTk</yt:videoId>
  <yt:channelId>UC_T1YqknD5yrpVlupc-ZXzg</yt:channelId>
  <title>【ASMR】お耳のオイルマッサージ⯎大きく触るやついっぱい。近めの吐息とほんのりささやき。睡眠導入、作業用。Oil Ear Massage/Ear Blowing【#イル_フローラ/Vtuber】</title>
  <link rel="alternate" href="https://www.youtube.com/watch?v=whTWQw7RdTk"/>
  <author>
    <name>イル_フローラ</name>
    <uri>https://www.youtube.com/channel/UC_T1YqknD5yrpVlupc-ZXzg</uri>
  </author>
  <published>2021-12-28T15:42:45+00:00</published>
  <updated>2021-12-29T02:51:26+00:00</updated>
  <media:group>
    ...
  </media:group>
</entry>
RSS の一つのアイテムには配信予定時刻が含まれていないので、 Videos: list の API を叩いてデータを取得する必要がある


Discussion
さらに追加の情報ですが、RSSの方に高頻度でリクエストを投げると429が返ってきます。複数のチャンネルの毎分チェック等を行うと起きます。(YouTubeの機嫌が良い時は429が返ってこないこともあります)
多くのチャンネルの更新状況を取得したい場合は、RSSの更新をPush通知で受け取る以下の方法を使うと良いです。
取得出来る情報はRSSと同じなのでVideoAPIは叩く必要があります。
コメントありがとうございます
そこまで高頻度で使っていなかったのでエラーが返されたことがなかったのですが、レートリミットがあるのですね(まあ、ありそうですが。。)
こちら初耳でした。試してみたあと記事を更新しようと思います!