推しが歌枠で歌ったあの歌が聞きたい人へ(YouTubeAPIによるタイムスタンプの取得)
YouTubeAPIを使って、動画のコメント欄、概要欄にあるタイムスタンプ付きコメントを取得してみました。
コードはこちら
推しの歌枠の特定の歌を聴くには
技術の話が見たい人はこの章をスキップしてください
私は作業のお供にお歌配信(歌枠)を良く聞きます、で、ときどき、歌枠で歌われた特定の歌を、後から聞き直したいなぁって思うわけです。
どの配信かさえ分かっていれば、ファンの人が、タイムスタンプ(特定の時間から動画を再生するリンク)を残してくれてたりするので、それを見つければいいわけです。
しかし、どの配信かも分からないときって、探しようがないんですよね。どうにかしたい。
で、実際歌枠の特定の歌を聞く方法は以下の通り
- タイムスタンプをたどる
- どの配信で歌われたか分かっているときはこれ
- 1動画内ならコメントの検索ができるのでそれで見つける
- 有志による切り抜きを視聴する
- 星街すいせいさん等、有志が1曲単位で小分けにした動画がある場合はこれ
- 有志のサービスを利用する
- 有志が歌枠で歌われた各曲へのリンクをサイトにまとめて、検索できるようにしている場合はこれ
- ググると、ホロライブさん関連のものがいくつか出てくる
- ファンの力技でデータベースができているという噂
- gozaru.fans https://gozaru.fans/
- ホロライブ歌まとめ - 推しのセトリ - https://www.holosite.me/song/search.html
- 人力で1つ1つ動画を開く
- 有志によるデータベースはさすがになくとも、コメント欄にタイムスタンプが残されていることは多い。つまり、1つづつ動画ページを開いてコメ欄を検索すれば……
- 論外、今回はこれを自動でやりたい
YouTubeAPI でやってみた
そんなわけで、YouTubeAPI を使って、
- 特定チャンネルの動画一覧を取得
- 動画のコメント欄・概要欄を取得
- タイムスタンプ付きコメントを抽出して保存
- あとは、
ctrl+f
検索なりアプリにするなりご自由な方法で検索
していく
詳細な実装はリポジトリを参照。以下ではざっくりとだけ説明する
ちなみに、APIを使わなくても、スクレイピングが合法的にできる場合もあるが、難解な利用規約を解読できる上級者向けなので、おすすめしない
コードに関して、こちらの記事も参考にしました
APIキーをゲット
なにはともあれ、APIキーを入手しなければ始まらない
手持ちの Google アカウントで、Google Cloud Platform にアクセスしてポチポチすればキーがもらえる
API キーの取得はこの辺を参考に
Python で API を叩いてみる
Web API なんて、何で叩いてもいいじゃないかと思うんだけど、スクレイピングなら慣れた Python が楽で早いってことで、Python を使う
環境構築
API のラッパーと dotenv を入れます
pip install google-api-python-client
pip install python-dotenv
ラッパーの詳細は以下、なんだかんだラッパー使った方が楽
dotenv は APIキーをコードにベタ書きしたくないので使う
ここ とか参照
↓こんな感じにやれば、あとは、youtube.hogehoge(params)
的なふうにAPIが叩ける
from apiclient import discovery
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv('API_KEY')
youtube = discovery.build('youtube', 'v3', developerKey = API_KEY)
まずは, 推しの投稿動画のIDを全部収集
実は、アップロード済み動画というのは、1つのプレイリストとして扱われている。
アップロード済み動画のプレイリストIDで、PlaylistItems を叩くとよい。
で、そのプレイリストのIDはというと、Channels を叩く。
Channnels を叩くためのIDは、チャンネルのURLからぶっこ抜く
例えば、下記太字の部分
https:// www.youtube.com/channel/UCI0vsOl2fo-h44RfA6ATUjw
プレイリストIDを取得するには、以下のように叩く
response = youtube.channels().list(
part="contentDetails",
id="UCI0vsOl2fo-h44RfA6ATUjw",
fields="items/contentDetails/relatedPlaylists/uploads"
).execute()
part
は欲しい物を書くパラメータ, 今回は contentDetails
を指定
さらに、fields
を指定することで、返ってくるパラメータを細かく決められるので、
ここに必要最小限欲しい物を書くことで、APIの使用量を抑えられる
この辺は、公式リファレンスの下の方で、試し打ちできるので、そちらで試したのちに実装すると良い
で、帰ってきたのはこちら
{'items': [{'contentDetails': {'relatedPlaylists': {'likes': '', 'uploads': 'UUI0vsOl2fo-h44RfA6ATUjw'}}}]}
これで、アップロード済み動画のプレイリストIDがUUI0vsOl2fo-h44RfA6ATUjw
であることがわかった。
ちょっと待って。
チャンネルID: UCI0vsOl2fo-h44RfA6ATUjw
プレイリストID: UUI0vsOl2fo-h44RfA6ATUjw
プレイリストIDって、チャンネルIDの先頭のUC
をUU
にしただけじゃん……
てことで、貴重なAPIを叩くんじゃなくて、先頭のUC
をUU
に置き換えるか
各動画のコメ欄を取得
プレイリストIDが分かったので、動画情報を一気にとっていく
↓みたいな関数をぶん回せばいい、VideoInfo
は実装上扱いやすくするための dataclass
def get_video_info_in_playlist(playlist_id: str) -> list[VideoInfo]:
# 参考: https://zenn.dev/yorifuji/articles/youtube-data-api-python
video_info_list = []
request = youtube.playlistItems().list(
part="snippet",
maxResults=100,
playlistId=playlist_id,
fields="nextPageToken,items/snippet(title,description,resourceId/videoId)"
)
while request:
response = request.execute()
video_info_list.extend(list(map(VideoInfo.from_response_snippet, response["items"])))
request = youtube.playlistItems().list_next(request, response)
return video_info_list
動画 IDが取れたので、コメントを一気にとっていく
コメントは, CommentThreads を叩いて取得する
普通のコメントと、リプライコメントが別になっているので、part
で、snippet,replies
と両方指定しておく
今回は、コメントの中身だけが大事なので、↓のように、普通のコメントもリプライも CommentInfo クラスに押し込んでいく
def get_comments(video_id: str) -> list[CommentInfo]:
comment_list = []
comment_field = "snippet(videoId,textDisplay,textOriginal)"
top_comment_f = f"items/snippet/topLevelComment/{comment_field}"
replies_f = f"items/replies/comments/{comment_field}"
request = youtube.commentThreads().list(
part="snippet,replies",
maxResults=100,
videoId=video_id,
fields=f"nextPageToken,{top_comment_f},{replies_f}"
)
while request:
response = request.execute()
c_mat = list(map(CommentInfo.response_item_to_comments, response["items"]))
comment_list.extend(sum(c_mat, []))
request = youtube.commentThreads().list_next(request, response)
return comment_list
取得したデータは適当にjsonで保存。後でこねくり回す。
タイムスタンプの抽出
肝心のコメントデータが手に入ったので、データをそのままctrl+f
してもお目当てのタイムスタンプを見つけることができる。
ただ、さすがに、生データを検索するのは使い勝手が悪いので、タイムスタンプに相当する部分だけ抜き出していく。
タイムスタンプコメント
コメントの生データ例は以下の通り
生データ例
{
"text_display": "☆タイムスタンプ☆\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h00m00s">0:00:00</a> 配信開始\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h01m39s">0:01:39</a> こんりーな\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h04m05s">0:04:05</a> 名前読み\r
\r
◇セットリスト\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h06m01s">0:06:01</a> メーベル\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h09m11s">0:09:11</a> 幽霊東京\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h12m37s">0:12:37</a> 春を告げる\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h15m52s">0:15:52</a> 夜のピエロ\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h19m13s">0:19:13</a> 夜に駆ける\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h23m20s">0:23:20</a> マーシャル・マキシマイザー\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h26m50s">0:26:50</a> 激動\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h31m44s">0:31:44</a> CORE PRIDE\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h36m05s">0:36:05</a> レディメイド\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h40m00s">0:40:00</a> サラマンダー\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h42m40s">0:42:40</a> 踊\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h46m13s">0:46:13</a> Mela!\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h50m11s">0:50:11</a> 正しくなれない\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h54m39s">0:54:39</a> 秒針を噛む\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h58m33s">0:58:33</a> 君の知らない物語\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h04m13s">1:04:13</a> からくりピエロ \r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h08m59s">1:08:59</a> Calc\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h13m00s">1:13:00</a> p.h.\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h15m45s">1:15:45</a> ボトム\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h19m17s">1:19:17</a> エバ\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h23m05s">1:23:05</a> ポッカデラベリタ\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h26m27s">1:26:27</a> フィクサー\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h30m28s">1:30:28</a> ビースト・ダンス\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h33m47s">1:33:47</a> 惑星ループ\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h37m02s">1:37:02</a> 太陽系ディスコ\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h40m23s">1:40:23</a> ダーリンダンス\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h43m45s">1:43:45</a> ベノム\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h47m02s">1:47:02</a> ルマ\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h50m07s">1:50:07</a> カブトムシ\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h55m20s">1:55:20</a> カワキヲアメク\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=1h59m34s">1:59:34</a> フリージア\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=2h06m23s">2:06:23</a> you\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=2h12m20s">2:12:20</a> お富さん\r
\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=2h21m09s">2:21:09</a> スパチャお礼\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=2h21m48s">2:21:48</a> 名前読み\r
<a href="https://www.youtube.com/watch?v=wfkmjpymbmE&t=2h23m07s">2:23:07</a> 海産!",
"text_original": "☆タイムスタンプ☆\r\n0:00:00 配信開始\r\n0:01:39 こんりーな\r\n0:04:05 名前読み\r\n\r\n◇セットリスト\r\n0:06:01 メーベル\r\n0:09:11 幽霊東京\r\n0:12:37 春を告げる\r\n0:15:52 夜のピエロ\r\n0:19:13 夜に駆ける\r\n0:23:20 マーシャル・マキシマイザー\r\n0:26:50 激動\r\n0:31:44 CORE PRIDE\r\n0:36:05 レディメイド\r\n0:40:00 サラマンダー\r\n0:42:40 踊\r\n0:46:13 Mela!\r\n0:50:11 正しくなれない\r\n0:54:39 秒針を噛む\r\n0:58:33 君の知らない物語\r\n1:04:13 からくりピエロ \r\n1:08:59 Calc\r\n1:13:00 p.h.\r\n1:15:45 ボトム\r\n1:19:17 エバ\r\n1:23:05 ポッカデラベリタ\r\n1:26:27 フィクサー\r\n1:30:28 ビースト・ダンス\r\n1:33:47 惑星ループ\r\n1:37:02 太陽系ディスコ\r\n1:40:23 ダーリンダンス\r\n1:43:45 ベノム\r\n1:47:02 ルマ\r\n1:50:07 カブトムシ\r\n1:55:20 カワキヲアメク\r\n1:59:34 フリージア\r\n2:06:23 you\r\n2:12:20 お富さん\r\n\r\n2:21:09 スパチャお礼\r\n2:21:48 名前読み\r\n2:23:07 海産!"
}
ご覧の通り、text_display
の方には、欲しいリンクがそのまま a タグで埋め込まれているので、これを取得できればいい。
あと、ついでにタイムスタンプの横の曲名も
てことで、上記のようなコメントを以下の正規表現チェッカーにぶちこんで、望みのテキストが得られるまでコネコネする。
結局、コメントを改行で区切ってから以下の正規表現を適用することで、リンク、00:00:00 を抽出した。
抽出したあと、リンク、00:00:00 を削除することで、曲名も抽出できた。
<a href=\\\"(https://www.youtube.com/watch\?v=[\w]+\&t=[\w]+)\\\">([\d:]+)</a>
最終的に手に入ったデータは以下の通り。
これなら、ctrl+f
でもなんとか運用できそう。
得られたデータ抜粋
{
"video_id": "wfkmjpymbmE",
"link": "https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h23m20s",
"timestamp": "0:23:20",
"text": "マーシャル・マキシマイザー"
},
{
"video_id": "wfkmjpymbmE",
"link": "https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h26m50s",
"timestamp": "0:26:50",
"text": "激動"
},
{
"video_id": "wfkmjpymbmE",
"link": "https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h31m44s",
"timestamp": "0:31:44",
"text": "CORE PRIDE"
},
{
"video_id": "wfkmjpymbmE",
"link": "https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h36m05s",
"timestamp": "0:36:05",
"text": "レディメイド"
},
{
"video_id": "wfkmjpymbmE",
"link": "https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h40m00s",
"timestamp": "0:40:00",
"text": "サラマンダー"
},
{
"video_id": "wfkmjpymbmE",
"link": "https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h42m40s",
"timestamp": "0:42:40",
"text": "踊"
},
{
"video_id": "wfkmjpymbmE",
"link": "https://www.youtube.com/watch?v=wfkmjpymbmE&t=0h46m13s",
"timestamp": "0:46:13",
"text": "Mela!"
},
課題
- 複数行にまたがる
ふざけたタイムスタンプ- 今回、1行に
00:00 hoge
かhoge 00:00
だけ書かれている場合だけを対象にした - なので、
1. 00:00\n hoge
のようなものには対応できていない。
- 今回、1行に
- web アプリへの応用
- 今回、Python で雑に書いたが、webアプリにできたらみんな使えていいと思う
- その場合、APIの使用量を節約するために、
- タイトルに
歌
が入ってる動画のコメントだけ収集 - 一度コメントを取得できた動画に関してはスキップする
- タイトルに
- 等の工夫も必要だろう
まとめ
そんな感じで、推しの歌枠で聞いたあの曲どこで聞いたかなぁを解決してみました。
思ったよりもYouTube API簡単に叩けましたね。
みなさんも、Vtuber の推し活にYouTube APIを活用してみてください。ノシ
Discussion