Pythonでニコニコ動画とYoutubeを同時検索できるようにしてみた 

# 前置き
娘：おとうさん、ニコニコ動画とYoutube面白いねぇ
私：そうだね！　最高のサービスだよね！
娘：でも別々のサイトだから検索面倒臭いねぇ
私：そうだね！　でもそれは仕方n
娘：面倒臭いねぇ
私：・・・きっと優秀な人が同時に検索できるサービス作ってるんじゃないかな？
娘：知らない人のサービスは触っちゃいけないっておじいちゃんが言ってた
私：・・・
娘：面倒臭いねぇ
私：・・・（無言でPCを起動する）
娘：さすがだねぇ

というわけで妄想の前置きは置いておいて、早速始めてみます。

# 環境
- 今回使用するのは下記です
```text
vscode
python
  - 3.9.7
docker
  - 20.10.11
django
  - 4.0.6
```

# docker

- 今回は環境作成にdockerを利用します。事前のインストール等の手順については割愛します。
- vsCodeのコンテナ開発を利用します。
- まずは`.devcontainer`フォルダを作成し、その中に必要なファイルを作成します。
```devcontainer.json
{
	"name": "my YouTube",
	"dockerFile": "Dockerfile",
	"extensions": [
		"ms-python.python"
	],
}

```

- 次に、DockerFileを作成します
```Dockerfile 
FROM python:3

ENV PYTHONUNBUFFERED 1 
RUN pip install django
RUN pip install google-api-python-client
```

- vscodeでリモート開発をスタートします。

# Django
- Djangoのプロジェクトを用意します。
- ターミナルを利用し、以下コマンドでプロジェクトを作成します

```bash
django-admin startproject myYoutubeApp .
```

- 次に、アプリケーションを作成します
```bash
python manage.py startapp youtube
```


# YoutubeAPIキーの取得
- Youtubeの利用にはAPIキーが必要なので、APIキーを取得します。
既に取得済みの方は不要です。
### プロジェクトの作成
- まずは [ココ](https://console.cloud.google.com/projectcreate?hl=ja) にアクセスし、新規にプロジェクトを作成します。

### YouTube Data API v3の有効化
- 作成したプロジェクトを開いた状態で、`ライブラリ`を選択します
- `YouTube Data API v3`で検索をすると、今回使用するライブラリが表示されるので、クリックします。
- `有効にする`をクリックして、有効化します。

### 認証情報を作成
- APIキー取得のために、認証情報を作成します
- `認証情報`をクリックして遷移した画面で、`CREATE CREDENTIALS`をクリックし、APIキーを選びます
- 自動でAPIキーが作成されるので、必ずメモをしておいてください

### セキュリティ
- 最低限のセキュリティはかけておきましょう。作成したAPIキーをクリックします
- APIの制限から、`キーの制限`を選択し`YouTube Data API v3`にチェックを入れて`保存`します

### ニコニコ動画
- ニコニコ動画のAPIの利用には特にキーが必要ないので、何かする必要はありません。

# 動画情報を取得してみる
- まずはトップページを開けるようにします。プロジェクトの`urls.py`に追記します

```python:myYoutubeApp > urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    # 追加
    path('', include('youtube.urls')),
]
```
- 次に、アプリケーションの方の`urls.py`を修正します
```youtube > urls.py
from django.urls import path
from . import views

urlpatterns = [
    # ルートのアクセスを定義
    path('', views.index, name='index'),
]
```
- ルートにアクセスした際の処理を作成します
```python:youtube > view.py
from django.shortcuts import render

def index(request):
    return render(request, 'index.html', {'text': 'Hello World!'})
```

- アクセス先のhtmlを作成します。アプリケーションのフォルダ内に`templates`フォルダを作成し、`index.html`を作成します
```html:youtube > index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="robots" content="noindex noffolow">
    <title>同時検索サイト</title>
</head>
<body>
    <p>{{ text }}</p>
</body>
</html>
```

- [ローカル環境](（127.0.0.1:8000）)にアクセスしてみます。Hello Worldと表示されていれば成功です。The・シンプル！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516859/cd2c21eb-3309-2932-9ac7-28b85ff2c7e5.png" alt="hello world" width="50%">

- さて、画面表示ができたので一旦動画情報を取得試してみます。`views.py`を書き換えます。画面表示は一旦しないので、index.htmlは使用していません。
```viws.py
import requests

from pprint import pprint
from googleapiclient.discovery import build

def index(request):
    # Youtubeの情報を取得する
    youtube_response_json = get_youtube_data()

    # ニコニコ動画の情報を取得する
    nico_response_json = get_niconico_data()

    # 取得した情報を出力
    pprint(youtube_response_json)
    pprint(nico_response_json)

# ニコニコ動画の情報を取得する
def get_niconico_data():
    url = 'https://api.search.nicovideo.jp/api/v2/snapshot/video/contents/search'
    
    param = {
        'q' : 'エガちゃん',
        'targets' : 'title',
        'fields' : 'contentId,title,viewCounter',
        'filters[viewCounter][gte]' : 100000,
        '_sort' : 'viewCounter',
        '_limit' : 1,
        '_context' : 'apiguide',
    }

    res = requests.get(url, params=param).json()["data"]

    return res

# Youtubeの情報を取得する
def get_youtube_data():
    youtube = build(
            'youtube',
            'v3',
            developerKey={取得したAPIキー}
        )

    response = youtube.search().list(
        q='エガちゃん',
        part='id,snippet',
        order='viewCount',
        type='video',
        maxResults=1).execute()

    return response
}
```

- 再び[トップページへ](127.0.0.1:8000)アクセスします。
- vscodeのターミナルに以下のように取得情報が表示されれば成功です。なお、ブラウザにはエラーが表示されると思いますが今はスルーします。
```
# Youtube
{'etag': 'GmRBbKt6o_U_5lxnmop9UDKhUeE',
 'items': [{'etag': '3JxWeqMPWTIIt24CykBg_U8T7R4',
            'id': {'kind': 'youtube#video', 'videoId': 'WfMEHQjHdH4'},
            'kind': 'youtube#searchResult',
            'snippet': {'channelId': 'UCTcU6mI-ucdFKdYDBekRMTQ',
                        'channelTitle': 'HoteiVEVO',
                        'description': 'Official: http://www.hotei.com HOTEI- '
                                       'Thrill [Official Music Video] A '
                                       'flashback to the mid 90s, this '
                                       'original music video represents ...',
                        'liveBroadcastContent': 'none',
                        'publishTime': '2012-11-22T09:27:11Z',
                        'publishedAt': '2012-11-22T09:27:11Z',
                        'thumbnails': {'default': {'height': 90,
                                                   'url': 'https://i.ytimg.com/vi/WfMEHQjHdH4/default.jpg',
                                                   'width': 120},
                                       'high': {'height': 360,
                                                'url': 'https://i.ytimg.com/vi/WfMEHQjHdH4/hqdefault.jpg',
                                                'width': 480},
                                       'medium': {'height': 180,
                                                  'url': 'https://i.ytimg.com/vi/WfMEHQjHdH4/mqdefault.jpg',
                                                  'width': 320}},
                        'title': '布袋寅泰 / HOTEI - スリル'}}],
 'kind': 'youtube#searchListResponse',
 'nextPageToken': 'CAEQAA',
 'pageInfo': {'resultsPerPage': 1, 'totalResults': 102105},
 'regionCode': 'JP'}

# ニコニコ動画
[{'contentId': 'sm1853698',
  'title': 'エガちゃんのパナウェーブ研究所潜入記',
  'viewCounter': 413333}]
```

- 取得した情報を元に動画を画面に表示してみます。まずは`view.py`のindexメソッドを修正します
```python:youtube > view.py
def index(request):
    # Youtubeの情報を取得する
    youtube_response_json = get_youtube_data()

    youtubeList = {}
    # 必要な情報を辞書に格納する
    if youtube_response_json:
        for item in youtube_response_json['items']:
            youtubeList[item['snippet']['channelTitle']] = item['id']['videoId']

    # ニコニコ動画の情報を取得する
    nico_response_json = get_niconico_data()

    nico_list = {}
    # 必要な情報を辞書に格納する
    if nico_response_json:
        for item in nico_response_json:
            nico_list[item['title']] = item['contentId']

    return render(request, 'index.html', {
        'nicoList': nico_list,
        'youtubeList': youtubeList,
    })
```
- また、今回動画の埋め込みに必要な情報はtitleとidだけなので、その2つだけ取得するようにします。こうすることで、クォーターの消費を抑えることができます。
```python
# Youtubeの情報を取得する
def get_youtube_data():
    youtube = build(
            'youtube',
            'v3',
            developerKey={取得したAPIキー}
        )

    response = youtube.search().list(
        q='エガちゃん',
        fields='items/snippet/channelTitle, items/id/videoId', # 取得するフィールドを制限する
        part='id,snippet',
        order='viewCount',
        type='video',
        maxResults=1).execute()

    return response
}
```


- 次に、`index.html`を修正します
```html:templates > index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="robots" content="noindex noffolow">
    <title>Document</title>
</head>
<body>
      <p>ニコニコ動画検索結果</p>
        {% for key, list in nicoList.items %}
            <div>
              <script type="application/javascript" src="https://embed.nicovideo.jp/watch/{{ list }}/script?w=640&h=360"></script><noscript>
              <a href="https://www.nicovideo.jp/watch/{{list}}">{{ title }}</a></noscript>
            </div>
        {% endfor %}

      <p>youtube検索結果</p>
        {% for key, list in youtubeList.items %}
            <iframe id="ytplayer{{ forloop.counter }}" type="text/html" width="640" height="360"
            src="https://www.youtube.com/embed/{{ list }}?autoplay=1&origin=http://example.com"
            frameborder="0"></iframe>
        {% endfor %}
</body>
</html>
```

- それでは、再び画面へアクセスします。取得した動画が表示されていることが確認できれば成功です！ なお、埋め込み許可されていない動画については表示できませんのでご注意ください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516859/658ecc96-67c4-fd94-1e37-099b5aed8650.png" alt="hello world" width="50%">

- フィルターとか色々いじれるようにすれば、より欲しい情報が取れるようになると思います。その辺の微調整できる機能はまたいつか。

# あとがき
私：よし、できたぞ、簡単なものだけどこれでどうだ！
娘：ありがとうおとうさん！！
娘：・・・
娘：・・・・・・
娘：・・・・・・・・・
私：（感動で声も出ないか）
娘：これ検索できないの？
私：え？
娘：いくらリロードしてもエガちゃんの同じ動画しか出てこないんだけど？
私：・・・（無言でPCを開く）
娘：さすがだねぇ

# 入力フォームを置いてみる
- というわけで、このままでは永遠に同じ動画しか表示されないので検索ワードを入力できるフォームを入れてみることにします。

- まずはフォームを作成します。アプリケーションフォルダに新しく`forms`フォルダを作成し、その中に`indexForm.py`を作成します。
```python:forms > indexForm.py
from django import forms

class IndexForm(forms.Form):
    searchWord = forms.CharField(
        label = '検索ワード'
    )
```
- どシンプルに、検索ワードを入れる入力ボックスを追加します
- そして、`view.py`と`index.html`を最終的に以下のように修正します
```python:youtube > view.py
import requests

from django.shortcuts import render
from googleapiclient.discovery import build
from youtube.forms.indexForm import IndexForm

def index(request):
    form = IndexForm()
    searchWord = request.POST.get('searchWord', False)

    youtubeList = {}
    if searchWord:
        # Youtubeの情報を取得する
        youtube_response_json = get_youtube_data(searchWord)

        # 必要な情報を辞書に格納する
        if youtube_response_json:
            for item in youtube_response_json['items']:
                youtubeList[item['snippet']['channelTitle']] = item['id']['videoId']

    nico_list = {}
    if searchWord:
        # ニコニコ動画の情報を取得する
        nico_response_json = get_niconico_data(searchWord)

        # 必要な情報を辞書に格納する
        if nico_response_json:
            for item in nico_response_json:
                nico_list[item['title']] = item['contentId']

    return render(request, 'index.html', {
        'form': form,
        'nicoList': nico_list,
        'youtubeList': youtubeList,
    })

# ニコニコ動画の情報を取得する
def get_niconico_data(searchWord):
    url = 'https://api.search.nicovideo.jp/api/v2/snapshot/video/contents/search'
    
    param = {
        'q' : searchWord,
        'targets' : 'title',
        'fields' : 'contentId,title,viewCounter',
        'filters[viewCounter][gte]' : 100000,
        '_sort' : 'viewCounter',
        '_limit' : 2,
        '_context' : 'apiguide',
    }

    res = requests.get(url, params=param).json()["data"]

    return res

# Youtubeの情報を取得する
def get_youtube_data(searchWord):
    youtube = build(
            'youtube',
            'v3',
            developerKey={取得したAPIキー}
        )

    response = youtube.search().list(
        q=searchWord,
        fields='items/snippet/channelTitle, items/id/videoId',
        part='id,snippet',
        order='viewCount',
        type='video',
        maxResults=10).execute()

    return response
```

```html:youtube > index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="robots" content="noindex noffolow">
    <title>Document</title>
</head>
<body>
      <form action="/" method="post">
        {% csrf_token %}
        <div>
            {{form}}
        </div>
        <input type="submit" value="送信する">
      </form>

      <p>ニコニコ動画検索結果</p>
        {% for key, list in nicoList.items %}
            <div>
              <script type="application/javascript" src="https://embed.nicovideo.jp/watch/{{ list }}/script?w=640&h=360"></script><noscript>
              <a href="https://www.nicovideo.jp/watch/{{list}}">{{ title }}</a></noscript>
            </div>
        {% endfor %}

      <p>youtube検索結果</p>
        {% for key, list in youtubeList.items %}
            <iframe id="ytplayer{{ forloop.counter }}" type="text/html" width="640" height="360"
            src="https://www.youtube.com/embed/{{ list }}?autoplay=1&origin=http://example.com"
            frameborder="0"></iframe>
        {% endfor %}
</body>
</html>
```

- 再び画面を開きます
![スクリーンショット 2022-08-07 8.11.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516859/52a9fa61-857e-fee9-1652-9df60831be50.png)
- フォームが追加されていることを確認できました。では検索ワード入力ボックスに`育児`と入れてみましょう。
![screencapture-127-0-0-1-8000-2022-08-07-08_12_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516859/6c1913ce-f1f6-65dd-8eae-83751e272089.png)
- 無事関連する動画が取得できました

# 本当にあとがき
私：で、できたぞ、これで好きな動画を検索できるぞ
娘：わーい！
娘：・・・
娘：・・・・・・
私：ま、まだ何か？
娘：複数キーワード検索できないの？
私：・・・
娘：あと動画の数が少なすぎて、結局みたいのが見れないんだけど？
私：・・・
娘：条件もキーワードだけしかないの？　再生数とかいいね数とかで絞り込めないの？あと・・・
私：（ダッシュ）
娘：あ、逃げた。。。ま、いいか、普通にYoutubeとニコニコ動画で見よっと
私：はぁはぁ、、、逃げれたか、、、しかしまあ言っていることは正論だな、、、娘の意見も取り入れて、ちょこちょこ手直ししていくか

〜完〜


というわけで、今回はYoutubeのAPI触ってみたいというのが目的でしたの、一旦ここまで。
気が向いたら改良するかもしれません。では！

### 注意点
- YoutubeのAPIのクォーターの割り当てが、現在は10,000しかありません。（以前は50万とかあったそうですが、、、すごい削減ですね）