YouTubeのコメント欄による都知事選の簡単な分析

# 都知事選をYouTubeのコメント欄から分析してみました

はじめまして,Qiita初投稿です．[学科の先輩](https://qiita.com/konbraphat51)にQiitaをやれとよく言われているので始めてみました．[学科](https://www.si.t.u-tokyo.ac.jp/course/sdm/)では微分方程式を離散化したりシステム論を学んだり色々やってます．

記念すべき初投稿はYouTubeのコメント欄を可視化することで都知事選の簡単な分析をしてみました．

### プログラミングに関する簡単な自己紹介
昔先輩のお手伝いでYouTubeのAPIを使って動画のコメントを取得しました．YouTubeAPIを使用したコメント取得については様々な記事が出ているので，それを参考にしてもらえればと思います．
一方で学科の授業では簡単な自然言語処理を学びWordCloudを使って遊んだり，熱伝導方程式を離散化して温度分布を可視化したりしていました．

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834669/3230c516-dc61-a776-f825-590f5d6115a7.png)
↑温度分布の可視化

### 都知事選の分析の概要
弊学科にはネット上で注目を集めたことを分析している先生がいます．

https://x.com/toritorix

[もちろん今回の都知事選も分析しています．](https://news.yahoo.co.jp/expert/articles/2b9a61ceb255aa3262800e6bd5309f8b994acb34)そこで先生の真似っぽいことをしたいと思い，自分が出来るYouTubeのコメント取得と簡単な自然言語処理をもとに都知事選を分析してみました．

具体的には
- 小池百合子さん
- 石丸伸二さん
- 田母神俊雄さん
- 安野貴博さん
の各候補について，**選挙期間の最後に公式youtube上で公開された動画のコメント欄を分析**しました．**蓮舫さんについてはコメント欄が公開されていなかったので分析できていません.**

分析は簡単に取得できたコメント内での出現頻度でWordCloudと頻度分布の可視化を行っています．requestsライブラリを用いて動画ごとにコメントを取得し，それに対してjanome.tokenizerによって処理しています．なお[この記事](https://tkstock.site/2021/03/23/python-youtube-api-%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88-%E5%85%A8%E4%BB%B6-%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3-%E5%8F%96%E5%BE%97/)をもとにコメントを取得しようとしましたが，全件取得できていません(全件取得する方法とかもネットに載ってるけどよくわかんなかったです).

また以下のコードの解説ではAPIキーの取得方法等は説明していないので，実際にご自分でやる場合は調べてみてください．結構記事あります．

それではざっくりコードを説明します．
まず必要なライブラリやフォントを準備します．
```python
import pandas as pd
import matplotlib.pyplot as plt
import re
from janome.tokenizer import Tokenizer
import unicodedata
import japanize_matplotlib
from wordcloud import WordCloud
from collections import Counter
from matplotlib import rcParams
import requests
font = 'ipaexg.ttf'
```
今回，フォントはIPAexゴシックを使用しました．
動画のIDを投げることでコメントを取得するget_sentence関数とそれに必要なprint_video_comment関数とprint_video_reply関数とを準備します．コードを書いたのが昔すぎて詳しいことはよく覚えていません...

```python
URL = 'https://www.googleapis.com/youtube/v3/'
API_KEY = 'ここに自分のAPIキーを入れる'

def print_video_comment(no, video_id, next_page_token, text_data):
        params = {
        'key': API_KEY,
        'part': 'snippet',
        'videoId': video_id,
        'order': 'relevance',
        'textFormat': 'plaintext',
        'maxResults': 100,
        }
        if next_page_token is not None:
            params['pageToken'] = next_page_token
        response = requests.get(URL + 'commentThreads', params=params)
        resource = response.json()
        for comment_info in resource['items']:
            # コメント
            text = comment_info['snippet']['topLevelComment']['snippet']['textDisplay']
            # 返信数
            reply_cnt = comment_info['snippet']['totalReplyCount']
            # Id
            parentId = comment_info['snippet']['topLevelComment']['id']
            text_data.append(['parent', text, reply_cnt])
            if reply_cnt > 0:
                cno = 1
                print_video_reply(no, cno, video_id, next_page_token, parentId, text_data)
            no = no + 1
        if 'nextPageToken' in resource:
            print_video_comment(no, video_id, resource["nextPageToken"], text_data)
            
def print_video_reply(no, cno, video_id, next_page_token, id, text_data):
        params = {
        'key': API_KEY,
        'part': 'snippet',
        'videoId': video_id,
        'textFormat': 'plaintext',
        'maxResults': 50,
        'parentId': id,
        }

        if next_page_token is not None:
            params['pageToken'] = next_page_token
        response = requests.get(URL + 'comments', params=params)
        resource = response.json()
        for comment_info in resource['items']:
            # コメント
            text = comment_info['snippet']['textDisplay']
            text_data.append(['child', text, 0])
            cno = cno + 1
        if 'nextPageToken' in resource:
            print_video_reply(no, cno, video_id, resource["nextPageToken"], id, text_data)

def get_sentence(video_id):
    text_data=[]
    no = 0
    print_video_comment(no, video_id, None, text_data)
    df = pd.DataFrame(text_data, columns=['type', 'comment_data', 'reply_cnt'])
    sentence = "\n"
    for i in range(len(df)):
        sentence += df.iloc[i][1] 
    return sentence
```
WordCloudを作るために必要なwords_wakachiと頻度分布を作るために必要なwords_listを返すmake_text関数を定義します．この関数内では名詞であって数詞や非自立語，代名詞でないものをとってきています．またあとで定義するexclude_wordsに含まれていないことも条件としています．
```python
def make_text(video_id):
    text = get_sentence(video_id)
    #変数text中に格納されたアンケート内の不要な文字や記号を削除する。
    #re.subで正規表現を使った文字列の削除。他にも色々あると思います。
    text = re.sub('・', '', text)
     
    
    # テキストをtokenize
    t = Tokenizer()
    tokenized_text = t.tokenize(text)
    words_list = []
    words_wakachi = ""

    # 必要な品詞を抽出
    for token in tokenized_text:
        tokenized_word = token.surface
        hinshi = token.part_of_speech.split(',')[0]
        hinshi2 = token.part_of_speech.split(',')[1]
        if hinshi == "名詞":
            if (hinshi2 != "数") and (hinshi2 != "非自立") and (hinshi2 != "代名詞"):
                if tokenized_word not in exclude_words and any(char_type in ["CJK", "Hiragana", "Katakana"] for char_type in [unicodedata.name(char).split()[0] for char in tokenized_word]):
                    words_list.append(tokenized_word)


    words_wakachi = " ".join(words_list)
    return words_wakachi, words_list
```
あとはWordCloudと頻度分布を可視化して終わりです.
```python
def make_cloud(words_wakachi):
    word_cloud = WordCloud(font_path=font, width=1500, height=900,
                           min_font_size=5, 
                           collocations=False, background_color='white', 
                           max_words=400).generate(words_wakachi)
    plt.figure(figsize=(15,10))
    plt.imshow(word_cloud)
    plt.axis("off")
    plt.savefig(f"{video_id}_cloud.png")
    plt.show()


def make_graph(words_list, no):
    word_counts = Counter(words_list)
    common_words = [(word, count) for word, count in word_counts.items() if word not in exclude_words]
    common_words.sort(key=lambda x: x[1], reverse=True)
    words, counts = zip(*common_words[:no])

    print("上位",no,"単語:")
    for word, count in common_words[:no]:
        print(f"{word}: {count}")

    # 日本語フォントの設定
    plt.rcParams['font.sans-serif'] = [font]  # 適切な日本語フォントを指定
    plt.rcParams['axes.unicode_minus'] = False
    plt.rcParams["font.size"] = 16


    plt.figure(figsize=(25,15))
    plt.bar(words, counts)
    plt.xlabel('Words')
    plt.ylabel('Count')
    plt.title('Word Frequency Distribution')
    plt.xticks(rotation=90)
    plt.savefig(f"{video_id}_graph.png")
    plt.show()

exclude_words = {"し","さ", "さん","れ", "する"}
video_id = "ここに動画IDを入れる"
words_wakachi, words_list = make_text(video_id)
make_cloud(words_wakachi)
make_graph(words_list,20)
```

YouTubeの動画IDについては[こちら](https://support.google.com/youtube/thread/228089037/%E3%83%93%E3%83%87%E3%82%AAid%E3%81%A8%E3%81%84%E3%81%86%E3%81%AE%E3%81%AF%E3%81%A9%E3%81%93%E3%81%A7%E8%A1%A8%E7%A4%BA%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%81%8B%EF%BC%9F?hl=ja)をご参照ください．
なおmake_graph関数の2つ目の引数の数値をn(適当な自然数)に変えると上位n位の単語が出力されます．

さて，てなわけで結果と考察です．
### 結果と考察
今回は2024年7月10日午前1時頃に上のコードを利用して分析を行いました．
注意としては単語の頻度分布における縦軸の値があげられます．各候補の動画によってコメント数が異なるため，数値が大きく異なっています．そこだけ注意していただければと思います．

**まず小池百合子さんの動画は↓です.**

https://www.youtube.com/watch?v=bdyM8K_Xbl4

可視化するとこちら
![bdyM8K_Xbl4_cloud.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834669/8f533f9b-eb35-3a13-8103-2fd1e50d1220.png)
![bdyM8K_Xbl4_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834669/011b319c-5e32-f693-9b70-5eb01146bc3c.png)

やはり「都知事」という単語が目を引きますね．「お願い」という単語があることから，コメント内では何かしらのお願いがされていると思われます．「都民」や「都政」「都知事」といった単語が多く，都知事選であることをよく表しています．
また4位には「当選」がランクインしました．実際に当選した後に書かれたコメントですかね．機会があれば単語ごとの時系列も出したいと思いました．
　
 　
**つぎに石丸伸二さんはこちら．**

https://www.youtube.com/watch?v=39vdDjfh894

![39vdDjfh894_cloud.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834669/cc1d4be7-2aab-9846-6785-5d876001df6b.png)
![39vdDjfh894_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834669/31988f86-bbf7-a8b1-f76e-9771567eb1b6.png)

「都知事」よりも「選挙」や「日本」という単語が多いのが興味深いですね．また「投票」という単語も多いことから，都知事選というよりは国会議員選挙のように感じます．
17位には「小池」と小池百合子さんのことであると考えられる単語があります．現職の影響が見て取れますね．
　
 　

**3番目は田母神俊雄さん．**

https://www.youtube.com/watch?v=T3vWN112nlo

![T3vWN112nlo_cloud.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834669/af6bc33c-f8b9-27d5-2b70-1c8cba7d4b3e.png)
![T3vWN112nlo_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834669/0503d020-bbda-f866-6668-bce8c9171b74.png)

こちらは動画がデヴィ夫人との対談だったということでそれが反映される結果に．また右派ということで「日本」や「日本人」がキーワードとして多くなっています．この辺りは石丸さんのコメント欄に似ていますね．
また20位には「残念」がランクインしており，投票結果が出た後にコメント欄で残念な思いを書き込んだ人が一定数いそうです．
　
 　

**最後に安野貴博さん．**

https://www.youtube.com/watch?v=ILhXZkefA1U

![ILhXZkefA1U_cloud.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834669/4cc6e664-10af-68c0-cefb-62bf485f211a.png)
![ILhXZkefA1U_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834669/36c9decf-d505-4621-66cb-12145b6cb2c3.png)


ほかの候補には見られない「未来」や「提案」というキーワードが出てきました．安野さんは他の候補に対する批判等がなく自分の政策を唱えることに終始したということで，それが現れたのではないかなと思います．
また「才能」という単語がランクインしており，安野さんがコメント欄でどのように受け止められていたかが分かりますね．
　


### まとめ
今回はざっくりYouTubeのコメント欄から都知事選の各候補に対する反応を可視化してみました．そのうちコメントから単語の共起ネットワークや感情分析もしてみたいですね．