Home 特定の YouTube チャンネル内の動画をキーワード検索できるサービスを作った
Post
Cancel

特定の YouTube チャンネル内の動画をキーワード検索できるサービスを作った

最近になって定期更新される YouTube チャンネルを観る習慣がついた。特定の発言がどの動画で出てきたかを探すために、キーワード検索を行うサービスを作成した。使用したコードは こちら、完成したものは こちら

概要

  • データの取得は YouTube API v3 を用いて行った。一つの動画について、データをまとめたファイルとサムネイル画像をローカルへ保存するようにした。
  • サイト自体は Wordpress で立ち上げ、検索機能も Wordpress に標準搭載されているものを用いた。WP-CLI を使うことにより、上記データが自動的に記事として登録されるようにした。
  • 以下、実装の手順を細かく見ていく。

データの取得

事前準備

  • Google Cloud Platform 経由で API KEY を取得しておく。
  • データを取得するため、チャンネルの動画をまとめたプレイリストの ID を把握する。チャンネルのトップページから「すべて再生」をクリックすると、チャンネルの動画が最新のものから順に並んだプレイリストにアクセスできるので、URL の list?= 以下を控えておく。

データの保存

データ取得方法の概要を示す。詳細な仕様については こちらのスクリプト を参照。

  • YouTube API v3 を叩くことによって、以下のようにプレイリスト内にある動画のメタデータを取得。さらに、それぞれの動画についてデータをまとめたファイルを comments/[VideoID].md へ保存する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def process(self, next_page_token=None) -> None:
    """
    Get videos from a playlist specified by `self.playlist_id`.
    If next_page_token exist, call this function recursively
    """
    sleep(1)
    params: dict[str, str] = {
        'playlistId': self.playlist_id,
        'key': self.API_KEY,
        'part': 'snippet',
        'maxResults': 50,
    }
    if next_page_token is not None:
        params['pageToken']: str = next_page_token
    response: requests.Response = requests.get(self.endpoint + 'playlistItems', params=params)
    resource: dict = response.json()
    items: dict = resource['items']
    for item in items:
        self.idx += 1
        # Update `self.video_info`
        self.process_video_info(item)
  • サムネイル画像を thumbnails/[VideoID].jpg へ保存する。
1
2
3
4
5
6
7
8
def download_thumbnail(self) -> None:
    """
    Downlowd thumbnail of the video in the `self.video_info`
    """
    response: requests.Response = requests.get(self.video_info.thumbnail_url)
    image = response.content
    with open(f'./thumbnails/{self.video_info.video_id}.jpg', "wb") as f:
        f.write(image)
  • 取得が完了した動画のメタデータについては log.tsv に保存しておく。
  • 以下のコマンドを cron に登録し、上記 Python スクリプトを毎日実行するようにした。取得する動画は、公開から一週間経っており、かつ前回までの実行で取得していない (log.tsv に含まれていない) ものに限定する仕様とした。
1
/usr/local/bin/python /home/ternbusty/download_video_info.py

Wordpress への登録

上記 Python スクリプト内で、動画データを取得したついでに Wordpress へ登録するスクリプトも実行するようにする。

1
2
3
4
5
6
def register_to_wp(self) -> None:
    """
    Register a comment file to wordpress
    """
    dt_str = self.video_info.published_at.split('T')[0]
    subprocess.run(['bash', 'register.sh', self.video_info.video_id, self.video_info.title, dt_str])

register.sh の中身は以下の通り。

  • wp post create を用いて Wordpress への記事登録を行う。後述する理由により post_excerpt には動画の ID を設定した。
  • wp media import によってサムネイルをインポート & featured image への設定を行った。
1
2
3
4
5
6
7
8
9
ID=$1
POST_TITLE=$2
DT=$3
PATH="./comments/${ID}.md"
echo $PATH
POST_ID=`/usr/local/bin/wp post create ${PATH} --path="./www/" --post_title=${ID} --post_date=${DT} --post_status=publish --post_excerpt=${ID} --porcelain`
echo $POST_ID
/usr/local/bin/wp post update ${POST_ID} --path="./www/" --post_title="${POST_TITLE}"
/usr/local/bin/wp media import ./thumbnails/${ID}.jpg --path='./www/' --post_id=${POST_ID} --featured_image

Wordpress の設定

テーマには Cocoon を用いた。また、/wp-content/themes/cocoon-child-master/functions.php に以下を追記した。

  • specific_url_redirect(): 検索結果をクリックすると動画ではなくデータをまとめた記事自体にアクセスされてしまうので、リダイレクトを行う。ここで先ほど登録した excerpt を用いて動画 ID を取得し、URL を生成した (本当は記事の ID を用いたかったが、動画 ID のなかには記事の ID に用いることができない文字が含まれている場合があったため)。
  • change_posts_per_page(): Wordpress の検索機能では、例えば “あいう えお” などと全角スペースを用いて検索を行うと、”あいう” AND “えお” ではなく “あいう えお” という文字列自体が検索されてしまうらしい。そのため、検索クエリ内の全角スペースを半角に置換するようにした。
  • custom_posts_search_orderby(): 検索結果が古い順に表示されるようにした。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
add_action('get_header', 'specific_url_redirect');
function specific_url_redirect(){
	  $url = $_SERVER['REQUEST_URI'];
	  if (strstr($url, 'posts')){
        $id = get_the_ID();
        $excerpt = get_the_excerpt($id);
        $new_url = 'https://www.youtube.com/watch?v='.$excerpt;
		    wp_redirect($new_url, 301);
		    exit;
	  }
}

add_action('pre_get_posts', 'change_posts_per_page');
function change_posts_per_page($query) {
    if (is_admin() || !$query->is_main_query()){
        return;
    }
    if ($query->is_search()) {
        $query->set('post_type', 'post');
        $s = $query->get('s');
        $s = str_replace(' ', ' ', $s);
        $query->set('s', $s);
    }
}

add_filter('posts_search_orderby', custom_posts_search_orderby);
function custom_posts_search_orderby() {
    return 'post_date asc';
}

サイトの見た目を整える

Wordpress のカスタマイズ機能を用いてサイトに こちらの CSS を設定した。何も分からんので !important だらけでやばくなってしまったけど、まあええか……

感想

今まで web サイトの立ち上げには Jekyll しか使ったことがなく、Wordpress は初めてだったので何も分からんわという感じで悪戦苦闘していた。投稿をクリックで動画に飛ぶところ、リダイレクトではなく単にその投稿へのリンクを張ったほうがよい気がするのでそのうち functions.php をごにょごにょして何とかしたい。

This post is licensed under CC BY 4.0 by the author.

自宅ネットワークを Grafana でモニタリング (3)

名字の五十音分布を可視化してみる