YouTube Data API による動画一覧取得

5 min read

YouTube の、あるチャンネルの動画一覧を取得しよう、という時に、それ自体は、検索APIが提供されているので、可能ではあります。
各言語での例も提供されています。

https://developers.google.com/youtube/v3/docs/search/list?hl=ja#例

こういう構造のデータが取得できる。

https://developers.google.com/youtube/v3/docs/search/list?hl=ja#レスポンス

実際のデータ部はこの形。

https://developers.google.com/youtube/v3/docs/search?hl=ja#resource

Search API だと、duration が取れないのが残念。

ごく簡単な例としてはこんな感じになります。

response.py
from apiclient.discovery import build
import json

# API情報
API_KEY = 'xxx'
YOUTUBE_API_SERVICE_NAME = 'youtube'
YOUTUBE_API_VERSION = 'v3'

youtube = build(
    YOUTUBE_API_SERVICE_NAME, 
    YOUTUBE_API_VERSION,
    developerKey=API_KEY
    )

search_response = youtube.search().list(
  channelId='UC7ovu6a8ydIbDy0fAKmoZ9A',
  part='snippet',
  maxResults=50,
).execute()

with open("response.json", mode="w", encoding="utf-8") as f:
    json.dump(search_response, f, ensure_ascii=False, indent=2)

python3 response.py 

とやれば、response.json が出力されます。(謎のchannelId・・・)

ところが、みなさん、苦労されているわけです。

理由の一つは、maxResults の最大値が50であること。
すなわち、一回の検索で、50件しか取得できません。
これを超える動画を保有しているチャンネルの場合は、ループしながら取得する必要があります。
そのための仕組みとして、pageToken というものが提供されており、maxResults を超える結果がある場合は、nextPageToken が返却されるので、次の検索をする際に、pageToken として nextPageToken を渡してあげれば、次のデータを取得できます。

この処理を書かなければいけないということはありますが、それでも、そのような仕様が提示されているので、そこもまだ良いです。

問題は、この仕組みで検索していっても、取得件数が500件くらいになると、nextPageToken が返って来なくなります。これがつらい。

そこでこちらの方は、期間を絞って取得したら、もう少しなんとかなりますよ、ということを書いていただいています。

https://qiita.com/yuji_saito/items/8f472dcd785c1fadf666

500件未満になる程度の期間条件をつけて検索すれば、その期間分は取得できる。
また、期間を変えて検索すれば、また500件取得できる、という形です。

ということで、こんな感じで書いてみました。
こちらの例は、取得結果から、一部項目を取り出して、新しいjsonファイルを出力する、というものです。

from apiclient.discovery import build
import json
import datetime
from dateutil.relativedelta import relativedelta

# API情報
API_KEY = 'xxx'
YOUTUBE_API_SERVICE_NAME = 'youtube'
YOUTUBE_API_VERSION = 'v3'

videos = [] #videoURLを格納する配列

def youtube_search(pagetoken, st, ed):
  youtube = build(
      YOUTUBE_API_SERVICE_NAME, 
      YOUTUBE_API_VERSION,
      developerKey=API_KEY
      )

  search_response = youtube.search().list(
    channelId='UCbDEamSXQhxqhovhd2NdEyg',
    part='snippet',
    type='video',
    maxResults=50,
    publishedAfter=st, #'2013-01-01T00:00:00Z',
    publishedBefore=ed, #'2014-01-01T00:00:00Z',
    pageToken=pagetoken
  ).execute()

  print(search_response["pageInfo"]["totalResults"])
  for search_result in search_response.get("items", []):
    if search_result["id"]["kind"] == "youtube#video":
      d = {'title': search_result["snippet"]["title"],
        'description': search_result["snippet"]["description"], 
        'keywords': '',
        'source': 'junyiacademy',
        'inherentProperties' : {
            'id': search_result["id"]["videoId"],
            'url': 'https://www.youtube.com/watch?v=%s' % search_result["id"]["videoId"],
            'thumbnailUrl': search_result["snippet"]["thumbnails"]["default"]["url"],
            'publishedAt': search_result["snippet"]["publishedAt"],
        }
        }
    
      videos.append(d)
      # print(search_result["snippet"]["title"])

  try:
    nextPagetoken =  search_response["nextPageToken"] 
    # print(nextPagetoken)
    youtube_search(nextPagetoken, st, ed)
  except:
    return

dt = datetime.datetime(2016, 1, 1, 0, 0)
for i in range(1, 2):
  print(dt.isoformat())
  youtube_search('', dt.isoformat()+'Z', (dt + relativedelta(months=1)).isoformat()+'Z')
  dt = dt + relativedelta(months=1)

with open("videolist.json", mode="w", encoding="utf-8") as f:
    json.dump(videos, f, ensure_ascii=False, indent=2)

for i in range(1, 2) のところを変えてあげれば○ヶ月分のデータを取得できる、というものです。
1ヶ月では広かったり狭かったりしたら、+ relativedelta(months=1) の部分を変えれば良いわけです。

ところが、これで話は終わりませんでした。

YouTube Data API では 1 日あたりのクォータ量が定められています。

https://developers.google.com/youtube/v3/getting-started?hl=ja#quota

無料で使えるのは、Queries /日:10,000と書かれています。
※[IAM と管理] にある [割り当て] ページで、割り当て上限の増加のリクエストや他のサービスの割り当ての確認ができます。

ただ、この「Queries」というのは、youtube.search().list を投げられる数ということではなくて、取得する情報量等も考慮されたボリュームです。
上記プログラムで取得すると3600件ちょっと取得するのが1日の最大量のように感じられます。

これは逃れようがありません。
なので、これを超えないボリュームを見計らって、何ヶ月分取得するかを定める、その分取得したら、翌日まで待つ、ということになります。
しかし1日は長いな・・・

もう一点。
上記で、print(search_response["pageInfo"]["totalResults"]) ということをやっています。
この場合は、この1ヶ月分のデータ件数が取得できるはずなのですが、どうもこの出力と実際の出力件数が異なります。
・・・と思いましたが、type='video' を指定せずに、if search_result["id"]["kind"] == "youtube#video": という形で絞っていたからかも。
検索時点でtype を指定しておけば、取得情報ボリュームも抑えられることになるので、3600件よりもう少し取得できるかもしれないですね。