
用R语言做网页爬虫和文本分析
受到这篇情感分析的文章和这篇网页爬虫指南的双重启发,我决定尝试抓取并分析 Goodreads 网站的书评数据。这个项目将会呈现一个从数据收集到机器学习建模分析的完整案例,我在中途犯下的错误也会一并呈现。本文将以5本流行的爱情故事书的评论为研究对象,我很自觉地选了同类型的书,使得评论具有可比性。这五本书也足够畅销,我们可以轻松获取上千条评论,如果你不喜欢爱情故事,你也可以选择其他类型的书做研究。
为了使这篇文章更易读,我把它分成了三个部分:
Part 1: 网页抓取
Part 2: 探索性数据分析和情感分析
Part 3: 基于机器学习的预测分析
这篇文章更新到了Part 1,后续部分会持续更新。
Part 1 网页抓取
Goodreads上的评论很容易抓取,在每条评论左侧都有一个非本文类型的排名变量。然而评论页面的切换是通过一个javascript按钮而不是html链接来实现的,处理起来有一点难度。不过好在这个问题有一个简单有效的解决方法,只要使用RSelenium包就可以了,点 这里 可以阅读该包的小品文。
起步
让我们先加载好要用的包并定义几个变量
library(data.table) # 为了rbindlist函数
library(dplyr) # 为了数据整理
library(magrittr) # 为了管道操作符 %>%
library(rvest) # 为了read_html函数
library(RSelenium) # 为了使用JavaScript进行网页抓取
url <- "https://www.goodreads.com/book/show/18619684-the-time-traveler-s-wife#other_reviews"
book.title <- "The time traveler's wife"
output.filename <- "GR_TimeTravelersWife.csv"
请注意,对每本书而言我需要改变上述变量的值并重新运行脚本。如果你觉得麻烦,可以用代码自动实现这个过程,但此处我就采取手动的做法。这么做也可以避免Goodreads'网站服务器过载。
让我们启动RSelenium服务器,利用Firefox浏览器可能会有些问题,为此我重新安装了一个比较旧的版本
startServer()
remDr <- remoteDriver(browserName = "firefox", port = 4444) # instantiate remote driver to connect to Selenium Server
remDr$open() # 打开浏览器
remDr$navigate(url)
这些指令会打开浏览器并转向你制定好的url,之后我们需要建立一个数据框,方便后续数据的操作。
global.df <- data.frame(book=character(),
reviewer = character(),
rating = character(),
review = character(),
stringsAsFactors = F)
现在万事俱备,可以开始网页抓取了。
网页抓取流程
为了提取我们需要的内容,对于每本书,我们将扫描其100页的评论。这里我去掉了循环,只扫描一页的内容,并对代码的工作原理逐行解释。
首先,我们需要定义书评在页面中的位置。使用SelectorGadget就能完成这一步骤,利用Chrome的一个拓展能帮助你识别CSS selector。只要找到了正确的CSS selector(这里是#bookReviews.stacked),将其传递给RSelenium服务器的findElements函数就可以了。
reviews <- remDr$findElements("css selector", "#bookReviews .stacked")
我们把书评的html代码先提取出来,然后再分离其中的内容。
reviews.html <- lapply(reviews, function(x){x$getElementAttribute("outerHTML")[[1]]})
reviews.list <- lapply(reviews.html, function(x){read_html(x) %>% html_text()} )
reviews.text <- unlist(reviews.list)
现在我们已经用list的格式保存了评论,然而其中依旧混杂着很多无关内容,我们需要利用正则表达式(regex)来清洗数据。
利用正则表达式清洗数据
依照我的文本分析经验,正则表达式既是天使也是魔鬼。通过它你可以用一行短短的命令就把字符串中所有的非字母元素移除,可它本身也是一门晦涩难懂的语言,使你在重读自己的代码时会倍感艰辛。所以如果你能读懂下面的代码做了些什么,我会倍感欣慰。
# 移除字母和符号外的元素
reviews.text2 <- gsub("[^A-Za-z\\-]|\\.+", " ", reviews.text)
# 移除换行符和多余的空格
reviews.clean <- gsub("\n|[ \t]+", " ", reviews.text2)
关于正则表达式,下面几个网址很有用:
http://www.regular-expressions.info/
http://stat545.com/block022_regular-expression.html
https://stat.ethz.ch/R-manual/R-devel/library/base/html/regex.html
用表格格式存储评论数据
现在我们已经得到了很干净的评论数据,然而由于html暗含的数据结构,我们会遇到这样的问题:对每一条评论,评论者的姓名和评分会存在同一个字符串里,而评论内容存在后一个字符串中。此外,预览评论系统会使得每个评论的开头在字符串中重复出现两次。我们需要对这些做做处理,再次使用正则表达式,我们将得到表格格式的数据。
我们先数数一共有多少条评论(即字符串数量的一半),然后建立一个临时数据框来存储数据。
n <- floor(length(reviews)/2)
reviews.df <- data.frame(book = character(n),
reviewer = character(n),
rating = character(n),
review = character(n),
stringsAsFactors = F)
我们遍历所有的字符串,逐评论地提取需要的内容并存在数据框里。这里我们采用for循环实现遍历,但如果是工业级应用,你应该更喜欢向量化处理。
下面的代码可能有点难懂,我先来解释下:
1. 第一部分,我先列举了可能出现在评论人姓名和评分之间的一些表达式,再结合正则表达式来确定姓名的结束位置,以此提取姓名。
2. 第二部分,我列举了可以出现在评分之后的表达式,有时这些表达式并不会出现,因此我得把这一情况也考虑进去。通过这两种方式,我们就可以提取评分了。
3. 第三部分,我把每个评论的开头移除了,我会记录50个字符重复出现的起始位置和结束位置。有时,评论可能篇幅较短还不到50字符,这里就用和第二部分相似的方法处理。
4. 最后,请注意这个循环的结构,我并没有一一循环字符串,而是遍历了评论,每个评论包含两个字符串,因此用2*j和2*j-1索引。
for(j in 1:n){
reviews.df$book[j] <- book.title
# 提取评论人姓名
auth.rat.sep <- regexpr(" rated it | marked it | added it ",
reviews.clean[2*j-1])
reviews.df$reviewer[j] <- substr(reviews.clean[2*j-1], 5, auth.rat.sep-1)
# 提取评分
rat.end <- regexpr("· | Shelves| Recommend| review of another edition",
reviews.clean[2*j-1])
if (rat.end==-1){rat.end <- nchar(reviews.clean[2*j-1])}
reviews.df$rating[j] <- substr(reviews.clean[2*j-1], auth.rat.sep+10, rat.end-1)
# 移除评论中重复的部分
short.str <- substr(reviews.clean[2*j], 1, 50)
rev.start <- unlist(gregexpr(short.str, reviews.clean[2*j]))[2]
if (is.na(rev.start)){rev.start <- 1}
rev.end <- regexpr("\\.+more|Blog", reviews.clean[2*j])
if (rev.end==-1){rev.end <- nchar(reviews.clean[2*j])}
reviews.df$review[j] <- substr(reviews.clean[2*j], rev.start, rev.end-1)
}
现在我们的临时数据框已经填写完毕,我们可以把它的内容转移到主数据框中了。
global.lst <- list(global.df, reviews.df)
global.df <- rbindlist(global.lst)
最后,我们需要告诉RSelenium点击进入下一页的按钮,通过传递利用SelectorGadget定义CSS selector可以实现这个功能。同时,Relenium的效率比较低,可能在循环中不能及时响应,因此我们在每个循环的末尾让R等待3秒。
NextPageButton <- remDr$findElement("css selector", ".next_page")
NextPageButton$clickElement()
Sys.sleep(3)
结束所有循环后,我们要把最终结果保存成一个文件。
write.csv(global.df,output.filename)
最终结果如下:
数据分析咨询请扫描二维码
若不方便扫码,搜微信号:CDAshujufenxi
Python HTTP 请求工具对比:urllib.request 与 requests 的核心差异与选择指南 在 Python 处理 HTTP 请求(如接口调用、数据爬取 ...
2025-09-12解决 pd.read_csv 读取长浮点数据的科学计数法问题 为帮助 Python 数据从业者解决pd.read_csv读取长浮点数据时的科学计数法问题 ...
2025-09-12CDA 数据分析师:业务数据分析步骤的落地者与价值优化者 业务数据分析是企业解决日常运营问题、提升执行效率的核心手段,其价值 ...
2025-09-12用 SQL 验证业务逻辑:从规则拆解到数据把关的实战指南 在业务系统落地过程中,“业务逻辑” 是连接 “需求设计” 与 “用户体验 ...
2025-09-11塔吉特百货孕妇营销案例:数据驱动下的精准零售革命与启示 在零售行业 “流量红利见顶” 的当下,精准营销成为企业突围的核心方 ...
2025-09-11CDA 数据分析师与战略 / 业务数据分析:概念辨析与协同价值 在数据驱动决策的体系中,“战略数据分析”“业务数据分析” 是企业 ...
2025-09-11Excel 数据聚类分析:从操作实践到业务价值挖掘 在数据分析场景中,聚类分析作为 “无监督分组” 的核心工具,能从杂乱数据中挖 ...
2025-09-10统计模型的核心目的:从数据解读到决策支撑的价值导向 统计模型作为数据分析的核心工具,并非简单的 “公式堆砌”,而是围绕特定 ...
2025-09-10CDA 数据分析师:商业数据分析实践的落地者与价值创造者 商业数据分析的价值,最终要在 “实践” 中体现 —— 脱离业务场景的分 ...
2025-09-10机器学习解决实际问题的核心关键:从业务到落地的全流程解析 在人工智能技术落地的浪潮中,机器学习作为核心工具,已广泛应用于 ...
2025-09-09SPSS 编码状态区域中 Unicode 的功能与价值解析 在 SPSS(Statistical Product and Service Solutions,统计产品与服务解决方案 ...
2025-09-09CDA 数据分析师:驾驭商业数据分析流程的核心力量 在商业决策从 “经验驱动” 向 “数据驱动” 转型的过程中,商业数据分析总体 ...
2025-09-09R 语言:数据科学与科研领域的核心工具及优势解析 一、引言 在数据驱动决策的时代,无论是科研人员验证实验假设(如前文中的 T ...
2025-09-08T 检验在假设检验中的应用与实践 一、引言 在科研数据分析、医学实验验证、经济指标对比等领域,常常需要判断 “样本间的差异是 ...
2025-09-08在商业竞争日益激烈的当下,“用数据说话” 已从企业的 “加分项” 变为 “生存必需”。然而,零散的数据分析无法持续为业务赋能 ...
2025-09-08随机森林算法的核心特点:原理、优势与应用解析 在机器学习领域,随机森林(Random Forest)作为集成学习(Ensemble Learning) ...
2025-09-05Excel 区域名定义:从基础到进阶的高效应用指南 在 Excel 数据处理中,频繁引用单元格区域(如A2:A100、B3:D20)不仅容易出错, ...
2025-09-05CDA 数据分析师:以六大分析方法构建数据驱动业务的核心能力 在数据驱动决策成为企业共识的当下,CDA(Certified Data Analyst) ...
2025-09-05SQL 日期截取:从基础方法到业务实战的全维度解析 在数据处理与业务分析中,日期数据是连接 “业务行为” 与 “时间维度” 的核 ...
2025-09-04在卷积神经网络(CNN)的发展历程中,解决 “梯度消失”“特征复用不足”“模型参数冗余” 一直是核心命题。2017 年提出的密集连 ...
2025-09-04