【R】YouTube 「にじさんじ」のライブ配信のチャット(配信終了後)を取得してみる
「にじさんじ」は、ライブ配信を結構やってるので、配信の時のチャットの内容の抽出をしてみる。
結論からいうと、思ったより難しい。APIでサクッと取れると思ったのだが、終了した配信のチャット内容を得るAPIは存在しない。
仕方ないので、スクレイピングで抽出することにした。
前提
前回の記事で作成した関数を使う。
「にじさんじ」のライブ配信した動画の取得
検索のAPIで、eventTypeで、検索対象をブロードキャストイベントに制限出来る。
completed:完了したブロードキャストのみを含める
live:アクティブなブロードキャストのみを含める
upcoming:今後配信予定のブロードキャストのみを含める
keyword <- "にじさんじ"
res_live <- executeYdApi(
"search",
params = c(key=api_key,
part="snippet",
q=keyword,
type="video",
order="viewCount",
maxResults=10,
eventType="completed")
)
動画ページを読み込む
Rでスクレイピングするには、rvestパッケージが便利である。
まず、1位の動画のページを読み込んでみる。
library(rvest)
# UserAgent
UA <- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
# 1位の動画のページのURL
video_url <- sprintf("https://www.youtube.com/watch?v=%s", res_live$items$id$videoId[1])
ページのソースを見てみると、JavaScriptで作成されているようで少し面倒であるが頑張って読み解く。
JavaScriptで、ytInitialDataという変数の中にチャットの情報が含まれていて、下記のようなURLの情報を読み込んでいる。
https://www.youtube.com/live_chat_replay?continuation=<continuation>
scriptタグの中にあるytInitialDataを定義しているところを特定して抽出。
抽出したテキストをJSON形式で読み込む。
リスト形式でデータが作成されるので、構造を読み取って、continuationを抜き取る。
getInitContinuation <- function(video_url){
res_html <- html_session(video_url, httr::user_agent(UA))
scripts <- res_html %>%
html_nodes("script")
content <- NULL
for(script in scripts){
content <- script %>%
html_text() %>%
stringr::str_trim()
if(grepl('ytInitialData', content)){
content <- gsub('var ytInitialData = ', "", content)
content <- gsub(";", "", content)
break
}
}
yt_init_data <- jsonlite::fromJSON(content)
continuation <- yt_init_data$contents$twoColumnWatchNextResults$
conversationBar$liveChatRenderer$header$liveChatHeaderRenderer$
viewSelector$sortFilterSubMenuRenderer$subMenuItems$continuation$
reloadContinuationData$continuation[1]
sprintf("https://www.youtube.com/live_chat_replay?continuation=%s", continuation)
}
live_chat_url <- getInitContinuation(video_url)
live_chat_url
チャット情報を読み込む
次に、live_chat_replayのページから情報を抽出する。構造に関してはソースで確認。
getChatInfo <- function(live_chat_url){
res_html <- html_session(live_chat_url, httr::user_agent(UA))
scripts <- res_html %>%
html_nodes("script")
content <- NULL
for (script in scripts) {
content <- script %>%
html_text() %>%
stringr::str_trim()
if (grepl('window\\["ytInitialData"\\]', content)) {
content <- gsub('window\\["ytInitialData"\\] = ', "", content)
content <- gsub(";", "", content)
break
}
}
jsonlite::fromJSON(content)
}
chat_info <- getChatInfo(live_chat_url)
チャットのメッセージと次のURLを作成する。
generateChatData <- function(chat_info){
# 配信開始からの経過時間
video_offset_time_msec <- chat_info$continuationContents$liveChatContinuation$
actions$replayChatItemAction$videoOffsetTimeMsec
# コメント情報
actions <- chat_info$continuationContents$liveChatContinuation$
actions$replayChatItemAction$actions
# コメント情報のデータフレーム作成
chat_df <- bind_rows(lapply(2:length(actions), function(i){
x <- actions[[i]]
if(!"addChatItemAction" %in% names(x)){
return(NULL)
}
msg_renderer <- x$addChatItemAction$item$liveChatTextMessageRenderer
authorName <- msg_renderer$authorName$simpleText
msg <- msg_renderer$message$runs[[1]]$text
if(is.null(authorName)){
authorName <- "Unknown"
}
if (is.null(msg)) {
msg <- ""
}
tibble(
offset_time_msec = video_offset_time_msec[i],
authorName = authorName,
comment = msg)
})) %>%
filter(comment != "")
# 次のチャット
next_continuation <- chat_info$continuationContents$liveChatContinuation$continuations$
liveChatReplayContinuationData$continuation[1]
list(chat_df=chat_df, next_continuation = next_continuation)
}
chat_data <- generateChatData(chat_info)
chat_data$chat_df %>% select(!authorName)
全体的な処理を実装する
ここまでで、部品は出来たので全体的な処理を実装する。
処理の流れを簡単に述べると、
1 動画ページを読み込み、最初のチャット情報のURLを取得
2 チャット情報のページを読み込み、チャットデータと次のURLを取得
3 次のURLがなくなるまで2を繰り返す。
generateChatDF <- function(video_url){
# 最初のチャットURLを取得
live_chat_url <- getInitContinuation(video_url)
i <- 1
chat_df_list <- list()
while(T){
try({
chat_info <- getChatInfo(live_chat_url)
chat_data <- generateChatData(chat_info)
chat_df_list[[i]] <- chat_data$chat_df
})
if(is.null(chat_data$next_continuation)){
break
}
next_live_chat_url <-
sprintf("https://www.youtube.com/live_chat_replay?continuation=%s",
chat_data$next_continuation)
if(i %% 20 == 0){
Sys.sleep(1)
}
if(live_chat_url == next_live_chat_url){
break
} else {
live_chat_url <- next_live_chat_url
i <- i + 1
}
}
bind_rows(chat_df_list)
}
正直、かなりエイヤで書いている。もう少し丁寧に精査したら本記事を修正したい。
実行結果
chat_df <- generateChatDF(video_url)
chat_df %>%
sample_n(10) %>%
select(!authorName)
おわりに
今回は、YouTubeのデータの中でも、APIで取得出来ない「終了したライブ配信動画のチャット]の取得を試してみた。
実行してる範囲ではデータは取れているが、スクレイピングは例外も多く、全てを網羅するのは難しい。
今回紹介した内容も私が確認した範囲では有効だが、ちょっと変わった事をやろうとすると動かないかも知れない。
とはいえ、大まかなところは使えると思うので、実際に試しながら、動かないところがあれば試行錯誤してほしい。
難しければ、ご相談頂ければ可能な限り対応したいと思っている。
この記事が気に入ったらサポートをしてみませんか?