YouTube Data APIでコメントとチャンネル登録者を取得する

# やりたかったこと

特定のチャンネルにコメントを動画しているユーザ群について、それぞれのユーザがどのチャンネルを登録しているかを取得する。
それによって、客層の推定ができるのでは？という想定。同じチャンネルを登録しているユーザが複数いる場合、そのチャンネルのトピックスに関心がある人が客層になっている可能性がある。
（本当に客層の推定ができるのかは未検証なので、できたらまたどこかに投稿しようと思っています）

# 説明すること

1. 指定したチャンネルに投稿された動画につけられたコメントを取得する方法
1. 指定したチャンネルが登録しているチャンネルを取得する方法

# 以前説明したので、今回は説明しないこと

* APIの使い始めの登録部分
    * https://qiita.com/moshisora/items/4ea23d5abd7b4d852955 を参考にやりました
* あるチャンネルに投稿されている動画一覧の取得
    * 具体的には channels, playlistItems, videos APIの使い方
    * https://qiita.com/miyatsuki/items/c221b48830db2b0a9eba

# 説明

## 1. 指定したチャンネルに投稿された動画につけられたコメントを取得する方法

```python
import requests
import pandas as pd
from typing import Dict


def get_comment_info(channel_id: str, pageToken: str = "") -> Dict:
    comment_url = "https://www.googleapis.com/youtube/v3/commentThreads"
    param = {
        "key": "自分のBrowser Key",
        "allThreadsRelatedToChannelId": channel_id,
        "part": "replies, snippet",  # リプライツリーの取得が不要な場合はreplies不要
        "maxResults": "100",
    }

    if pageToken:
        param["pageToken"] = pageToken

    req = requests.get(comment_url, params=param)
    return req.json()


comments = []
channel_id = "取得したいチャンネルのID"

# 初回はpageToken渡したくないので空文字にしておく
pageToken = ""
while pageToken != None:
    req = get_comment_info(channel_id, pageToken)

    for comment_thread in req["items"]:
        snippet = comment_thread["snippet"]
        topLevelComment_snippet = snippet["topLevelComment"]["snippet"]

        video_id = snippet["videoId"]
        author_name = topLevelComment_snippet["authorDisplayName"]
        author_channel = topLevelComment_snippet["authorChannelId"]["value"]

        comments.append((video_id, author_name, author_channel))

        if "replies" in comment_thread and "comments" in comment_thread["replies"]:
            for replies in comment_thread["replies"]["comments"]:
                author_name = replies["snippet"]["authorDisplayName"]
                author_channel = replies["snippet"]["authorChannelId"]["value"]

                comments.append((video_id, author_name, author_channel))

    if "nextPageToken" in req:
        pageToken = req["nextPageToken"]
    else:
        pageToken = None


comment_df = pd.DataFrame(comments, columns=["video_id", "author_name", "channel_id"])
print(comment_df)
```

ポイントは下記の二つです

* リプライツリーの情報を取得するためには、snippet.topLevelComment.snippetだけでなく、snippet.replies.comments.snippetもみる必要がある
* ただし、上の二つは同じ構造のjsonになっているので同じような処理を通すだけで良い

また、上の例だとコメントした人のチャンネル名とchannel_idしか取得していませんが、コメントのテキストを取得する場合は、下記で取得可能です

* `comment_thread["snippet"]["topLevelComment"]["snippet"]["textDisplay"]`
* `replies["snippet"]["textDisplay"]`

詳細や少し違う使い方をしたい場合はこちらのドキュメントをご確認ください。例えば、今回はchannel_idを指定して、そのチャンネルに投稿された全ての動画に紐づくコメントを取得していますが、特定の動画だけを指定することも可能です。
https://developers.google.com/youtube/v3/docs/commentThreads?hl=ja


## 2. 指定したチャンネルが登録しているチャンネルを取得する方法

```python
def get_subscription_info(channel_id, pageToken):
    subscription_url = 'https://www.googleapis.com/youtube/v3/subscriptions'
    param = {
        'key': 【Browser Key】
        , 'channelId': channel_id
        , 'part': 'snippet'
        , 'maxResults': '50'
        , 'pageToken': pageToken
    }

    req = requests.get(subscription_url, params=param)
    return req.json()

subscription_df = pd.DataFrame([]*3, 
    columns=["channel_id", "subscript_channel_name", "subscript_channel_id"])

# pageTokenに空文字列を渡すと何も指定していないのと同じになる
# 1.で取得したコードを実行している場合は、channel_id_list = comment_df["channel_id"].unique() になる
for channel_id in channel_id_list:

    pageToken = ""
    while True:
        req = get_subscription_info(channel_id, pageToken)

        if "items" in req:
            for item in req["items"]:
                subscript_channel_name = item["snippet"]["title"]
                subscript_channel_id = item["snippet"]["resourceId"]["channelId"]
                subscription_df = subscription_df.append(
                    pd.DataFrame(
                        [[channel_id, subscript_channel_name, subscript_channel_id]]
                        , columns=["channel_id", "subscript_channel_name", "subscript_channel_id"]
                    )
                )


        # 残りのアイテム数がmaxResultsを超えている場合はnextPageTokenが帰ってくる
        if "nextPageToken" in req:
            pageToken = req["nextPageToken"]
            print(channel_id, pageToken)
        else:
            break
```

日本語訳がサブカテゴリとなっているSubscriptions APIを使います
https://developers.google.com/youtube/v3/docs/subscriptions?hl=ja

使い方としては、以前playlistIdからvideoIdを取得する方法を説明しましたが、それと近い雰囲気です。
https://qiita.com/miyatsuki/items/c221b48830db2b0a9eba#12-%E3%83%97%E3%83%AC%E3%82%A4%E3%83%AA%E3%82%B9%E3%83%88id%E3%81%8B%E3%82%89%E5%8B%95%E7%94%BB%E3%81%AEid%E3%82%92%E5%8F%96%E5%BE%97

具体的には、

* channelIdをパラメータとして渡すと、そのチャンネルが登録(subscript)しているチャンネルのリストが返ってくる。
* ただし、50件以上受け取る場合はpageTokenを使う必要がある。一つ前のリクエストで受け取ったpageTokenを渡すとその続きからデータが返ってくるので、クライアント側で連結して使用する
* 最初は空文字を渡す