网络爬虫(web crawler),也被称为网络蜘蛛(web spider),是在万维网浏览网页并按照一定规则提取信息的脚本或者程序。
浏览网页时,一般流程如下:
利用网络爬虫爬取信息就是模拟这个过程.用脚本模仿浏览器,向网站服务器发出浏览网页内容的请求,在服务器检验成功后,返回网页的信息,然后解析网页并提取需要的数据,最后将提取得到的数据保存即可。
在数据量爆发式增长的互联网时代,网站与用户的沟通本质上是数据的交换:搜索引擎从数据库中提取搜索结果,将其展现在用户面前;电商将产品的描述、价格展现在网站上,以供买家选择心仪的产品;社交媒体在用户生态圈的自我交互下产生大量文本、图片和视频数据等。这些数据如果得以分析利用,不仅能够帮助第一方企业(拥有这些数据的企业)做出更好的决策,对于第三方企业也是有益的。而网络爬虫技术,则是大数据分析领域的第一个环节。
简单来说,平时在浏览网站时,所有能见到的数据都可以通过爬虫程序保存下来。从社交媒体的每一条发帖到团购网站的价格及点评,再到招聘网站的招聘信息,这些数据都可以存储下来。
网络爬虫这个名字虽然能够形象地描述这项技术,但是有关爬虫的利弊确实存在很多质疑。从目前的情况来看,如果抓取的数据属于个人使用或科研范畴,基本不存在问题;而如果数据属于商业盈利范畴,就要就事而论,有可能属于违法行为,也有可能不违法。 爬虫频繁地访问网站会导致该网站的资源被占用,甚至用户的个人信息和商业信息都受到侵害。
爬虫技术可能存在以下风险:
1)由于大量占用爬取网站的资源,对爬取网站造成访问困难,严重影响网站的可用性;
2)网站敏感信息的获取是否造成不良后果;
3)违背网站爬取设置。
网络爬虫的流程其实非常简单,主要可以分为三部分:
(1)获取网页;
(2)解析网页(提取数据);
(3)存储数据
(1)获取网页就是向服务器发送请求,服务器会返回整个网页的数据。类似于在浏览器中键入网址并按回车键;
发起请求之后服务器会对请求进行检验:因为大量的爬虫请求会造成服务器压力过大,可能使得服务器响应速度变慢,影响网站的正常运行。所以网站一般会检验请求头里面的User-Agent(以下简称UA,相当于身份的识别)来判断发起请求的是不是机器人,而我们可以通过自己设置UA来进行简单伪装。在没有UA的伪装下,服务器很容易就能识别出对方是一只网络爬虫的,所以有些网站在发现请求来自网络爬虫时将直接拒绝请求。
(2)解析网页就是从整个网页的数据中提取想要的数据。类似于你在页面中想找到产品的价格,价格就是你要提取的数据;
(3)存储数据也很容易理解,就是把数据存储下来。我们可以存储在csv中,也可以存储在数据库中。
一般情况下,爬取某个网页数据时,会首先使用R语言获取某个网页的源代码,查看所需要的的数据是否可以通过解析源代码获取。评论数据无法通过解析网页源代码获取,操作步骤如下:
1) 下载Google Chrome 浏览器
2)找到【更多工具】--【开发者工具】
3)打开【Network】面板
4)单击网页中的【商品评价】选项
5)CTRL+R 加载所有资源
6)第一列显示所有请求的文件名,找到【size】降序排列,因为评论数据一般比较大
7) 找到【productPageComments.action?】商品评论
8)右键单击,选择【open in new tab】
即可打开评论数据
找到上述评论数据后将url复制下来即可。也可在【Header】下找到【Request URL】复制链接
下载评论页面数据需要用到【RCurl】库中的【getURL】函数。
# 安装
install.packages('RCurl')
# 载入
library(RCurl)
提交请求并返回检索响应
getURL(url, ..., .opts = list(),
write = basicTextGatherer(.mapUnicode = .mapUnicode),
curl = getCurlHandle(), async = length(url) > 1,
.encoding = integer(), .mapUnicode = TRUE)
参数 | 含义 |
---|---|
url | 网址链接 |
… | 控制http请求的一些设置,如user-agent;没有完全理解该参数 |
.encodings | 请求返回内容的编码格式 |
关于getURL函数“…”中的具体内容,没有找到相关完整资料,尚不理解。
url <- 'https://club.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98&productId=100010083789&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&fold=1'
# install.packages('RCurl')
library(RCurl)
header <- c("User-Agent"="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/
537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36",
"content-encoding"="gzip", # 非必要
"content-type"="application/json", # 非必要
"charset"="GBK") # 非必要
tmp <- getURL(url, httpheader=header,.encoding='gbk')
tmp
通过httpheader传入浏览器UA,伪装成为浏览器。编码方式可以通过通过“content-type”中的取值查看。
这里我使用了GBK编码,但是出来依然是中文乱码,不清楚是不是跟返回结果是gzip压缩格式有关系。
为解决中文乱码问题,使用iconv函数转换字符编码格式
转换字符编码
iconv(x, from = "", to = "", sub = NA, mark = TRUE, toRaw = FALSE)
参数 | 含义 |
---|---|
x | 要转换的字符 |
from | 字符的现有编码方式 |
to | 字符的目标编码方式 |
sub | 不能转换为目标编码的字符将默认以NA返回,若通过sub指定,则返回sub后指定的字符 |
tmp <- iconv(tmp,"gbk","utf-8" )
tmp
由于读取到的源码不是标准的JSON格式,因此将符合JSON格式的内容提取出来:
cont <- substr(tmp,nchar('fetchJSON_comment98')+2,nchar(tmp)-2)
cont
截取字符
substr(x, start, stop)
参数 | 含义 |
---|---|
x | 要截取的字符串 |
start | 起始位置(包含) |
stop | 结束位置(包含) |
使用jsonlite库中的fromJSON函数将获取的标准JSON格式的内容转化为R语言的列表形式:
library(jsonlite)
web1 <- jsonlite::fromJSON(cont)
web1
# 取需要信息并转换成数据框形式
content <- data.frame(id=web1$comments$id,nickname=web1$comments$nickname,creationTime=web1$comments$creationTime,productColor=web1$comments$productColor,content=web1$comments$content)
content
要想实现循环爬取评论数据,顾名思义需要用到循环,包括URL与获取内容的保存两部分的循环
从上图可以看出,获取的评论数据以JSON格式存储,URL存在规律,https://club.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98&productId=100010083789&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&fold=1
“?”后面的为查询语句,每个参数之间均以“&”隔开,各查询参数说明如下:
参数名称 | 说明 |
---|---|
productId | 产品ID |
score | 评论类型,score=0表示获取全部评论,score=3表示获取好评,score=1表示获取差评 |
sortType | 评论排序方式,按推荐排序(sortType=5)或时间排序(sortType=6) |
page | 表示评论页码,默认从0开始递增 |
pagesize | 表示每页展示的评论量,默认为10 |
不同商品之间的主要不同在于"productId",同一商品不同页面的评论则可通过page参数实现
由于不同页面的评论只有page参数后面的内容不同,因此,构造不同页面的url如下:
cont_url <- data.frame(matrix(NA,nrow=3,ncol=1))
names(cont_url) <- 'url'
cont_url
for (i in 1:3){
cont_url[i,1] <- paste0('https://club.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98&productId=100010083789&score=0&sortType=5&page=',i-1,'&pageSize=10&isShadowSku=0&fold=1')
}
cont_url
将参数转换为字符串并拼接在一起
paste (..., sep = " ", collapse = NULL, recycle0 = FALSE)
paste0(..., collapse = NULL, recycle0 = FALSE)
参数 | 含义 |
---|---|
… | 需要拼接的R对象 |
sep | 分隔拼接对象之间的字符,默认为空格 |
collapse | 可选参数,如果为collapse指定了一个值,则结果中的值将被连接到一个字符串中,元素由collapse的值分隔。 |
recycle0 | 布尔值,不清楚 |
paste()函数默认以空格分隔连接的内容,而paste0()函数则在连接的内容中间不放空格,因此paste0()函数相当于paste()函数sep=“”,但是更高效。
a <- paste('a','b',1:2) # 默认以空格分隔
a
bb <- paste('a','b',1:2,sep="") # 指定拼接字符之间无空格
bb
dd <- paste0('a','b',1:2)
dd
按照2.2下载单个页面评论数据的思路,下载每个页面的评论数据,并提取需要的内容组成数据框,之后将每页内容的数据框放在list列表中。
cont_total <- list()
for (i in 1:3){
cont_each <- getURL(cont_url[i,1],httpheader=header,.encoding='gbk')
#print(cont_each)
cont_cheach <- iconv(cont_each,"gbk","utf-8" )
cont_cheach
cont_deal <- substr(cont_cheach,nchar('fetchJSON_comment98')+2,nchar(cont_cheach)-2)
#print(cont_deal)
cont_json <- jsonlite::fromJSON(cont_deal)
#print(cont_json)
cont_total[[i]] <- data.frame(id=cont_json$comments$id,nickname=cont_json$comments$nickname,creationtime=cont_json$comments$creationTime,
productcolor=cont_json$comments$productColor,content=cont_json$comments$content)
}
cont_total
tl <- data.frame(matrix(NA,nrow=0,ncol=5))
names(tl) <- c('id','nickname','creationtime','productcolor','content')
for (i in 1:3){
tl <- rbind(tl,cont_total[[i]])
}
tl
还可以使用更简单的累计合并函数Reduce()
实现给定向量元素的连续合并
Reduce(f, x, init, right = FALSE, accumulate = FALSE)
参数 | 含义 |
---|---|
f | 函数,对x执行的函数操作 |
x | 执行操作的向量对象,实践表明可以是列表 |
init | 初始值 |
right | 逻辑值,函数操作从左向右执行(默认)还是从右向左执行 |
accumulate | 逻辑值,是否显示每步累计操作结果,默认只显示最终操作结果 |
b <- Reduce(sum,c(1,2,3,4)) # 对向量中的内容进行sum函数操作,无初始值
b
d <- Reduce(sum,1:4,init=10) # 对向量中的内容进行sum函数操作,给定初始值10
d
e <- Reduce(sum,c(1,2,3,4),accumulate=TRUE) # 对向量中的内容进行sum函数操作,无初始值,显示每步结果
e
f <- Reduce(sum,c(1,2,3,4),init=10,accumulate=TRUE) # 对向量中的内容进行sum函数操作,给定初始值10,显示每步结果
f
可以使用Reduce()函数对cont_total进行行合并(rbind)操作,这样大大优化了代码:
tl <- Reduce(rbind,cont_total)
tl
参考资料: