# 安装必要的包(如果尚未安装)
if (!require("gh", quietly = TRUE)) install.packages("gh")
if (!require("webshot2", quietly = TRUE)) install.packages("webshot2")
if (!require("dplyr", quietly = TRUE)) install.packages("dplyr")
if (!require("stringr", quietly = TRUE)) install.packages("stringr")
if (!require("tibble", quietly = TRUE)) install.packages("tibble")
if (!require("httr", quietly = TRUE)) install.packages("httr")
if (!require("jsonlite", quietly = TRUE)) install.packages("jsonlite")
# 加载包
library(gh)
library(webshot2)
library(dplyr)
library(stringr)
library(tibble)
library(httr)
library(jsonlite)
# 创建webshot文件夹(如果不存在)
if (!dir.exists("www/webshot")) {
dir.create("www/webshot", recursive = TRUE)
}
About
创建一个 R 脚本,用于从 GitHub Issue #1 中提取回复,整理为表格,并对内容中的链接生成网页截图。
准备工作
首先,我们需要安装并加载一些必要的R包,包括 gh
、webshot2
、dplyr
、stringr
、tibble
、httr
和 jsonlite
。如果你尚未安装这些包,可以运行以下代码来安装它们:
这些包的作用如下:
gh
:用于访问 GitHub APIwebshot2
:用于生成网页截图dplyr
:用于数据处理stringr
:用于字符串处理tibble
:用于创建数据框httr
:用于发起 HTTP 请求jsonlite
:用于处理 JSON 数据
获取评论
接下来,我们将使用 GitHub API 获取指定仓库的 Issue #1 的所有评论。
首先,将以下代码中的 repo_owner
、repo_name
和 issue_number
替换为要爬取的仓库和 Issue 编号:
# 指定仓库和Issue编号
# 请替换为你需要爬取的仓库
<- "D2RS-2025spring"
repo_owner <- "myHomePages"
repo_name <- 1
issue_number
# 使用GitHub API获取issue评论
<- gh::gh(
issue_comments "GET /repos/{owner}/{repo}/issues/{issue_number}/comments",
owner = repo_owner,
repo = repo_name,
issue_number = issue_number,
.limit = Inf
)
在这里使用 .limit = Inf
来获取所有评论,如果评论数量较多,可能需要等待一段时间。
打印原始 Issue 内容
在获取 ISSUE comments 的方法中,并不会包含第一条的评论(即 Issue 的标题和内容),所以我们需要单独获取 Issue 的标题和内容,打印原始 Issue 的标题和内容,以便了解 Issue 的背景信息。
# 获取原始issue内容
<- gh::gh(
issue_data "GET /repos/{owner}/{repo}/issues/{issue_number}",
owner = repo_owner,
repo = repo_name,
issue_number = issue_number
)
::cat_rule("原始Issue内容")
clicat(issue_data$title, "\n")
cat(issue_data$body, "\n")
创建辅助函数
get()
函数用于从评论内容中提取匹配的内容。如果没有匹配,会打印警告;如果有多个匹配,会保留第一个;如果只有一个匹配,直接返回。
get_id()
这里使用的正则表达式为 (?<!\\d)\\d{13}(?!\\d)
,可以匹配 13 位数字,前后不是数字的情况。其中 (?<!\\d)
表示前面不是数字,\\d{13}
表示匹配 13 位数字,(?!\\d)
表示后面不是数字。
get_link()
这里使用的正则表达式为 https?://[^\\s()<>\"\\[\\]]+
,可以匹配直接 URL。其中 https?://
表示匹配 http://
或 https://
,[^\\s()<>\"\\[\\]]+
表示匹配除空格、括号、尖括号、引号和方括号之外的字符。
create_webshot()
用于生成网页截图,如果文件已经存在且大小大于 1 kb,则跳过。如果文件不存在或大小小于 1 kb,则尝试生成网页截图。如果生成成功,则打印成功信息;如果生成失败,则打印失败信息。
= function(content, pattern){
get <- str_extract_all(content, pattern)[[1]]
matches <- unique(matches)
matches <- NULL
match if (length(matches) < 1) {
# 如果没有匹配,打印警告
warning(glue::glue("{content} 中未检测到: {pattern}"))
else if (length(matches) > 1) {
} # 如果有多个匹配,只取第一个
<- matches[1]
match message(glue::glue("{content} 中检测到多个匹配: `{pattern}`。保留第一个。"))
else {
} # 如果只有一个匹配,直接取出
= matches[1]
match
}return(match)
}
= function(content){
get_id get(content, pattern = "(?<!\\d)\\d{13}(?!\\d)")
}
= function(content){
get_link get(content, pattern = "https?://[^\\s()<>\"\\[\\]]+")
}
= function(link, verbose = TRUE) {
create_webshot # valid link
if (is.null(link) || !str_detect(link, "^https?://")) {
warning(glue::glue("Invalid link: {link}"))
return(NULL)
}
# 创建一个安全的文件名
<- paste0("www/webshot/",
safe_filename str_replace_all(gsub("https?://", "", link), "[^a-zA-Z0-9]", "_"),
".png")
# 如果文件已经存在且大小大于 1 kb,跳过
if (file.exists(safe_filename) & file.size(safe_filename) > 1000) {
if (verbose) message(sprintf("跳过截图: %s (文件已存在)\n", link))
else {
} # 尝试截图
tryCatch({
::webshot(url = link,
webshot2file = safe_filename,
delay = 5)
if (verbose) message(sprintf("成功截图: %s\n", link))
error = function(e) {
}, if (verbose) warning(sprintf("截图失败: %s, 错误: %s\n", link, e$message))
})
}
= gsub("www/", "", safe_filename)
safe_filename return(safe_filename)
}
整理评论
利用 lapply
函数将评论整理为一个列表,然后使用 bind_rows
函数将列表转换为 tibble,方便后续处理。
# 创建一个列表来存储评论信息
= lapply(issue_comments, function(comment) {
comments_list # 提取评论信息
= comment$user$login
author = comment$body
content = comment$html_url
content_url = comment$created_at
time = get_id(comment$body)
id = get_link(comment$body)
link = create_webshot(link, verbose = FALSE)
screenshot_path
# 返回一个 tibble
tibble(
author = author,
content = content,
content_url = content_url,
time = time,
id = id,
link = link,
screenshot_path = screenshot_path
)
})
# 将列表转换为 tibble
= bind_rows(comments_list) comments_df
comments_df
是一个数据框,包含了评论的作者、内容、评论链接和时间。
保存结果
最后,将整理好的评论数据保存为CSV文件,并输出处理结果。对于存在多个链接的评论(),将多个链接用分号分隔,合并为一个评论。
# 保存结果到CSV
write.csv(comments_df, "github_issue_comments.csv", row.names = FALSE, fileEncoding = "UTF-8")
# 输出结果
cat("处理完成! 共处理了", nrow(comments_df), "条评论\n")
cat("结果已保存到 github_issue_comments.csv\n")
print(comments_df)
小结
这个 R 脚本会完成以下任务:
- 安装并加载必要的R包
- 创建一个 webshot 文件夹用于保存网页截图
- 从 GitHub API 获取 Issue #1 的内容和所有评论
- 将评论整理成一个表格,包含
author
、content
和time
列等 - 从评论内容中提取所有链接和学号
- 使用
webshot2
包访问链接并生成网页截图 - 将截图保存在
webshot
文件夹中 - 将截图的文件路径添加到表格中
- 将结果保存为CSV文件
附加
利用md5检测当前目录下面的 png 文件,删掉重复的。然后将 png 文件重命名,去掉文件名开始的 \d+_\d+_
。
可以使用 R 处理当前目录下的 PNG 文件,完成以下任务:
- 计算 MD5 哈希值并删除重复的 PNG 文件
- 重命名 PNG 文件,去掉文件名开头的
\d+_\d+_
以下是 R 代码:
library(digest)
# 计算文件 MD5 哈希值
= function(file_path) {
get_md5 digest(file_path, algo = "md5", file = TRUE)
}
# 删除重复的 PNG 文件
= function(directory) {
remove_duplicates = list.files(directory, pattern = "\\.png$", full.names = TRUE, ignore.case = TRUE)
files = list()
seen_hashes
for (file in files) {
= get_md5(file)
file_hash
if (file_hash %in% names(seen_hashes)) {
message("删除重复文件: ", file)
file.remove(file)
else {
} = file
seen_hashes[[file_hash]]
}
}
}
# 重命名 PNG 文件,去掉开头的 \d+_\d+_
= function(directory) {
rename_files = list.files(directory, pattern = "\\.png$", full.names = TRUE, ignore.case = TRUE)
files
for (file in files) {
= basename(file)
filename = sub("^\\d+_\\d+_", "", filename)
new_name
if (new_name != filename) {
= file.path(dirname(file), new_name)
new_path message("重命名: ", file, " -> ", new_path)
file.rename(file, new_path)
}
}
}
# 运行
= "webshot"
current_directory remove_duplicates(current_directory)
rename_files(current_directory)
说明:
get_md5(file_path)
: 计算 PNG 文件的 MD5 哈希值。remove_duplicates(directory)
: 通过 MD5 识别重复 PNG 文件并删除。rename_files(directory)
: 通过正则表达式^\\d+_\\d+_
处理文件名,去掉前缀。