YouTube 评论抓取工具 returns 有限的结果
YouTube comment scraper returns limited results
任务:
我想从给定视频中抓取所有 YouTube 评论。
我成功地改编了上一个问题 (Scraping Youtube comments in R) 中的 R 代码。
代码如下:
library(RCurl)
library(XML)
x <- "https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo/comments?orderby=published"
html = getURL(x)
doc = htmlParse(html, asText=TRUE)
txt = xpathSApply(doc,
"//body//text()[not(ancestor::script)][not(ancestor::style)[not(ancestor::noscript)]",xmlValue)
要使用它,只需将视频 ID(即“4H9pTgQY_mo”)替换为您需要的 ID。
问题:
问题是它没有 return 所有评论。事实上,无论视频中有多少评论,它总是 return 是一个包含 283 个元素的向量。
任何人都可以阐明这里出了什么问题吗?这是令人难以置信的沮丧。谢谢你。
XML
包的替代方案是 rvest
包。使用您提供的 URL,抓取评论将如下所示:
library(rvest)
x <- "https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo/comments?orderby=published"
x %>%
html %>%
html_nodes("content") %>%
html_text
其中returns一个字符向量的注释:
[1] "That Andorra player was really Ruud.."
[2] "This just in; Karma is a bitch."
[3] "Legend! Haha B)"
[4] "When did Van der sar ran up? He must have run real fast!"
[5] "What a beast Ruud was!"
...
有关 rvest
的更多信息,请参见 here。
您的问题在于获得最大结果。
求解算法
首先你需要调用 url <a href="https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo?v=2" rel="nofollow">https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo?v=2</a>
这个 url 包含视频评论计数的信息,从那里提取那个数字并用它来迭代超过。
<gd:comments><gd:feedLink ..... countHint='1797'/></gd:comments>
之后使用它来迭代思想 url 和这两个参数 <a href="https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo/comments?max-results=50&start-index=1" rel="nofollow">https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo/comments?max-results=50&start-index=1</a>
当您迭代时,您需要将起始索引从 1,51,101,151 更改为...是否测试了 max-result
它限制为 50。
我(在大多数情况下)能够通过使用最新版本的 Youtube Data API 和 R 包 httr
来完成此任务。我采用的基本方法是向适当的 URL 发送多个 GET
请求,并以 100 个为一组获取数据(API 允许的最大值)——即
base_url <- "https://www.googleapis.com/youtube/v3/commentThreads/"
api_opts <- list(
part = "snippet",
maxResults = 100,
textFormat = "plainText",
videoId = "4H9pTgQY_mo",
key = "my_google_developer_api_key",
fields = "items,nextPageToken",
orderBy = "published")
当然,key
是您实际的 Google 开发人员密钥。
初始批次是这样检索的:
init_results <- httr::content(httr::GET(base_url, query = api_opts))
##
R> names(init_results)
#[1] "nextPageToken" "items"
R> init_results$nextPageToken
#[1] "Cg0Q-YjT3bmSxQIgACgBEhQIABDI3ZWQkbzEAhjVneqH75u4AhgCIGQ="
R> class(init_results)
#[1] "list"
第二个元素 - items
- 是第一批的实际结果集:它是一个长度为 100 的列表,因为我们在 GET 请求中指定了 maxResults = 100
。第一个元素 - nextPageToken
- 是我们用来确保每个请求 returns 适当的结果序列的元素。例如,我们可以这样得到接下来的 100 个结果:
api_opts$pageToken <- gsub("\=","",init_results$nextPageToken)
next_results <- httr::content(
httr::GET(base_url, query = api_opts))
##
R> next_results$nextPageToken
#[1] "ChYQ-YjT3bmSxQIYyN2VkJG8xAIgACgCEhQIABDI3ZWQkbzEAhiSsMv-ivu0AhgCIMgB"
其中当前请求的pageToken
作为先前请求nextPageToken
返回,我们得到一个新的 nextPageToken
用于获取下一批结果。
这很简单,但是在每次发送请求后都必须手动更改 nextPageToken
的值显然会非常乏味。相反,我认为这对于简单的 R6 class 是一个很好的用例:
yt_scraper <- setRefClass(
"yt_scraper",
fields = list(
base_url = "character",
api_opts = "list",
nextPageToken = "character",
data = "list",
unique_count = "numeric",
done = "logical",
core_df = "data.frame"),
methods = list(
scrape = function() {
opts <- api_opts
if (nextPageToken != "") {
opts$pageToken <- nextPageToken
}
res <- httr::content(
httr::GET(base_url, query = opts))
nextPageToken <<- gsub("\=","",res$nextPageToken)
data <<- c(data, res$items)
unique_count <<- length(unique(data))
},
scrape_all = function() {
while (TRUE) {
old_count <- unique_count
scrape()
if (unique_count == old_count) {
done <<- TRUE
nextPageToken <<- ""
data <<- unique(data)
break
}
}
},
initialize = function() {
base_url <<- "https://www.googleapis.com/youtube/v3/commentThreads/"
api_opts <<- list(
part = "snippet",
maxResults = 100,
textFormat = "plainText",
videoId = "4H9pTgQY_mo",
key = "my_google_developer_api_key",
fields = "items,nextPageToken",
orderBy = "published")
nextPageToken <<- ""
data <<- list()
unique_count <<- 0
done <<- FALSE
core_df <<- data.frame()
},
reset = function() {
data <<- list()
nextPageToken <<- ""
unique_count <<- 0
done <<- FALSE
core_df <<- data.frame()
},
cache_core_data = function() {
if (nrow(core_df) < unique_count) {
sub_data <- lapply(data, function(x) {
data.frame(
Comment = x$snippet$topLevelComment$snippet$textDisplay,
User = x$snippet$topLevelComment$snippet$authorDisplayName,
ReplyCount = x$snippet$totalReplyCount,
LikeCount = x$snippet$topLevelComment$snippet$likeCount,
PublishTime = x$snippet$topLevelComment$snippet$publishedAt,
CommentId = x$snippet$topLevelComment$id,
stringsAsFactors=FALSE)
})
core_df <<- do.call("rbind", sub_data)
} else {
message("\n`core_df` is already up to date.\n")
}
}
)
)
可以这样使用:
rObj <- yt_scraper()
##
R> rObj$data
#list()
R> rObj$unique_count
#[1] 0
##
rObj$scrape_all()
##
R> rObj$unique_count
#[1] 1673
R> length(rObj$data)
#[1] 1673
R> ##
R> head(rObj$core_df)
Comment User ReplyCount LikeCount PublishTime
1 That Andorra player was really Ruud..<U+feff> Cistrolat 0 6 2015-03-22T14:07:31.213Z
2 This just in; Karma is a bitch.<U+feff> Swagdalf The Obey 0 1 2015-03-21T20:00:26.044Z
3 Legend! Haha B)<U+feff> martyn baltussen 0 1 2015-01-26T15:33:00.311Z
4 When did Van der sar ran up? He must have run real fast!<U+feff> Witsakorn Poomjan 0 0 2015-01-04T03:33:36.157Z
5 <U+003c>b<U+003e>LOL<U+003c>/b<U+003e> F Hanif 5 19 2014-12-30T13:46:44.028Z
6 Fucking Legend.<U+feff> Heisenberg 0 12 2014-12-27T11:59:39.845Z
CommentId
1 z123ybioxyqojdgka231tn5zbl20tdcvn
2 z13hilaiftvus1cc1233trvrwzfjg1enm
3 z13fidjhbsvih5hok04cfrkrnla2htjpxfk
4 z12js3zpvm2hipgtf23oytbxqkyhcro12
5 z12egtfq5ojifdapz04ceffqfrregdnrrbk
6 z12fth0gemnwdtlnj22zg3vymlrogthwd04
正如我之前提到的,这让你 几乎 一切 - 大约 1790 条评论中的 1673 条。出于某种原因,它似乎无法捕获用户的嵌套回复,而且我不太确定如何在 API 框架内指定它。
我之前设置了一个 Google Developer account a while back for using the Google Analytics API, but if you haven't done that yet, it should be pretty straightforward. Here's an overview - 你不需要设置 OAuth 或类似的东西,只需创建一个项目并创建一个新的 Public API 访问关键。
我在 R 中使用 "tuber" 包尝试了不同的视频,结果在这里。
如果一个作者只有回复(没有关于视频的评论),那么根据回复数 behave.If 作者没有超过 5 个回复那么不要刮 anyone.But 如果有超过 5 个回复那么一些评论正在刮。
如果一位作者既有自己的评论又有回复,那么第二个人(我告诉过的)评论正在被抓取。
任务:
我想从给定视频中抓取所有 YouTube 评论。
我成功地改编了上一个问题 (Scraping Youtube comments in R) 中的 R 代码。
代码如下:
library(RCurl)
library(XML)
x <- "https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo/comments?orderby=published"
html = getURL(x)
doc = htmlParse(html, asText=TRUE)
txt = xpathSApply(doc,
"//body//text()[not(ancestor::script)][not(ancestor::style)[not(ancestor::noscript)]",xmlValue)
要使用它,只需将视频 ID(即“4H9pTgQY_mo”)替换为您需要的 ID。
问题:
问题是它没有 return 所有评论。事实上,无论视频中有多少评论,它总是 return 是一个包含 283 个元素的向量。
任何人都可以阐明这里出了什么问题吗?这是令人难以置信的沮丧。谢谢你。
XML
包的替代方案是 rvest
包。使用您提供的 URL,抓取评论将如下所示:
library(rvest)
x <- "https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo/comments?orderby=published"
x %>%
html %>%
html_nodes("content") %>%
html_text
其中returns一个字符向量的注释:
[1] "That Andorra player was really Ruud.."
[2] "This just in; Karma is a bitch."
[3] "Legend! Haha B)"
[4] "When did Van der sar ran up? He must have run real fast!"
[5] "What a beast Ruud was!"
...
有关 rvest
的更多信息,请参见 here。
您的问题在于获得最大结果。
求解算法
首先你需要调用 url <a href="https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo?v=2" rel="nofollow">https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo?v=2</a>
这个 url 包含视频评论计数的信息,从那里提取那个数字并用它来迭代超过。
<gd:comments><gd:feedLink ..... countHint='1797'/></gd:comments>
之后使用它来迭代思想 url 和这两个参数 <a href="https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo/comments?max-results=50&start-index=1" rel="nofollow">https://gdata.youtube.com/feeds/api/videos/4H9pTgQY_mo/comments?max-results=50&start-index=1</a>
当您迭代时,您需要将起始索引从 1,51,101,151 更改为...是否测试了 max-result
它限制为 50。
我(在大多数情况下)能够通过使用最新版本的 Youtube Data API 和 R 包 httr
来完成此任务。我采用的基本方法是向适当的 URL 发送多个 GET
请求,并以 100 个为一组获取数据(API 允许的最大值)——即
base_url <- "https://www.googleapis.com/youtube/v3/commentThreads/"
api_opts <- list(
part = "snippet",
maxResults = 100,
textFormat = "plainText",
videoId = "4H9pTgQY_mo",
key = "my_google_developer_api_key",
fields = "items,nextPageToken",
orderBy = "published")
当然,key
是您实际的 Google 开发人员密钥。
初始批次是这样检索的:
init_results <- httr::content(httr::GET(base_url, query = api_opts))
##
R> names(init_results)
#[1] "nextPageToken" "items"
R> init_results$nextPageToken
#[1] "Cg0Q-YjT3bmSxQIgACgBEhQIABDI3ZWQkbzEAhjVneqH75u4AhgCIGQ="
R> class(init_results)
#[1] "list"
第二个元素 - items
- 是第一批的实际结果集:它是一个长度为 100 的列表,因为我们在 GET 请求中指定了 maxResults = 100
。第一个元素 - nextPageToken
- 是我们用来确保每个请求 returns 适当的结果序列的元素。例如,我们可以这样得到接下来的 100 个结果:
api_opts$pageToken <- gsub("\=","",init_results$nextPageToken)
next_results <- httr::content(
httr::GET(base_url, query = api_opts))
##
R> next_results$nextPageToken
#[1] "ChYQ-YjT3bmSxQIYyN2VkJG8xAIgACgCEhQIABDI3ZWQkbzEAhiSsMv-ivu0AhgCIMgB"
其中当前请求的pageToken
作为先前请求nextPageToken
返回,我们得到一个新的 nextPageToken
用于获取下一批结果。
这很简单,但是在每次发送请求后都必须手动更改 nextPageToken
的值显然会非常乏味。相反,我认为这对于简单的 R6 class 是一个很好的用例:
yt_scraper <- setRefClass(
"yt_scraper",
fields = list(
base_url = "character",
api_opts = "list",
nextPageToken = "character",
data = "list",
unique_count = "numeric",
done = "logical",
core_df = "data.frame"),
methods = list(
scrape = function() {
opts <- api_opts
if (nextPageToken != "") {
opts$pageToken <- nextPageToken
}
res <- httr::content(
httr::GET(base_url, query = opts))
nextPageToken <<- gsub("\=","",res$nextPageToken)
data <<- c(data, res$items)
unique_count <<- length(unique(data))
},
scrape_all = function() {
while (TRUE) {
old_count <- unique_count
scrape()
if (unique_count == old_count) {
done <<- TRUE
nextPageToken <<- ""
data <<- unique(data)
break
}
}
},
initialize = function() {
base_url <<- "https://www.googleapis.com/youtube/v3/commentThreads/"
api_opts <<- list(
part = "snippet",
maxResults = 100,
textFormat = "plainText",
videoId = "4H9pTgQY_mo",
key = "my_google_developer_api_key",
fields = "items,nextPageToken",
orderBy = "published")
nextPageToken <<- ""
data <<- list()
unique_count <<- 0
done <<- FALSE
core_df <<- data.frame()
},
reset = function() {
data <<- list()
nextPageToken <<- ""
unique_count <<- 0
done <<- FALSE
core_df <<- data.frame()
},
cache_core_data = function() {
if (nrow(core_df) < unique_count) {
sub_data <- lapply(data, function(x) {
data.frame(
Comment = x$snippet$topLevelComment$snippet$textDisplay,
User = x$snippet$topLevelComment$snippet$authorDisplayName,
ReplyCount = x$snippet$totalReplyCount,
LikeCount = x$snippet$topLevelComment$snippet$likeCount,
PublishTime = x$snippet$topLevelComment$snippet$publishedAt,
CommentId = x$snippet$topLevelComment$id,
stringsAsFactors=FALSE)
})
core_df <<- do.call("rbind", sub_data)
} else {
message("\n`core_df` is already up to date.\n")
}
}
)
)
可以这样使用:
rObj <- yt_scraper()
##
R> rObj$data
#list()
R> rObj$unique_count
#[1] 0
##
rObj$scrape_all()
##
R> rObj$unique_count
#[1] 1673
R> length(rObj$data)
#[1] 1673
R> ##
R> head(rObj$core_df)
Comment User ReplyCount LikeCount PublishTime
1 That Andorra player was really Ruud..<U+feff> Cistrolat 0 6 2015-03-22T14:07:31.213Z
2 This just in; Karma is a bitch.<U+feff> Swagdalf The Obey 0 1 2015-03-21T20:00:26.044Z
3 Legend! Haha B)<U+feff> martyn baltussen 0 1 2015-01-26T15:33:00.311Z
4 When did Van der sar ran up? He must have run real fast!<U+feff> Witsakorn Poomjan 0 0 2015-01-04T03:33:36.157Z
5 <U+003c>b<U+003e>LOL<U+003c>/b<U+003e> F Hanif 5 19 2014-12-30T13:46:44.028Z
6 Fucking Legend.<U+feff> Heisenberg 0 12 2014-12-27T11:59:39.845Z
CommentId
1 z123ybioxyqojdgka231tn5zbl20tdcvn
2 z13hilaiftvus1cc1233trvrwzfjg1enm
3 z13fidjhbsvih5hok04cfrkrnla2htjpxfk
4 z12js3zpvm2hipgtf23oytbxqkyhcro12
5 z12egtfq5ojifdapz04ceffqfrregdnrrbk
6 z12fth0gemnwdtlnj22zg3vymlrogthwd04
正如我之前提到的,这让你 几乎 一切 - 大约 1790 条评论中的 1673 条。出于某种原因,它似乎无法捕获用户的嵌套回复,而且我不太确定如何在 API 框架内指定它。
我之前设置了一个 Google Developer account a while back for using the Google Analytics API, but if you haven't done that yet, it should be pretty straightforward. Here's an overview - 你不需要设置 OAuth 或类似的东西,只需创建一个项目并创建一个新的 Public API 访问关键。
我在 R 中使用 "tuber" 包尝试了不同的视频,结果在这里。 如果一个作者只有回复(没有关于视频的评论),那么根据回复数 behave.If 作者没有超过 5 个回复那么不要刮 anyone.But 如果有超过 5 个回复那么一些评论正在刮。 如果一位作者既有自己的评论又有回复,那么第二个人(我告诉过的)评论正在被抓取。