【Mac】YouTube 動画のコメント欄を Python の MeCab で形態素解析にかける

せっかく YouTube Data API でコメントを取得できるので、コメント欄にどんな単語が出てくるのか見れる様にしたい。

今回はとりあえず Mac ローカル環境で触りだけやってみます。

ちなみに API キーの取得やライブラリのインストールがまだの場合は下記記事をどうぞ。

YouTube Data API でコメント情報を取得

まずはコメント情報を取得するところから。コメント関連では CommentsThreads と Comments の二つがあります。

CommentThreads

CommentThreads では動画 ID やチャンネル ID をもとにコメントを取得することができます。

例えば動画 ID 「fdsaZ8EMR2U」のコメント 5 件のデータを取る場合はこんな感じ。

# -*- coding: utf-8 -*-

# Sample Python code for youtube.commentThreads.list
# See instructions for running these code samples locally:
# https://developers.google.com/explorer-help/guides/code_samples#python

import os

import googleapiclient.discovery

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "YOUR_API_KEY"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    request = youtube.commentThreads().list(
        part="id,replies,snippet",
        maxResults=5,
        videoId="fdsaZ8EMR2U"
    )
    response = request.execute()

    print(response)

if __name__ == "__main__":
    main()

下記の様な JSON が返ってきます。コメントの内容、投稿主のチャンネル名などが含まれています。そして「replies」にはコメントに対する返信も含まれます。

{
  "kind": "youtube#commentThreadListResponse",
  "etag": "tSw5WSiFS4IMMytcgoYXJ9zpu6I",
  "nextPageToken": "QURTSl9pMDFKVnZtTFl0NFZOdnhaZFpXaFBOcWU5aDA0QWM5bDVpYk5oVTd1WDQwSDY1cU11OVBOZHNnWFNOTmNJby1Db1JpWno2Qnd5bw==",
  "pageInfo": {
    "totalResults": 5,
    "resultsPerPage": 5
  },
  "items": [
    {
      "kind": "youtube#commentThread",
      "etag": "An3zz04lgE7jUVO7VXXmqfdwFjk",
      "id": "UgwDY44NUll4uiZXqqx4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "Kty1w2F4dTbXmakl-ywdK28vLEg",
          "id": "UgwDY44NUll4uiZXqqx4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "SO gooood",
            "textOriginal": "SO gooood",
            "authorDisplayName": "fatt musiek",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwngpQ-20jVq0c-9aC-wDJ87aTKi2QvPLTRN2GXGRaw=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCl3ha3zwY9p6CemIZZXIdXQ",
            "authorChannelId": {
              "value": "UCl3ha3zwY9p6CemIZZXIdXQ"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-22T18:48:34Z",
            "updatedAt": "2021-05-22T18:48:34Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 0,
        "isPublic": true
      }
    },
    {
      "kind": "youtube#commentThread",
      "etag": "QVJH5RHTNij1fN5jRj_mNcDscHA",
      "id": "Ugx8sUuwqKqPVG9eSuJ4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "Y_SsBrGGxQoLpcztQqND9wGarUc",
          "id": "Ugx8sUuwqKqPVG9eSuJ4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "so what if really yuffie have met johnny hehe",
            "textOriginal": "so what if really yuffie have met johnny hehe",
            "authorDisplayName": "GregOrio Barachina",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnjgJE6zBYksYQWt8TmKlMDYOyG0t-BHPNWWmvUUPQ=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCUs2OJ4-KqYGS2EPJCDj7tQ",
            "authorChannelId": {
              "value": "UCUs2OJ4-KqYGS2EPJCDj7tQ"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-21T13:42:45Z",
            "updatedAt": "2021-05-21T13:42:45Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 0,
        "isPublic": true
      }
    },
    {
      "kind": "youtube#commentThread",
      "etag": "ggLtp9jtNyrqb3JSvzkvUDon7gg",
      "id": "UgwP-4ucsrWh_iXJQMN4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "Raxyf3_Zw3ksZyGeWDt7_HHW8SA",
          "id": "UgwP-4ucsrWh_iXJQMN4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "The Aerith and Cloud scene is much more meaningful than the one with Tifa. Still a good scene but come on...Aerith just appearing amongst the flowers and getting to see her again...priceless",
            "textOriginal": "The Aerith and Cloud scene is much more meaningful than the one with Tifa. Still a good scene but come on...Aerith just appearing amongst the flowers and getting to see her again...priceless",
            "authorDisplayName": "Maxx Doran",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnixfMDBxLt_TfUEjlpHhU-OvwE1vjCgpFBAVIMxjg=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCXcLTX_9fNHLVAMr_plxeqQ",
            "authorChannelId": {
              "value": "UCXcLTX_9fNHLVAMr_plxeqQ"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-18T01:50:04Z",
            "updatedAt": "2021-05-18T01:50:04Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 0,
        "isPublic": true
      }
    },
    {
      "kind": "youtube#commentThread",
      "etag": "ptUVfOGBkZDnUXKFoDaGnQ9Y-gw",
      "id": "UgzTN_4ek7syWNNbCrB4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "L3vDroBKOAklgIqKSkcX_JvLn_g",
          "id": "UgzTN_4ek7syWNNbCrB4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "You caught on to the magnify barrier idea so early. I was part way into hard mode before I thought of that.",
            "textOriginal": "You caught on to the magnify barrier idea so early. I was part way into hard mode before I thought of that.",
            "authorDisplayName": "Justin Edwards",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwngz1mU5zD3QHSRVU3jXTEZApnkYsmAzCKFXxUyD1w=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCO-oPQJCpNw87M6YbcuuFMw",
            "authorChannelId": {
              "value": "UCO-oPQJCpNw87M6YbcuuFMw"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-10T07:25:36Z",
            "updatedAt": "2021-05-10T07:25:36Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 0,
        "isPublic": true
      }
    },
    {
      "kind": "youtube#commentThread",
      "etag": "vfaqu09YbjpC_akz6riq0_XpSCw",
      "id": "UgygOOysmSAraKnx81h4AaABAg",
      "snippet": {
        "videoId": "fdsaZ8EMR2U",
        "topLevelComment": {
          "kind": "youtube#comment",
          "etag": "k8vIS0anrGkqCcFfyj0gnrUwXQI",
          "id": "UgygOOysmSAraKnx81h4AaABAg",
          "snippet": {
            "videoId": "fdsaZ8EMR2U",
            "textDisplay": "Y'know Max, you COULD have just run 5k steps in Aerith's garden, checked what the Materia did, and moved on. Or, maybe, look up an online guide, since by now I'm sure SOMEONE has posted one.",
            "textOriginal": "Y'know Max, you COULD have just run 5k steps in Aerith's garden, checked what the Materia did, and moved on. Or, maybe, look up an online guide, since by now I'm sure SOMEONE has posted one.",
            "authorDisplayName": "Soma Cruz the Demigod of Balance",
            "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnilg2dkOBvJqeTbW34CBoxURHLWv78fnbCRkArv=s48-c-k-c0x00ffffff-no-rj",
            "authorChannelUrl": "http://www.youtube.com/channel/UCNaiemmWvNbzfaQm5e3hyqA",
            "authorChannelId": {
              "value": "UCNaiemmWvNbzfaQm5e3hyqA"
            },
            "canRate": true,
            "viewerRating": "none",
            "likeCount": 0,
            "publishedAt": "2021-05-07T02:21:29Z",
            "updatedAt": "2021-05-07T02:21:29Z"
          }
        },
        "canReply": true,
        "totalReplyCount": 1,
        "isPublic": true
      },
      "replies": {
        "comments": [
          {
            "kind": "youtube#comment",
            "etag": "oMSJ1drDJreTmguX72NWydzfbcY",
            "id": "UgygOOysmSAraKnx81h4AaABAg.9N10GKEX1209N2h_vQGc9Y",
            "snippet": {
              "videoId": "fdsaZ8EMR2U",
              "textDisplay": "\u003ca href=\"https://www.youtube.com/watch?v=fdsaZ8EMR2U&t=38m03s\"\u003e38:03\u003c/a\u003e These things don't stagger? But each time they clone, they lose health, and the clones are much weaker. Damn, I see why you had trouble with these, Max.",
              "textOriginal": "38:03 These things don't stagger? But each time they clone, they lose health, and the clones are much weaker. Damn, I see why you had trouble with these, Max.",
              "parentId": "UgygOOysmSAraKnx81h4AaABAg",
              "authorDisplayName": "Soma Cruz the Demigod of Balance",
              "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnilg2dkOBvJqeTbW34CBoxURHLWv78fnbCRkArv=s48-c-k-c0x00ffffff-no-rj",
              "authorChannelUrl": "http://www.youtube.com/channel/UCNaiemmWvNbzfaQm5e3hyqA",
              "authorChannelId": {
                "value": "UCNaiemmWvNbzfaQm5e3hyqA"
              },
              "canRate": true,
              "viewerRating": "none",
              "likeCount": 0,
              "publishedAt": "2021-05-07T18:08:01Z",
              "updatedAt": "2021-05-07T18:08:01Z"
            }
          }
        ]
      }
    }
  ]
}

Comments

Comments ではコメント ID を直接指定してデータを取得します。上の例で取得した 5 つのコメント ID を「id」に指定してデータを取得してみます。

# -*- coding: utf-8 -*-

# Sample Python code for youtube.comments.list
# See instructions for running these code samples locally:
# https://developers.google.com/explorer-help/guides/code_samples#python

import os

import googleapiclient.discovery

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "YOUR_API_KEY"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    request = youtube.comments().list(
        part="id,snippet",
        id="UgwDY44NUll4uiZXqqx4AaABAg,Ugx8sUuwqKqPVG9eSuJ4AaABAg,UgwP-4ucsrWh_iXJQMN4AaABAg,UgzTN_4ek7syWNNbCrB4AaABAg,UgygOOysmSAraKnx81h4AaABAg"
    )
    response = request.execute()

    print(response)

if __name__ == "__main__":
    main()

下記の様な JSON が返ってきます。CommentThreads の方ではコメントが投稿された動画の ID や、コメントに対する返信も含まれていましたが、Comments の方には含まれません。

{
  "kind": "youtube#commentListResponse",
  "etag": "vwaB3KAa_Snb_GuTkkMYrlL7Jrg",
  "items": [
    {
      "kind": "youtube#comment",
      "etag": "E9ovRZPTGOUQzHb0AEiKA26EJxY",
      "id": "UgwDY44NUll4uiZXqqx4AaABAg",
      "snippet": {
        "textDisplay": "SO gooood",
        "textOriginal": "SO gooood",
        "authorDisplayName": "fatt musiek",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwngpQ-20jVq0c-9aC-wDJ87aTKi2QvPLTRN2GXGRaw=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCl3ha3zwY9p6CemIZZXIdXQ",
        "authorChannelId": {
          "value": "UCl3ha3zwY9p6CemIZZXIdXQ"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-22T18:48:34Z",
        "updatedAt": "2021-05-22T18:48:34Z"
      }
    },
    {
      "kind": "youtube#comment",
      "etag": "FG5oWvmMF39kDNl_rlnzb0bsSWM",
      "id": "Ugx8sUuwqKqPVG9eSuJ4AaABAg",
      "snippet": {
        "textDisplay": "so what if really yuffie have met johnny hehe",
        "textOriginal": "so what if really yuffie have met johnny hehe",
        "authorDisplayName": "GregOrio Barachina",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnjgJE6zBYksYQWt8TmKlMDYOyG0t-BHPNWWmvUUPQ=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCUs2OJ4-KqYGS2EPJCDj7tQ",
        "authorChannelId": {
          "value": "UCUs2OJ4-KqYGS2EPJCDj7tQ"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-21T13:42:45Z",
        "updatedAt": "2021-05-21T13:42:45Z"
      }
    },
    {
      "kind": "youtube#comment",
      "etag": "WQw2UyILXAhkYOAl-AKScZi1pCY",
      "id": "UgwP-4ucsrWh_iXJQMN4AaABAg",
      "snippet": {
        "textDisplay": "The Aerith and Cloud scene is much more meaningful than the one with Tifa. Still a good scene but come on...Aerith just appearing amongst the flowers and getting to see her again...priceless",
        "textOriginal": "The Aerith and Cloud scene is much more meaningful than the one with Tifa. Still a good scene but come on...Aerith just appearing amongst the flowers and getting to see her again...priceless",
        "authorDisplayName": "Maxx Doran",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnixfMDBxLt_TfUEjlpHhU-OvwE1vjCgpFBAVIMxjg=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCXcLTX_9fNHLVAMr_plxeqQ",
        "authorChannelId": {
          "value": "UCXcLTX_9fNHLVAMr_plxeqQ"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-18T01:50:04Z",
        "updatedAt": "2021-05-18T01:50:04Z"
      }
    },
    {
      "kind": "youtube#comment",
      "etag": "Vchl7kutnRgZb-uKYjMNMrJQ2qQ",
      "id": "UgzTN_4ek7syWNNbCrB4AaABAg",
      "snippet": {
        "textDisplay": "You caught on to the magnify barrier idea so early. I was part way into hard mode before I thought of that.",
        "textOriginal": "You caught on to the magnify barrier idea so early. I was part way into hard mode before I thought of that.",
        "authorDisplayName": "Justin Edwards",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwngz1mU5zD3QHSRVU3jXTEZApnkYsmAzCKFXxUyD1w=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCO-oPQJCpNw87M6YbcuuFMw",
        "authorChannelId": {
          "value": "UCO-oPQJCpNw87M6YbcuuFMw"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-10T07:25:36Z",
        "updatedAt": "2021-05-10T07:25:36Z"
      }
    },
    {
      "kind": "youtube#comment",
      "etag": "pbfhdpIgB5QCKnr4Inkm_U2wbjQ",
      "id": "UgygOOysmSAraKnx81h4AaABAg",
      "snippet": {
        "textDisplay": "Y'know Max, you COULD have just run 5k steps in Aerith's garden, checked what the Materia did, and moved on. Or, maybe, look up an online guide, since by now I'm sure SOMEONE has posted one.",
        "textOriginal": "Y'know Max, you COULD have just run 5k steps in Aerith's garden, checked what the Materia did, and moved on. Or, maybe, look up an online guide, since by now I'm sure SOMEONE has posted one.",
        "authorDisplayName": "Soma Cruz the Demigod of Balance",
        "authorProfileImageUrl": "https://yt3.ggpht.com/ytc/AAUvwnilg2dkOBvJqeTbW34CBoxURHLWv78fnbCRkArv=s48-c-k-c0x00ffffff-no-rj",
        "authorChannelUrl": "http://www.youtube.com/channel/UCNaiemmWvNbzfaQm5e3hyqA",
        "authorChannelId": {
          "value": "UCNaiemmWvNbzfaQm5e3hyqA"
        },
        "canRate": true,
        "viewerRating": "none",
        "likeCount": 0,
        "publishedAt": "2021-05-07T02:21:29Z",
        "updatedAt": "2021-05-07T02:21:29Z"
      }
    }
  ]
}

YouTube 動画のコメントを MeCab で処理する

基本編:単一コメント取得〜形態素解析

まず仮想環境で下記を実行し、MeCab を使える様準備します。

$ brew install mecab
$ brew install mecab-ipadic
$ pip install mecab-python3

上記が済んだら下記の様に

import os
import googleapiclient.discovery
import MeCab

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "YOUR_API_KEY"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    # コメント ID を指定してコメントを取得
    request = youtube.comments().list(
        part="id,snippet",
        id="UgzKZhtc3fX6JgX6p5p4AaABAg" # コメント ID 
    )
    response = request.execute()

    # 返された JSON からコメントの文章を取得し text に保存
    text = response['items'][0]['snippet']['textOriginal']
    
    m = MeCab.Tagger()
 
    node = m.parseToNode(text)
    text_after = []
    while node:
        words.append(node.surface)
        node = node.next

    print('処理前: '+str(text)) # MeCab 処理前の text
    print('処理後: '+str(text_after)) # MeCab 処理を行なった後の text
    
if __name__ == "__main__":
    main()

狩野英孝さんの動画のコメントで、上記のコードを実行すると下記の結果を返してくれます。

処理前: ここ最近英孝ちゃんの動画みてたらいつの間にか日付け変わってるんだけど笑
処理前: ['', 'ここ', '最近', '英孝', 'ちゃん', 'の', '動画', 'み', 'て', 'たら', 'いつの間にか', '日', '付け', '変わっ', 'てる', 'ん', 'だ', 'けど', '笑', '']

応用編:複数コメント取得〜頻出単語の表出

YouTube Data API でコメントスレッドを取得し、それを MeCab の処理に欠けて出現数の多い単語を抽出してみます。

import os
import googleapiclient.discovery
import MeCab
import collections

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "YOUR_API_KEY"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    # コメントスレッドの取得
    request = youtube.commentThreads().list(
        part="id,replies,snippet",
        maxResults=100, # 最大取得コメントスレッド数
        videoId="jsRR_ZimvAo", # 動画 ID
        order="relevance" # 関連性の高い順にコメントスレッドを取得
    )
    response = request.execute()

    comment_list = []
    for item in response['items']:

        comment_list.append(item['snippet']['topLevelComment']['snippet']['textOriginal'])
        if 'replies' in item.keys():
            for reply in item['replies']['comments']:

                comment_list.append(reply['snippet']['textOriginal'])
    
    # MeCab の処理
    comment_particles = []
    for comment in comment_list:
        m = MeCab.Tagger()
    
        node = m.parseToNode(comment)
        while node:
            if len(node.surface) > 0: # ''は処理から除外
                hinshi = node.feature.split(',')[0]
                if hinshi in ['名詞','形容詞']: # 名詞か形容詞に絞る
                    comment_particles.append(node.surface)
            
            node = node.next

    c = collections.Counter(comment_particles)
    
    # 出現数順に print
    for i in c.most_common(30):
        print(i)


if __name__ == "__main__":
    main()

お笑い芸人のさまぁ〜ずとぺこぱの動画なので下記の様な感じでタプルが返ってきます。

('シュウ', 23)
('ペイ', 23)
('さん', 21)
('笑', 17)
('好き', 11)
('ちゃん', 10)
('シュウペイ', 9)
('の', 9)
('ん', 9)
('ぺこぱ', 9)
('企画', 9)
('お前', 9)
('ツッコミ', 8)
('~', 8)
('寺', 7)
('かわいい', 7)
('最高', 7)
('松陰', 6)
('ショック', 6)
('回', 6)
('純粋', 6)
('人', 6)
('俺', 6)
('いい', 5)
('www', 5)
('w', 5)
('面白い', 5)
('ww', 5)
('❤', 5)
('ずさん', 4)

不自然なところで単語が切れてしまったりするのが気になりますが、とりあえずこんな風なことができるということで!

Python で YouTube Data API v3 を利用してチャンネル・動画情報を抽出する方法

  1. API キーの作成
  2. ライブラリのインストール
  3. API リクエストを試してみる
  4. 抽出できる情報

1. API キーの作成

「認証情報」のタブを開きます。

「+認証情報を作成」をクリックし「API キー」を選択。

黒く塗りつぶしてますが、ここに API キーが表示されます。

2. ライブラリのインストール

仮想環境内で Google APIs Client Library for Python をインストールします。

コマンド「pip install --upgrade google-api-python-client」を実行します。

(sample_venv) % pip install --upgrade google-api-python-client
Collecting google-api-python-client
  Downloading https://files.pythonhosted.org/packages/b7/56/bc7314cd180d1420e4ef11202dc9548ec22237a0a6de1aaf37b460ee7753/google_api_python_client-2.0.2-py2.py3-none-any.whl (6.5MB)
     |████████████████████████████████| 6.5MB 9.0MB/s 
Collecting google-auth<2dev,>=1.16.0 (from google-api-python-client)
  Downloading https://files.pythonhosted.org/packages/b5/ef/13d3b65c41ddde1a57e89de6adfc610093d109ebd472545a33e76dcdd8bf/google_auth-1.28.0-py2.py3-none-any.whl (136kB)
     |████████████████████████████████| 143kB 10.8MB/s 
Collecting google-api-core<2dev,>=1.21.0 (from google-api-python-client)
  Downloading https://files.pythonhosted.org/packages/66/21/891884b4fe2eda194e5cdc9ea617b63ab41e0aca595c865492380e2c10b3/google_api_core-1.26.2-py2.py3-none-any.whl (93kB)
     |████████████████████████████████| 102kB 11.4MB/s 
Collecting httplib2<1dev,>=0.15.0 (from google-api-python-client)
  Downloading https://files.pythonhosted.org/packages/15/7e/51e5bd333c0afa1c7bdbf98eb3b0ccf5167e2b1ecc8b4d13e9cc29291f81/httplib2-0.19.0-py3-none-any.whl (95kB)
     |████████████████████████████████| 102kB 11.4MB/s 
Collecting uritemplate<4dev,>=3.0.0 (from google-api-python-client)
  Using cached https://files.pythonhosted.org/packages/bf/0c/60d82c077998feb631608dca3cc1fe19ac074e772bf0c24cf409b977b815/uritemplate-3.0.1-py2.py3-none-any.whl
Collecting google-auth-httplib2>=0.0.3 (from google-api-python-client)
  Downloading https://files.pythonhosted.org/packages/ba/db/721e2f3f32339080153995d16e46edc3a7657251f167ddcb9327e632783b/google_auth_httplib2-0.1.0-py2.py3-none-any.whl
Requirement already satisfied, skipping upgrade: six<2dev,>=1.13.0 in /Users/ユーザー名/PythonProjects/sample_venv/lib/python3.8/site-packages (from google-api-python-client) (1.15.0)
Collecting cachetools<5.0,>=2.0.0 (from google-auth<2dev,>=1.16.0->google-api-python-client)
  Downloading https://files.pythonhosted.org/packages/bb/72/8df2e0dc991f1a1d2c6869404e7622e8ee50d80bff357dbb57c3df70305b/cachetools-4.2.1-py3-none-any.whl
Collecting pyasn1-modules>=0.2.1 (from google-auth<2dev,>=1.16.0->google-api-python-client)
  Using cached https://files.pythonhosted.org/packages/95/de/214830a981892a3e286c3794f41ae67a4495df1108c3da8a9f62159b9a9d/pyasn1_modules-0.2.8-py2.py3-none-any.whl
Collecting rsa<5,>=3.1.4; python_version >= "3.6" (from google-auth<2dev,>=1.16.0->google-api-python-client)
  Downloading https://files.pythonhosted.org/packages/e9/93/0c0f002031f18b53af7a6166103c02b9c0667be528944137cc954ec921b3/rsa-4.7.2-py3-none-any.whl
Requirement already satisfied, skipping upgrade: setuptools>=40.3.0 in /Users/ユーザー名/PythonProjects/sample_venv/lib/python3.8/site-packages (from google-auth<2dev,>=1.16.0->google-api-python-client) (41.2.0)
Collecting packaging>=14.3 (from google-api-core<2dev,>=1.21.0->google-api-python-client)
  Downloading https://files.pythonhosted.org/packages/3e/89/7ea760b4daa42653ece2380531c90f64788d979110a2ab51049d92f408af/packaging-20.9-py2.py3-none-any.whl (40kB)
     |████████████████████████████████| 40kB 8.7MB/s 
Requirement already satisfied, skipping upgrade: requests<3.0.0dev,>=2.18.0 in /Users/ユーザー名/PythonProjects/sample_venv/lib/python3.8/site-packages (from google-api-core<2dev,>=1.21.0->google-api-python-client) (2.25.1)
Collecting googleapis-common-protos<2.0dev,>=1.6.0 (from google-api-core<2dev,>=1.21.0->google-api-python-client)
  Downloading https://files.pythonhosted.org/packages/55/08/796a6bc0b550e2b7116041c953d3d5100016abea106131d71e5651826e7b/googleapis_common_protos-1.53.0-py2.py3-none-any.whl (198kB)
     |████████████████████████████████| 204kB 11.0MB/s 
Collecting protobuf>=3.12.0 (from google-api-core<2dev,>=1.21.0->google-api-python-client)
  Downloading https://files.pythonhosted.org/packages/62/be/f610958bf64f2a64b6b0b3403d17564af53bbfad5dad69f69b164cca038d/protobuf-3.15.6-cp38-cp38-macosx_10_9_x86_64.whl (1.0MB)
     |████████████████████████████████| 1.0MB 10.8MB/s 
Requirement already satisfied, skipping upgrade: pytz in /Users/ユーザー名/PythonProjects/sample_venv/lib/python3.8/site-packages (from google-api-core<2dev,>=1.21.0->google-api-python-client) (2021.1)
Requirement already satisfied, skipping upgrade: pyparsing<3,>=2.4.2 in /Users/ユーザー名/PythonProjects/sample_venv/lib/python3.8/site-packages (from httplib2<1dev,>=0.15.0->google-api-python-client) (2.4.7)
Collecting pyasn1<0.5.0,>=0.4.6 (from pyasn1-modules>=0.2.1->google-auth<2dev,>=1.16.0->google-api-python-client)
  Using cached https://files.pythonhosted.org/packages/62/1e/a94a8d635fa3ce4cfc7f506003548d0a2447ae76fd5ca53932970fe3053f/pyasn1-0.4.8-py2.py3-none-any.whl
Requirement already satisfied, skipping upgrade: certifi>=2017.4.17 in /Users/ユーザー名/PythonProjects/sample_venv/lib/python3.8/site-packages (from requests<3.0.0dev,>=2.18.0->google-api-core<2dev,>=1.21.0->google-api-python-client) (2020.12.5)
Requirement already satisfied, skipping upgrade: chardet<5,>=3.0.2 in /Users/ユーザー名/PythonProjects/sample_venv/lib/python3.8/site-packages (from requests<3.0.0dev,>=2.18.0->google-api-core<2dev,>=1.21.0->google-api-python-client) (4.0.0)
Requirement already satisfied, skipping upgrade: urllib3<1.27,>=1.21.1 in /Users/ユーザー名/PythonProjects/sample_venv/lib/python3.8/site-packages (from requests<3.0.0dev,>=2.18.0->google-api-core<2dev,>=1.21.0->google-api-python-client) (1.26.4)
Requirement already satisfied, skipping upgrade: idna<3,>=2.5 in /Users/ユーザー名/PythonProjects/sample_venv/lib/python3.8/site-packages (from requests<3.0.0dev,>=2.18.0->google-api-core<2dev,>=1.21.0->google-api-python-client) (2.10)
Installing collected packages: cachetools, pyasn1, pyasn1-modules, rsa, google-auth, packaging, protobuf, googleapis-common-protos, google-api-core, httplib2, uritemplate, google-auth-httplib2, google-api-python-client
Successfully installed cachetools-4.2.1 google-api-core-1.26.2 google-api-python-client-2.0.2 google-auth-1.28.0 google-auth-httplib2-0.1.0 googleapis-common-protos-1.53.0 httplib2-0.19.0 packaging-20.9 protobuf-3.15.6 pyasn1-0.4.8 pyasn1-modules-0.2.8 rsa-4.7.2 uritemplate-3.0.1
(sample_venv) %

3. API リクエストを試してみる

単純にブラウザの URL バーから

まずは Python 関係なく、単純にブラウザの URL バーに下記の URL を入れてみます。(APIキーはご自分のを入れてください)

https://youtube.googleapis.com/youtube/v3/channels?part=snippet%2CcontentDetails%2Cstatistics&id=UC_x5XG1OV2P6uZZ5FSM9Ttw&key=APIキー

すると下記の JSON テキストが表示されました。ID「UC_x5XG1OV2P6uZZ5FSM9Ttw」の Google Developers チャンネルの情報が取れました。

Python での実行

同じことを Python でやろうとすると下記の様なコードになるそうです。(Google のサイト参照

import os

import googleapiclient.discovery

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "API キー"

    youtube = googleapiclient.discovery.build(api_service_name, api_version, developerKey = DEVELOPER_KEY)

    request = youtube.channels().list( # channels().list
        part="snippet,contentDetails,statistics", # 取得したい情報の指定
        id="UC_x5XG1OV2P6uZZ5FSM9Ttw" # チャンネル ID を指定(複数も可)
    )
    response = request.execute()

    print(response)

if __name__ == "__main__":
    main()

「part」に渡しているのが取得するデータの種類です。この記事の最後に取得できる part のリストを載せておきます。

動画情報の場合は下記の通り。個人的に好きで見てた FF 7 リメイクのプレイ動画です。 動画ID「fdsaZ8EMR2U」で指定しています。

import os

import googleapiclient.discovery

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "API キー"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    request = youtube.videos().list( # videos().list
        part="contentDetails", # 取得したい情報の指定
        id="fdsaZ8EMR2U" # 動画 ID を指定(複数も可)
    )
    response = request.execute()

    print(response)

if __name__ == "__main__":
    main()

動画情報の part の内下記は投稿主のみアクセスできるそうです。

  • fileDetails
  • processingDetails
  • suggestions

そして急上昇ランキングを取る場合はこう。動画 ID の指定は消して「chart="mostPopular"」、「regionCode="JP"」、「maxResults=50」 を追記します。

JP(日本)の mostPopular(急上昇ランキング)から 50 件のデータを取るという意味です。

import os

import googleapiclient.discovery

def main():
    # Disable OAuthlib's HTTPS verification when running locally.
    # *DO NOT* leave this option enabled in production.
    os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"

    api_service_name = "youtube"
    api_version = "v3"
    DEVELOPER_KEY = "API キー"

    youtube = googleapiclient.discovery.build(
        api_service_name, api_version, developerKey = DEVELOPER_KEY)

    request = youtube.videos().list(
        part="snippet,contentDetails,statistics", # 取得したい情報の指定
        chart="mostPopular", # chart に「mostPopular」を指定
        maxResults=50, # 最大件数
        regionCode="JP" # 地域コード
    )
    response = request.execute()

    print(response)

if __name__ == "__main__":
    main()

実行すると下記の様な内容の JSON テキストが取れるはずです。(実際は整形されていない状態)

{
  "kind": "youtube#videoListResponse",
  "etag": "jUM1iWDvm5ncH7VdFCrks9TZYQY",
  "items": [
    {
      "kind": "youtube#video",
      "etag": "jtNs4gdyS4wCzxvZKpEA0GbXJ0M",
      "id": "GOBeIaj7Ml0",
      "snippet": {
        "publishedAt": "2021-03-27T11:00:43Z",
        "channelId": "UCpOjLndjOqMoffA-fr8cbKA",
        "title": "【コムドット】生意気な後輩YouTuberと学校貸し切って本気でドロケイした結果【アバ溜りドット】",
        "description": "まだまだ道は開けない!!\n\nアバンティーズ\nhttps://www.youtube.com/user/avntisdouga\n\nコムドット \nhttps://www.youtube.com/channel/UCRxPrFmRHsXGWfAyE6oqrPQ\n\nフォーエイト とのドロケイ動画はこちらです!\nhttps://youtu.be/cbrQUkl6iD8\n\n動画がいいなと思っていただけたら高評価押してください!再生数よりもこちらを参考にして動画を作りたい!!そして少しでも面白いなと思っていただけたらぜひ【チャンネル登録】お願いします!\n\n水溜りボンドLINE公式アカウント!\nhttps://lin.ee/cVkWzGW\n最新情報不定期配信中!僕らからのメッセージも届きますよ!\nぜひ友だち登録してください!\n\nしゃべる!水溜りボンドLINEスタンプ\nhttps://00m.in/AcJcI\n水溜りボンドLINEスタンプ\nhttps://00m.in/2mzGU\n水溜りボンドのグッズはこちら!\nhttps://muuu.com/users/c7bf52c2b9fb5c5a\nおすすめでございます!!\n\n6年間1度も休まず毎日投稿しておりました!これからも少しでもみなさんが楽しんでいただければ幸いです。\n\n\n\n☆水溜りボンドを初めて知った方に是非見ていただきたい動画はこちら!☆\nhttps://www.youtube.com/watch?v=BvRvt6Nxny8\n\n●水溜りボンドの日常!僕らのサブチャンネルです。毎日比較的ゆったりやってます〜\nhttps://www.youtube.com/channel/UCaTFX7ckdowpnwHWcJGH2KQ\n\n\n●水溜りボンドのオールナイトニッポン0の詳細はこちらから\nぜひフォローお願いいたします!!\nhttps://twitter.com/mizuann0\n●限定動画や専用の絵文字など。チャンネルメンバーシップはこちらから!\nhttps://www.youtube.com/channel/UCpOjLndjOqMoffA-fr8cbKA/join\n\n◆Twitter\nカンタのアカウント\nhttps://twitter.com/kantamizutamari\nトミーのアカウント\nhttps://twitter.com/miztamari_nikki\n\n■インスタグラム\nカンタのアカウント\nhttps://bit.ly/2UckzeP\nトミーのアカウント\nhttps://www.instagram.com/tommymizutamaribond/\nーーーーーー\nよく使用している友人のBGM作曲者さん↓\nhttps://twitter.com/gt_k_2014?lang=ja\n素材提供 Music-Note.jp(ミュージックノート)\n他の楽曲提供:Production Music by http://www.epidemicsound.com\n素材提供 PIXTA\n\n#水溜りボンド\n#ドロケイ\n#コムドット",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/GOBeIaj7Ml0/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/GOBeIaj7Ml0/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/GOBeIaj7Ml0/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/GOBeIaj7Ml0/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/GOBeIaj7Ml0/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "水溜りボンド",
        "tags": [
          "水溜りボンド",
          "都市伝説",
          "検証",
          "実験",
          "科学",
          "お笑い",
          "面白",
          "面白い",
          "企画",
          "楽しい",
          "ワクワク",
          "カンタ",
          "トミー"
        ],
        "categoryId": "24",
        "liveBroadcastContent": "none",
        "defaultLanguage": "ja",
        "localized": {
          "title": "【コムドット】生意気な後輩YouTuberと学校貸し切って本気でドロケイした結果【アバ溜りドット】",
          "description": "まだまだ道は開けない!!\n\nアバンティーズ\nhttps://www.youtube.com/user/avntisdouga\n\nコムドット \nhttps://www.youtube.com/channel/UCRxPrFmRHsXGWfAyE6oqrPQ\n\nフォーエイト とのドロケイ動画はこちらです!\nhttps://youtu.be/cbrQUkl6iD8\n\n動画がいいなと思っていただけたら高評価押してください!再生数よりもこちらを参考にして動画を作りたい!!そして少しでも面白いなと思っていただけたらぜひ【チャンネル登録】お願いします!\n\n水溜りボンドLINE公式アカウント!\nhttps://lin.ee/cVkWzGW\n最新情報不定期配信中!僕らからのメッセージも届きますよ!\nぜひ友だち登録してください!\n\nしゃべる!水溜りボンドLINEスタンプ\nhttps://00m.in/AcJcI\n水溜りボンドLINEスタンプ\nhttps://00m.in/2mzGU\n水溜りボンドのグッズはこちら!\nhttps://muuu.com/users/c7bf52c2b9fb5c5a\nおすすめでございます!!\n\n6年間1度も休まず毎日投稿しておりました!これからも少しでもみなさんが楽しんでいただければ幸いです。\n\n\n\n☆水溜りボンドを初めて知った方に是非見ていただきたい動画はこちら!☆\nhttps://www.youtube.com/watch?v=BvRvt6Nxny8\n\n●水溜りボンドの日常!僕らのサブチャンネルです。毎日比較的ゆったりやってます〜\nhttps://www.youtube.com/channel/UCaTFX7ckdowpnwHWcJGH2KQ\n\n\n●水溜りボンドのオールナイトニッポン0の詳細はこちらから\nぜひフォローお願いいたします!!\nhttps://twitter.com/mizuann0\n●限定動画や専用の絵文字など。チャンネルメンバーシップはこちらから!\nhttps://www.youtube.com/channel/UCpOjLndjOqMoffA-fr8cbKA/join\n\n◆Twitter\nカンタのアカウント\nhttps://twitter.com/kantamizutamari\nトミーのアカウント\nhttps://twitter.com/miztamari_nikki\n\n■インスタグラム\nカンタのアカウント\nhttps://bit.ly/2UckzeP\nトミーのアカウント\nhttps://www.instagram.com/tommymizutamaribond/\nーーーーーー\nよく使用している友人のBGM作曲者さん↓\nhttps://twitter.com/gt_k_2014?lang=ja\n素材提供 Music-Note.jp(ミュージックノート)\n他の楽曲提供:Production Music by http://www.epidemicsound.com\n素材提供 PIXTA\n\n#水溜りボンド\n#ドロケイ\n#コムドット"
        },
        "defaultAudioLanguage": "ja"
      },
      "contentDetails": {
        "duration": "PT23M58S",
        "dimension": "2d",
        "definition": "hd",
        "caption": "false",
        "licensedContent": true,
        "contentRating": {},
        "projection": "rectangular"
      },
      "statistics": {
        "viewCount": "337770",
        "likeCount": "17557",
        "dislikeCount": "544",
        "favoriteCount": "0",
        "commentCount": "1650"
      }
    },
 

4. 抽出できる情報

確認できた範囲で、どの part にどんな項目が含まれているのか書き出しておきます。

チャンネル情報の part

part 名称配下の項目
auditDetails投稿主のみアクセス可
brandingSettingschannel, image
contentDetailsrelatedPlaylists
contentOwnerDetails
idチャンネル ID
localizations
snippettitle, description, customUrl, publishedAt, thumbnails, localized, country
statisticsviewCount, subscriberCount, hiddenSubscriberCount, videoCount
statusprivacyStatus, isLinked, longUploadsStatus, madeForKids
topicDetailstopicIds, topicCategories

動画情報の part

part 名称配下の項目
contentDetailsduration, dimension, definition, caption, licensedContent, contentRating, projection
fileDetails投稿主のみアクセス可
id動画 ID
liveStreamingDetailsactualStartTime, scheduledStartTime, concurrentViewers, activeLiveChatId
localizations
playerembedHtml
processingDetails投稿主のみアクセス可
recordingDetails
snippetpublishedAt, channelId, title, description, thumbnails, channelTitle, tags, categoryId, liveBroadcastContent, localized, defaultAudioLanguage
statisticsviewCount, likeCount, dislikeCount, favoriteCount, commentCount
statusuploadStatus, privacyStatus, license, embeddable, publicStatsViewable, madeForKids
suggestions投稿主のみアクセス可
topicDetailstopicCategories

チャンネル情報や動画情報をリクエストする際、一度に指定できる id の数は 50 までの様です。