はじめに
この度、タイトルにあるような技術を使ったアプリを作ったので、そのやり方について説明します。Twitchの動画情報取得以外は非公式のAPIを使っているのでいつの間にか変更されているかもしれません。Twitchも2020年4月30日にAPIが変わるみたいです。なお、情報は2020年2月8日の時点です。
実装はC#でしましたが、具体的な実装方法についてはここに書きません。(参考にしたところだとC#とPythonが多かった)
※2022/05/15:各サイトが更新されて使えなくなっていたため内容を修正。
コメントスクレイピングの基礎
スクレイピングはそれなりに相手のサーバーに負荷をかけます。スクレイピング間隔を設け、気をつけましょう。
①公式APIを探す
まずはここから探すのが基本です。まとまった形式のデータを取得するのにも、相手のサーバーの負荷を減らすのにもまずはここから調べましょう。
②通信から調べる
公式APIに目的のものが無く、ほしい情報があるときは、Chromeの開発者ツールのNetworkingを監視します。特にコメントの場合は、常にダウンロードしなければコメントが流れていきません。目的の情報をここで見つけましょう。
③リクエストヘッダを確認
どのところでコメント取得しているか確認したら、リクエストヘッダを見て何の情報が必要か確認します。場合によっては、クエリパラメータ、ボディ、クッキーが必要になることもあります。
④必要な情報を取得
困ったら、元のhtmlを見るといいかもしれません。意外と中に書いてあるかも。
YouTube Live
動画情報
動画の情報は、動画のページをGETで取得するとすべて書かれています。どこに書かれているかというと、以下から始まる<script>
要素のところです。
<script>var ytplayer = ytplayer || {};ytplayer.config= //以下省略
そして、このytplayer.config
にJSONを代入しているわけなんですが、そのJSONのargs.player_response
に文字列化されたJSONとして動画情報(というよりページの情報)が入っています。そこから抽出しましょう。
抽出には、以下のような正規表現を使うといいかと思います。
Regex.Match("文字列","\\\"目的のキー\\\":\\\"[^\\]+\\\""); // 「\」 多すぎw
動画の長さを調べる場合には以下のようになります。
Regex.Match("文字列","\\\"lengthSeconds\\\":\\\"[^\\]+\\\"");
// \"lengthSeconds\":\"***\"
コメント
まず、YouTube Liveのコメントはブラウザからでないと見れません。なので、リクエストヘッダのUser-Agent
にブラウザの物に変えてやります。Chromeであれば、Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36
です。
YouTube Liveのコメントはどこにアクセスして取得すればいいのかというと、動画のページに埋め込まれています。live-chat-iframe
というidの振られた要素のsrc
属性がコメントのあるURLです。そこにGETリクエストを送ればコメント(の一部)が取得できます。
レスポンスはhtmlで返ってきて、コメントは、script
要素で、window["ytInitialData"]
から始まるものにJSONとして代入されています。window["ytInitialData"] =
と;
を取り除いてJSONとして処理しましょう。
コメントのデータはそのJSONのcontinuationContents.liveChatContinuation.actions
の配列の最初のものを飛ばしたものとなっています。コメントは3種類あって、普通のコメント、スパチャ1、スパチャ2です。
普通のコメント
コメントは、continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatTextMessageRenderer.message
に中に入っていて、simpletext
またはruns
の配列となっています。
投稿された時間は、continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatTextMessageRenderer.timestampText.simpleText
に、xx:xxという形で入っています。
投稿主は、continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatTextMessageRenderer.authorName.simpleText
の中に入っています。
スパチャ1
コメントは、continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatPaidMessageRenderer.message
に中に入っていて、simpletext
またはruns
の配列となっています。
投稿された時間は、continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatPaidMessageRenderer.timestampText.simpleText
に、xx:xxという形で入っています。
投稿主は、continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatPaidMessageRenderer.authorName.simpleText
の中に入っています。
スパチャ金額は、
continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatPaidMessageRenderer.purchaseAmountText.simpleText
の中に¥x,xxxという形で入っています。
スパチャ2
コメントは、continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatPaidStickerRenderer.message
に中に入っていて、simpletext
またはruns
の配列となっています。
投稿された時間は、continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatPaidStickerRenderer.timestampText.simpleText
に、xx:xxという形で入っています。
投稿主は、continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatPaidStickerRenderer.authorName.simpleText
の中に入っています。
スパチャ金額は、
continuationContents.liveChatContinuation.actions[n].replayChatItemAction.actions[0].addChatItemAction.item.liveChatPaidStickerRenderer.purchaseAmountText.simpleText
の中に¥x,xxxという形で入っています。
次のURL
動画ページにのっているURLからは動画が始まってから一定数のコメントしか取得できません。次のURLに移る必要があります。その次のURLはhttps://www.youtube.com/live_chat_replay?continuation=
にcontinuationContents.liveChatContinuation.continuations[0].liveChatReplayContinuationData.continuation
の中身を足したものです。
参考サイト
PythonでYouTube Liveのアーカイブからチャット(コメント)を取得する
PythonでYouTube Liveのアーカイブからチャット(コメント)を取得する(改訂版)
ニコ動
動画情報
動画の情報は、非公式APIのhttps://ext.nicovideo.jp/api/getthumbinfo/
に動画のid(smxxxxxxxとかいうやつ)を足したところにGETリクエストを送るとXMLで帰ってきます。以下がレスポンスの例。
<?xml version="1.0" encoding="UTF-8"?>
<nicovideo_thumb_response status="ok">
<thumb>
<video_id>ビデオid</video_id>
<title>タイトル</title>
<description>動画説明</description>
<thumbnail_url>サムネイルのあるURL</thumbnail_url>
<first_retrieve>投稿日時</first_retrieve>
<length>x:xx</length>
<movie_type>mp4</movie_type>
<size_high>11155011</size_high>
<size_low>10519950</size_low>
<view_counter>視聴回数</view_counter>
<comment_num>コメント数</comment_num>
<mylist_counter>マイリス数</mylist_counter>
<last_res_body>最後に投稿されたコメント</last_res_body>
<watch_url>動画のURL</watch_url>
<thumb_type>video</thumb_type>
<embeddable>1</embeddable>
<no_live_play>0</no_live_play>
<tags domain="jp">
<tag category="1" lock="1">いろんなタグ</tag>
<tag lock="1">いろんなタグ</tag>
<tag>いろんなタグ</tag>
<tag>いろんなタグ</tag>
</tags>
<genre>ジャンル</genre>
<user_id>投稿者のuser_id</user_id>
<user_nickname>投稿者の名前</user_nickname>
<user_icon_url>投稿者のアイコン</user_icon_url>
</thumb>
</nicovideo_thumb_response>
コメント
コメントを取得する非公式APIのエンドポイントは https://nmsg.nicovideo.jp/api.json/
https://nvcomment.nicovideo.jp/legacy/api.json/
です。(legacyついているから変更されそう。)ここに適切なコマンドをPOSTするとその時点での動画に表示されるコメントが返ってきます。約1000コメントが要約されて返ってきます。
コマンドを作るための情報取得
コマンドを作るための情報は、動画ページのhtmlにのっています。js-initial-watch-data
とidの振られた要素のdata-api-data
属性の値にhtmlエンコードされたJSONとして情報がのっています。
使うのは、 context.userkey
comment.keys.userKey
と commentComposite.threads
comment.threads
の各要素です。comment.threads
で使うのは、isActive
、label
、id
、fork
、isLeafRequired
、isThreadkeyRequired
です。
コマンド作成
コマンドは、isActive
がtrue
になっているものだけ作ります。最小のコマンドを基礎とし、isLeafRequired
、isThreadkeyRequired
を見てさらにコマンドを追加します。
最小コマンド
コマンドの中で最小(というか基礎)となるのは以下の通りです。
[
{
"ping": {
"content": "rs:0" //コマンド送信ごとにインクリメント
}
},
{
"ping": {
"content": "ps:0" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"thread": {
"thread": "", // id を入れる
"version": "20090904",
"fork": , // fork を入れる
"language": 0,
"user_id": "", // ニコニコのユーザーidを入れる。空欄だとゲスト。
"with_global": 1,
"scores": 1,
"nicoru": 3,
"userkey": "" // context.userkey を入れる
}
},
{
"ping": {
"content": "pf:0" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"ping": {
"content": "rf:0" //コマンド送信ごとにインクリメント
}
}
]
isLeafRequiredがtrueのとき
isLeafRequiredがtrueだと、基礎のコマンドに加え、追加のコマンドが必要です。その結果、以下のようになります。
[
{
"ping": {
"content": "rs:1" //コマンド送信ごとにインクリメント
}
},
{
"ping": {
"content": "ps:6" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"thread": {
"thread": "", // id を入れる
"version": "20090904",
"fork": , // fork を入れる
"language": 0,
"user_id": "", // ニコニコのユーザーidを入れる。空欄だとゲスト。
"with_global": 1,
"scores": 1,
"nicoru": 3,
"userkey": "" // context.userkey を入れる
}
},
{
"ping": {
"content": "pf:6" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"ping": {
"content": "ps:7" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"thread_leaves": {
"thread": "", // id を入れる
"language": 0,
"user_id": "", // ニコニコのユーザーidを入れる。空欄だとゲスト。
"content": "0-x:100,y,nicoru:100", //xは動画の長さ(分切り上げ)、yは x<=1 だと100、x<=5 だと 250、x<=10だと500、それ以上だと1000っぽい。
"scores": 1,
"nicoru": 3,
"userkey": "" // context.userkey を入れる
}
},
{
"ping": {
"content": "pf:7" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"ping": {
"content": "rf:1" //コマンド送信ごとにインクリメント
}
}
]
isThreadkeyRequiredがtrueのとき
要は公式動画のときですが、threadkey
と force184
が必要になります。 http://flapi.nicovideo.jp/api/getthreadkey?thread=
にid
を足したところにGETリクエストを送るとレスポンスとして以下のように返ってきます。
threadkey=****&force_184=*
先ほど解析したhtmlの comment.threads
の threadkey
と is184Forced
にあります。
これらを使います。そして、コマンドが以下のように変わります。isLeafRequiredもtrueのときとして書いているので、isLeafRequiredがfalseのときは前半のみで大丈夫です。
[
{
"ping": {
"content": "rs:2" //コマンド送信ごとにインクリメント
}
},
{
"ping": {
"content": "ps:12" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"thread": {
"thread": "", // id を入れる
"version": "20090904",
"fork": , // fork を入れる
"language": 0,
"user_id": "", // ニコニコのユーザーidを入れる。空欄だとゲスト。
"force_184": "", // trueのとき1、falseのとき0を入れる
"with_global": 1,
"scores": 1,
"nicoru": 3,
"threadkey": "" // threadkeyを入れる
}
},
{
"ping": {
"content": "pf:12" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"ping": {
"content": "ps:13" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"thread_leaves": {
"thread": "", // id を入れる
"language": 0,
"user_id": "", // ニコニコのユーザーidを入れる。空欄だとゲスト。
"content": "0-x:100,y,nicoru:100", //xは動画の長さ(分切り上げ)、yは x<=1 だと100、x<=5 だと 250、x<=10だと500、それ以上だと1000っぽい。
"scores": 1,
"nicoru": 3,
"force_184": "", // trueのとき1、falseのとき0を入れる
"threadkey": "" // threadkeyを入れる
}
},
{
"ping": {
"content": "pf:13" //コマンドごとにインクリメント。送信で5とか10とかインクリメント
}
},
{
"ping": {
"content": "rf:2" //コマンド送信ごとにインクリメント
}
}
]
さらにコメントを取得する
コメントをさらに取得することもできます。ニコニコには、時期を戻ってコメントを取得する機能があるので、そちらを使えば別のコメントも取得できます。(ログイン必須となりました。)それにはwhen
とwaybackkey
が必要です。
ログインは、https://account.nicovideo.jp/login/redirector
へフォームとして mail_tel:ログインするメールアドレス
、password:パスワード
を送信すればセッションのクッキーが返ってくるので、それを使えばログイン状態を維持できます。
when
は、戻りたいときのUNIX時間、waybackkey
はhttps://flapi.nicovideo.jp/api/getwaybackkey?thread=
にid
を足したところにGETリクエストを送ると以下のようなレスポンスが返ってきます。
waybackkey=*
このwaybackkey
です。
when
はlanguage
プロパティの後に数値として、waybackkey
はコマンドの最後に文字列としてそれぞれ追加したコマンドを作成すると更なるコメントを取得できます。
##参考サイト
ニコニコ動画のコメントをJSONで取得したり投稿したり
python3系でニコニコ動画のコメントを取得してみた.
ニコ生
動画情報
ニコ生で動画の情報は、動画のあるhtmlページに埋め込まれています。embedded-data
というidのdata-props
という属性にJSONがhtmlエンコードされて埋まっています。動画情報は、program
以下にあります。
コメント
ニコ生でのコメント取得ではWebSocket+JSONで行います。WebSocketですが、ニコ動と同じようにコマンドを送り、レスポンスが返ってきます。まずは、動画情報等が入っているJSONのsite.relive.webSocketUrl
+ "&frontend_id="
+ site.frontendId
というところにWebSocketで接続してコメントを取得するためのデータを取得します。以下のようなコマンドを送ると、いくつか正常なレスポンスが返ってきます。
{
"type": "watch",
"body": {
"command": "getpermit",
"requirement": {
"broadcastId": "320976328",
"route": "",
"stream": {
"protocol": "hls",
"requireNewStream": true,
"priorStreamQuality": "low",
"isLowLatency": true,
"isChasePlay": false
},
"room": {
"isCommentable": true,
"protocol": "webSocket"
}
}
}
}
{
"type": "startWatching",
"data": {
"reconnect": "false",
"room": {
"commentable": true,
"protocol": "webSocket"
}
},
"stream": {
"chasePlay": "false",
"latency": "low",
"protocol": "hls",
"abr": "abr"
}
}
}
必要なレスポンスは以下の通りです。
{
"type": "watch",
"body": {
"room": {
"messageServerUri": "",
"messageServerType": "niwavided",
"roomName": "アリーナ 最前列",
"threadId": "",
"forks": [
0
],
"importedForks": [],
"isFirst": true,
"waybackkey": ""
},
"command": "currentroom"
}
}
{
"type": "room",
"data": {
"name": "アリーナ",
"messageServer": {
"uri": "",
"type": "niwavided"
},
"threadId": "",
"isFirst": true,
"waybackkey": "waybackkey", // waybackkeyとなっているときは取得していない
"vposBaseTime": ""
}
}
ここで必要なのは、 body.room.messageServerUri
、body.room.threadId
、body.room.waybackkey
data.threadId
、data.messageServer.uri
です。
{
"type": "watch",
"body": {
"command": "watchinginterval",
"params": [
"30"
]
}
}
{
"type": "seat",
"data": {
"keepIntervalSec": "30"
}
}
この body.params
data.keepIntervalSec
の値が、コメントを取得する間隔(秒数)です。この場合だと、30秒ずつのコメントが取得されるので、コメントの取得する部分は30秒ずつずらします。
また、一定時間ごとにpingが飛んでくるのでpongを返しましょう。
さて、ここからコメント取得をしていきます。そのためには動画ページのJSONのprogram.beginTime
も使います。まずは、data.messageServer.uri
にWebSocketでアクセスします。そして、以下のようなコマンドを送ります。
[
{
"ping": {
"content": "rs:0" //例のごとくコマンドを送信するとインクリメントされる
}
},
{
"ping": {
"content": "ps:0" //例のごとくコマンドごとにインクリメントされ、コマンド送信ごとに5とか10増える
}
},
{
"thread": {
"thread": "", // data.threadIdを入れる
"version": "20061206",
"when": , //どの時点からコメントをさかのぼるか。最初はprogram.beginTimeを入れればいい
"user_id": "", //ニコニコのユーザーidを入れる。空欄だとゲスト。
"res_from": , //どのコメントまでさかのぼるか。0以下だと、コメント数、1以上だとコメントNo。最初は-200とか入れればいい。
"with_global": 1,
"scores": 1,
"nicoru": 0,
"waybackkey": "" // data.waybackkeyを入れる
}
},
{
"ping": {
"content": "pf:0" //例のごとくコマンドごとにインクリメントされ、コマンド送信ごとに5とか10増える
}
},
{
"ping": {
"content": "rf:0" //例のごとくコマンドを送信するとインクリメントされる
}
}
]
そうすると、以下のレスポンスが返ってきます。
{
"thread": {
"resultcode": 0,
"thread": 1653481833,
"last_res": 511,
"ticket": "0x8116d80",
"revision": 1,
"server_time": 1581592898
}
}
この、thread.last_res
が次のコマンドを作るのに必要です。このthread.last_res
は受信する最後のコメントNoです。次にコマンドを送信する際は、thread.res_from
をthread.last_res
に1足して、thread.when
をbody.params
だけ足したコマンドを送信すれば連続的なコメントを取得できます。
コメント自体は以下のようなJSONで送られます。
{
"chat": {
"thread": ,
"vpos": , //コメントが表示される時間。基準はprogram.vposBaseTimeであって、動画での時間ではないのに注意
"date": ,
"date_usec":,
"mail": "",
"user_id": "",
"anonymity": 1,
"locale": "ja-jp",
"content": "" //コメントの内容
}
}
Twitch
Twitchは公式APIがしっかりしているので、そちらをまず参考にしましょう。
まずは、Twitch Developerとして登録します。そして、アプリを登録します。OAuthはnew APIでは必要になってくるので作りましょう。認証についての公式の説明はこちらです。
アプリごとに、クライアントIDが振られるので記録しておきます。また、クライアントの秘密(シークレットキー)を生成できます。こちらはユーザーの届かないところで使いましょう。これを使うことによってアプリケーションキーを生成でき、これをAPIの認証(Bearer)に使うことによってAPIの使用できる量が大幅に増します。
動画情報
※2020年4月30日に置き換わるAPIについて解説していきます。すでに使えます。
動画情報を得るnew APIのURLはhttps://api.twitch.tv/helix/videos
です。クエリパラメータ―として、video_id
、user_id
、game_id
を与えることができます。video_id
は動画のURLの末尾の数字です。
このURLに、リクエストヘッダとしてClient-ID:xxx
を付け加えて(Bearer認証は任意)GETリクエストを送ると以下のようなレスポンスが返ってきます。
{
"data": [{
"id": "", //動画のid
"user_id": "", //user_id
"user_name": "", //ユーザーの名前
"title": "", //動画のタイトル
"description": "", //動画の説明
"created_at": "", //動画が作られた日時
"published_at": "", //動画が公開された日時
"url": "", //動画のURL
"thumbnail_url": "", //サムネイルのURL
"viewable": "public", //公開されているかどうか
"view_count": , //視聴回数
"language": "en", //言語
"type": "archive",
"duration": "" //動画の長さ
}],
"pagination":{"cursor":"eyJiIjpudWxsLCJhIjoiMTUwMzQ0MTc3NjQyNDQyMjAwMCJ9"}
}
コメント
コメントについては公式APIがありませんでした。なのでブラウザと同じように動かします。
コメントを取得するには、https://api.twitch.tv/v5/videos/
+ 動画id + /comments
にGETリクエストを送ると取得できます。クエリパラメーターでどこのコメントを拾うか決めれます。
クエリパラメーターはcontent_offset_seconds=
に数値でコメントの拾い始める時間を、cursor=
にコメントidでそのコメント(も含めた)以降のコメントを取得できます。
コメントは、comments
に配列として入っています。レスポンスの次のコメントidは_next
に入っています。
comments.content_offset_seconds
に動画内の位置、comments.commenter.display_name
に表示名、comments.commenter.name
に登録名、comments.message.body
にコメントの中身が入っています。
チアーは、コメントの中身にCheer
+ 数字が入っているかで判断するしかありませんでした。
おわりに
スクレイピング記事にすると見づらいし、長いですね…