📆

特定のチャンネルの配信予定日時を YouTube Data API から取得する

2021/12/30に公開約6,300字2件のコメント

要約

YouTube Data API から

  1. Search: list で ChannelId にもとづく動画一覧( videoId の一覧)を取得
  2. Videos: listid={先程の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 を使う

https://developers.google.com/youtube/v3/docs/search/list?hl=ja
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'を指定することで取得できる

https://developers.google.com/youtube/v3/docs/videos/list?hl=ja
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
  }
}

※この配信は既に終了したものなので、actualStartTimeactualEndTimeが存在する。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 をつかう方法を教えてもらったので追記

https://twitter.com/space_kedama/status/1476431895236059138

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通知で受け取る以下の方法を使うと良いです。

https://developers.google.com/youtube/v3/guides/push_notifications

取得出来る情報はRSSと同じなのでVideoAPIは叩く必要があります。

コメントありがとうございます

RSSの方に高頻度でリクエストを投げると429が返ってきます。複数のチャンネルの毎分チェック等を行うと起きます。

そこまで高頻度で使っていなかったのでエラーが返されたことがなかったのですが、レートリミットがあるのですね(まあ、ありそうですが。。)

多くのチャンネルの更新状況を取得したい場合は、RSSの更新をPush通知で受け取る以下の方法を使うと良いです。

こちら初耳でした。試してみたあと記事を更新しようと思います!

ログインするとコメントできます