PythonでYouTube API を叩いてみる

>この記事は[**福島高専 Advent Calendar 2020**](https://qiita.com/advent-calendar/2020/nitfc) 25日目の記事です。
本記事はAPIについての記事です。内容を悪用するのは厳禁でお願いします。

# はじめに
昨今、APIの重要性について述べられている記事や動画などをちらほらと見かけるようになってきたので、自分もPythonの勉強がてら触ってみることにしました。まだまだ初学者なのでスマートなコードを書けませんが、よろしくお願いします。

#概要
取り敢えず、比較的メジャーだと思われる```YouTube Data API```を叩いてYouTubeチャンネルや動画のデータを取得して、解析してみたいと思います。下記の事前準備は既に済んでいるという前提です。

#事前準備
- YouTube Data API v3のAPIキーを取得しておく
- Pythonをインストールし開発環境を整える

#テスト実装
取り敢えず、APIの使い方を軽く理解するためにいくつかメソッドを利用してみます。因みにAPIのQuotas(1日の使用量)は10000が上限です。テストを繰り返していると案外あっさり到達する可能性があるので注意してください。
##必要なパッケージのインストール
まず、PythonでYouTube Data APIを使用するためのパッケージをインストールします。

```shell:パッケージのインストール
$ pip install google-api-python-client
```

##検索クエリを含むYouTubeチャンネルを取得する
手始めに検索ワードを含むYouTubeチャンネルを取得する処理を実装します。

```python:getChannel.py
from apiclient.discovery import build

API_KEY = '<API_KEY>' #取得したAPIキー
YOUTUBE_API_SERVICE_NAME = 'youtube'
YOUTUBE_API_VERSION = 'v3'

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

SEARCH_QUELY = input('Query >> ')
response = youtube.search().list(
    q=SEARCH_QUELY, 
    part='id,snippet', 
    maxResults=10,
    type='channel').execute()

for item in response.get('items', []):
    print(item['snippet']['title'])
```

スクリプトを実行してキーワードを入力すると該当するチャンネル10個が一覧で出力されます。```<API_KEY>```には各自取得したAPIキーを入れてください。

```python
response = youtube.search().list(
    q=SEARCH_QUELY, 
    part='id,snippet', 
    maxResults=10,
    type='channel').execute()
```

肝となるのはここでしょうか。`search().list()`メソッドの引数に各パラメータを与えることで取得したい情報を設定できます。パラメータの設定によって、チャンネルだけでなく動画や再生リストも取得できるようです。

```python
for item in response.get('items', []):
    print(item['snippet']['title'])
```

データはjson形式で返ってくるため、`get`で必要な情報を抜き出します。パラメータや返り値の詳細な形式は[YouTube Data API Reference](https://developers.google.com/youtube/v3/docs?hl=ja)で確認してください。

##指定したチャンネルの動画データを取得する
特定のチャンネルのIDを指定することでそのチャンネルの動画情報を取得できます。

```python:getVideos.py
from apiclient.discovery import build

API_KEY = '<API key>'
YOUTUBE_API_SERVICE_NAME = 'youtube'
YOUTUBE_API_VERSION = 'v3'
CHANNEL_ID = '<Channel ID>'

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

response = youtube.search().list(
    part = "snippet",
    channelId = CHANNEL_ID,
    maxResults = 5,
    order = "date",
    type ='video'
    ).execute()

for item in response.get("items", []):
    print(item['snippet']['title'])
```

先ほどのコードを多少改変しただけです。```search().list()```メソッドのパラメータが増えていますね。```channelId```を指定すると該当するチャンネルの動画情報を最大値```maxResults```まで取得することができます。```order```でレスポンスのソート方法を指定できます。dateは日付順ですね。

##動画のコメントを取得する
特定の動画のコメントを取得する処理です。

```python:getComments.py
import json
import requests
from apiclient.discovery import build

URL = 'https://www.googleapis.com/youtube/v3/'
API_KEY = '<API_KEY>'
YOUTUBE_API_SERVICE_NAME = 'youtube'
YOUTUBE_API_VERSION = 'v3'
VIDEO_ID = '<Video ID>'

params = {
    'key': API_KEY,
    'part': 'snippet',
    'videoId': VIDEO_ID,
    'order': 'relevance',
    'textFormat': 'plaintext',
    'maxResults': 100,
}

response = requests.get(URL + 'commentThreads', params=params)
resource = response.json()

for item in resource['items']:
    name = item['snippet']['topLevelComment']['snippet']['authorDisplayName']
    like_cnt = item['snippet']['topLevelComment']['snippet']['likeCount']
    text = item['snippet']['topLevelComment']['snippet']['textDisplay']
    print('ユーザ名: {}\n{}\nグッド数: {}\n'.format(name, text, like_cnt))
```

チャンネルと同様にIDを指定すると特定の動画のコメントを取得できます。

```python:
response = requests.get(URL + 'commentThreads', params=params)
resource = response.json()
```

URLに指定したパラメータを繋いで```request```しています。

```python:
for item in resource['items']:
    name = item['snippet']['topLevelComment']['snippet']['authorDisplayName']
    like_cnt = item['snippet']['topLevelComment']['snippet']['likeCount']
    text = item['snippet']['topLevelComment']['snippet']['textDisplay']
    print('ユーザ名: {}\n{}\nグッド数: {}\n'.format(name, text, like_cnt))
```

例の如く、レスポンスはjsonなので必要な情報を抜き出しています。今回はコメントしたユーザ名、本文、グッド数を取得していますが、他にも返信数や子コメントなども取得可能です。

#データ解析
APIの使い方がなんとなく掴めたところで軽いデータ解析を行ってみます。データ解析といっても上記のコードを元に特定の動画のコメントを取得してCSV出力するだけの簡単なものです。具体的には

- 検索ワードを入力して関連するチャンネルを取得する
- チャンネルを指定して動画を取得する
- 動画を指定してコメントを取得する
- CSVに書き出す

といった処理を実装してみます。```youtube_api.py```で作成します。

##チャンネル取得
検索ワードを入力して関連するチャンネルタイトルをナンバリングしてを一覧で表示させます。

```python:youtube_api.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import requests
import pandas as pd
from apiclient.discovery import build

URL = 'https://www.googleapis.com/youtube/v3/'
API_KEY = '<API_KEY>'
YOUTUBE_API_SERVICE_NAME = 'youtube'
YOUTUBE_API_VERSION = 'v3'
SEARCH_QUELY =''

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

def getChannel():
    channel_list = []
    num = 0
    search_res = youtube.search().list(
        q=SEARCH_QUELY, 
        part='id,snippet', 
        maxResults=10,
        type='channel',
        order='rating'
    ).execute()

    for item in search_res.get('items', []):
        num += 1
        channel_dict = {'num':str(num),'type':item['id']['kind'],'title':item['snippet']['title'],'channelId':item['snippet']['channelId']}
        channel_list.append(channel_dict)
    
    print('***Channel list***')
    for data in channel_list:
        print("Channel " + data["num"] + " : " + data["title"])
    print('******************')

    return getId(input('Channel Number>> '),channel_list)
```

軽く解説です。```search()```のパラメータを関連チャンネル10個をリソースの評価が高い順に取得するように設定しています。
リソースのタイトルとChannelIDをdictionary型に格納しています。```num```は一覧から特定のチャンネルを指定するための番号です。dictionaryをさらにlistに格納します。選択したいチャンネルの番号を入力するとChannelIDを返します。

##動画取得
続いて指定したChannelIDから動画を取得して表示させるコードを追加します。

```python:youtube_api.py
def getVideos(_channelId):
    video_list = []
    num = 0
    video_res = youtube.search().list(
        part = 'snippet',
        channelId = _channelId,
        maxResults = 100,
        type = 'video',
        order = 'date'
    ).execute()
    
    for item in video_res.get("items",[]):
        num += 1
        video_dict = {'num':str(num),'type':item['id']['kind'],'title':item['snippet']['title'],'videoId':item['id']['videoId']}
        video_list.append(video_dict)

    print('***Video list***')
    for data in video_list:
        print("Video " + data["num"] + " : " + data["title"])
    print('****************')

    return getId(input('Video Number>> '),video_list)
```
同じようなことをやっているだけなので説明は省略。

##コメント取得
さらに動画からコメントを取得するコードを追加します。

```python:youtube_api.py
def getComments(_videoId):
    global API_KEY
    comment_list = []
    params = {
        'key': API_KEY,
        'part': 'snippet',
        'videoId': _videoId,
        'order': 'relevance',
        'textFormat': 'plaintext',
        'maxResults': 100,
    }

    response = requests.get(URL + 'commentThreads', params=params)
    resource = response.json()

    for item in resource['items']:
        text = item['snippet']['topLevelComment']['snippet']['textDisplay']
        comment_list.append([item['snippet']['topLevelComment']['snippet']['authorDisplayName'],
                             item['snippet']['topLevelComment']['snippet']['likeCount'],
                             item['snippet']['topLevelComment']['snippet']['textDisplay']])
    return comment_list
```
VideoIDで指定した動画からコメント100件のユーザ名、本文、グッド数をlistに格納しています。

##CSV出力
DataFrameに格納してCSVに書き出しているだけです。

```python:youtube_api.py
def dataList(_comment_list):
    if(_comment_list != []):
        param=['User name', 'Like count', 'text']
        df = pd.DataFrame(data = _comment_list,columns=param)
        df.to_csv("comments.csv")
        print('Output csv')
    else:
        print('None comment')
```

##全体コード

```python:youtube_api.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import requests
import pandas as pd
from apiclient.discovery import build

URL = 'https://www.googleapis.com/youtube/v3/'
API_KEY = '<API_KEY>'
YOUTUBE_API_SERVICE_NAME = 'youtube'
YOUTUBE_API_VERSION = 'v3'
SEARCH_QUELY =''

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

def run():
    global SEARCH_QUELY
    SEARCH_QUELY = input('検索ワード>> ')
    dataList(getComments(getVideos(getChannel())))

def getId(_num,_items):
    for data in _items:
        if data['num'] == _num:
            if data['type'] == 'youtube#channel':
                return data['channelId']
            else:
                return data['videoId']
    return ''

def getChannel():
    channel_list = []
    num = 0
    search_res = youtube.search().list(
        q=SEARCH_QUELY, 
        part='id,snippet', 
        maxResults=10,
        type='channel',
        order='rating'
    ).execute()

    for item in search_res.get('items', []):
        num += 1
        channel_dict = {'num':str(num),'type':item['id']['kind'],'title':item['snippet']['title'],'channelId':item['snippet']['channelId']}
        channel_list.append(channel_dict)
    
    print('***Channel list***')
    for data in channel_list:
        print("Channel " + data["num"] + " : " + data["title"])
    print('******************')

    return getId(input('Channel Number>> '),channel_list)

def getVideos(_channelId):
    video_list = []
    num = 0
    video_res = youtube.search().list(
        part = 'snippet',
        channelId = _channelId,
        maxResults = 100,
        type = 'video',
        order = 'date'
    ).execute()
    
    for item in video_res.get("items",[]):
        num += 1
        video_dict = {'num':str(num),'type':item['id']['kind'],'title':item['snippet']['title'],'videoId':item['id']['videoId']}
        video_list.append(video_dict)

    print('***Video list***')
    for data in video_list:
        print("Video " + data["num"] + " : " + data["title"])
    print('****************')

    return getId(input('Video Number>> '),video_list)

def getComments(_videoId):
    global API_KEY
    comment_list = []
    params = {
        'key': API_KEY,
        'part': 'snippet',
        'videoId': _videoId,
        'order': 'relevance',
        'textFormat': 'plaintext',
        'maxResults': 100,
    }

    response = requests.get(URL + 'commentThreads', params=params)
    resource = response.json()

    for item in resource['items']:
        text = item['snippet']['topLevelComment']['snippet']['textDisplay']
        comment_list.append([item['snippet']['topLevelComment']['snippet']['authorDisplayName'],
                             item['snippet']['topLevelComment']['snippet']['likeCount'],
                             item['snippet']['topLevelComment']['snippet']['textDisplay']])
    return comment_list

def dataList(_comment_list):
    if(_comment_list != []):
        param=['User name', 'Like count', 'text']
        df = pd.DataFrame(data = _comment_list,columns=param)
        df.to_csv("comments.csv")
        print('Output csv')
    else:
        print('None comment')

#実行
run()

```
##実行
では早速動かしてみましょう。```youtube_api.py```を実行してください。適当なワードを入力してみます。(念のためにモザイクをかけています)

![execute1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677573/33836363-e72f-2969-726f-c18797703360.png)
チャンネル番号を指定。↓
![execute2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677573/06005a91-8889-ce1b-4cab-d0ac67558a15.png)
動画番号を指定。↓
![execute3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677573/9c2f50fc-1360-fdfc-8bd6-933824deb036.png)
無事CSVに出力できたら成功です。お疲れ様でした。
今回はコメントを抜き出すだけの簡単な処理でしたが、チャンネルや動画のデータをグラフ化したりしてみても面白そうですね。また、別のAPIを使えばコメント文を感情分析してアンチコメントを割り出すみたいなこともできるようです。
今回使ったコードはGitHubにあるのでそちらを参照してください。

#編集後記
本当は別の内容を書きたかったのですが、時間の都合上でただAPIを叩いただけの薄い内容になってしまいました。もう少しトリを飾るのにふさわしい内容にしたかったですね。ただAPIを利用するスキルは磨いておいて損はないのかなと思います。
本記事の内容に改善点やアドバイスなどがありましたら、よろしくお願いします。

最後に、このような記事を載せる場を設けてくださった福島高専 Advent Calendar 2020の主催者様に心より感謝申し上げます。

#参考文献

- [YouTube Data API Reference](https://developers.google.com/youtube/v3/docs?hl=ja)
- [【Python】YouTube Data API を使って、いろんな情報を取得してみた！](https://qiita.com/ryoya41/items/dd1fd4c1427ece787eea)
- [Youtube のコメントを取得する in Python](https://qiita.com/Doarakko/items/191209bf14cf5d76fa6f)

#GitHub
https://github.com/oyachi/youtube_API.git