爬取豆瓣书籍数据(基于R)

  • 爬取豆瓣书籍数据
  • 了解网页结构
  • 自动收集单个网页数据
  • 自动收集多个网页数据
  • 字符串切割,以提取需要的信息


爬取豆瓣书籍数据

网络爬虫,就是从网页中获取需要的信息,提取相应的数据。
可以利用R语言爬虫获取网页数据信息,便于统计分析。
常用的从网页中获取信息的包有RCurl,XML,rvest等 。还可以利用RSslenium包或者Rwebdriver包模拟浏览器爬取异步加载等较难爬取的网页信息。
本文便以爬取豆瓣电影数据为例,来描述网络爬虫过程。
爬取网址如下:
https://book.douban.com/top250?start=0
<本文是学习《R语言统计分析与机器学习》后的学习笔记>

了解网页结构

所需要的数据概况:

python 爬虫获取当当的书籍信息 爬取书籍数据_python 爬虫获取当当的书籍信息


从网页中大概能发现,该网页含有书籍名称,书籍评分,书籍主题,出版时间,价格,作者名字,译者名字等数据信息。

再进入网页源代码界面找规律

python 爬虫获取当当的书籍信息 爬取书籍数据_字符串_02


可以发现有十分一致的规律:

书籍名称:

<div class="pl2">
		<a href="https://book.douban.com/subject/1007305/" onclick="moreurl(this,{i:'0'})" title="红楼梦">      红楼梦
</a>

作者/译者/出版社/出版时间/价格:

</div>
<p class="pl">[清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元</p>

评分信息:

<div class="star clearfix">
      <span class="allstar50"></span>
      <span class="rating_nums">9.6</span>
      <span class="pl">(
                   278287人评价
                       )</span>
</div>

下面便利用发现的规律,利用R进行数据爬取。

自动收集单个网页数据

  1. 导入需要的包 ,导入需要的包;
library(dplyr)
library(rvest)
library(stringr)
  1. 读取网页,并进行数据提取 ,读取网页,程序如下:
alldata250<-data.frame()
    u1<-paste("https://book.douban.com/top250?start=",0,sep = "", collapse = NULL)#字符串拼接
    ## 读取网页
    top250book<-read_html(u1)
    ## 评分水平
    mark<-top250book %>% html_nodes("span.rating_nums") %>% html_text()
    #作品名称
    name<-top250book %>% html_nodes("div.pl2 a") %>% html_text()%>%str_trim()%>%str_replace_all("\n|","")%>%str_replace_all(" ","")
    head(name)
    #作者/出版社/日期/年份/价格
    place <-top250book%>% html_nodes("p.pl") %>% html_text()
    work<-data.frame(name,mark,place)#创建数据框
    #下面将place中的字符串拆开,便于分析
    alldata250<-rbind(alldata250,work)                     
}

运行结果

python 爬虫获取当当的书籍信息 爬取书籍数据_python 爬虫获取当当的书籍信息_03


此时,我们成功爬取了第一页的数据,那么如何利用程序自动爬取其他页面的数据呢?

我们可以观察网址的规律。

  1. 第一页:https://book.douban.com/top250?start=0
  2. 第二页:https://book.douban.com/top250?start=25
  3. 第三页:https://book.douban.com/top250?start=50
    我们可以发现加粗部分,存在以25递增的关系,其他的保持不变。故我们可以考虑利用循环的方式进行爬取。

自动收集多个网页数据

字符串的拼接,网址是一串较为复杂的字符串,但本例之中,大部分内容不改变,改变的仅仅是数字,故我们可以考虑利用字符串拼接的方式,得到不同页面的网址链接。
字符串拼接函数:

paste(......, sep = " ", collapse = NULL)

其中……表示一个或多个可以被转化为字符型的对象;
参数sep表示分隔符,默认为空格;
参数collapse可选,如果不指定值,那么函数paste的返回值是自变量之间通过sep指定的分隔符连接后得到的一个字符型向量; 如果为其指定了特定的值,那么自变量连接后的字符型向量会再被连接成一个字符串,之间通过collapse的值分隔。下面用具体的例子说明各参数的作用
本例中,对网页进行拼接可得到:

> i=9
> u1<-paste("https://book.douban.com/top250?start=",25*i,sep = "", collapse = NULL)#字符串拼接
> print(u1)
[1] "https://book.douban.com/top250?start=225"

其中i是从0开始增加,每个i对应一个网页页面,故可由i循环,以达到反复爬取网页数据的功能。
程序如下:

library(dplyr)
library(rvest)
library(stringr) 
alldata250<-data.frame()
for(i in 0:9){

    u1<-paste("https://book.douban.com/top250?start=",25*i,sep = "", collapse = NULL)#字符串拼接
    ## 读取网页
    top250book<-read_html(u1)

    ## 评分水平
    mark<-top250book %>% html_nodes("span.rating_nums") %>% html_text()
    #作品名称
    name<-top250book %>% html_nodes("div.pl2 a") %>% html_text()%>%str_trim()%>%str_replace_all("\n|","")%>%str_replace_all(" ","")
    head(name)
    #作者/出版社/日期/年份/价格
    place <-top250book%>% html_nodes("p.pl") %>% html_text()
    work<-data.frame(name,mark,place)#创建数据框
    #下面将place中的字符串拆开,便于分析
    alldata250<-rbind(alldata250,work)                       
}

运行结果如下:

python 爬虫获取当当的书籍信息 爬取书籍数据_数据分析_04


本例探究的是爬取网页中的文本数据,注意得从网页源代码找规律,同时注意对文本数据字符串的处理方法。

字符串切割,以提取需要的信息

**但由上面结果可发现,place一列,是多个信息数据以/为间隔组合在一起的,故我们设法将其拆分,以获得更准确的数据,便于统计分析。

可发现不同书籍对应的place部分数据个数并不相同:
其中“/”的数量就有2,3,4,5等四种情况,"/"数量不同,意味着数据个数也不同。比如,有些书籍并没有作者信息,有些书籍没有译者信息,有些书籍有两种价格。
尝试根据"/“的数量作为标签,对于不同的”/"数量的信息进行分别处理赋值。

#再对place中进行切分
    work<-alldata250
    label<-str_count(work$place,"/")#统计place中/符号的个数,因为有n个“/”意味着有n+1个数据
    work$label<-label#将label添加进数据框
    work2<-work[label==2,]#筛选有两个/的部分
    work3<-work[label==3,]#筛选有三个/的部分
    work4<-work[label>=4,]#筛选有四个以上/的部分

再利用strsplit()函数,根据/为间隔符,将其拆分开,使用格式如下:

#含有两个/的部分
 palce2<-as.character(work2$place)#将其转化为字符格式
 palce2<-strsplit(palce2,split="/")#根据/为间隔符,将其拆分
#含有三个/的部分
 palce3<-as.character(work3$place)#将其转化为字符格式
 palce3<-strsplit(palce3,split="/")#根据/为间隔符,将其拆分
#含有四个/的部分
 palce4<-as.character(work4$place)#将其转化为字符格式
 palce4<-strsplit(palce4,split="/")#根据/为间隔符,将其拆分

下面对各种情况进行观察,便于寻找规律正确赋值。
观察含有两个/的结构

> head(palce2)
[[1]]
[1] "中国基督教协会 "  " 中国基督教协会 " " 1996"           
[[2]]
[1] "中华书局 " " 2006-12 " " 9.80元"

可发现数量仅有2个,且格式不统一,故暂不作处理。
观察含有三个/的结构

> head(palce3)
[[1]]
[1] "[清] 曹雪芹 著 "" 人民文学出版社 "  " 1996-12 " " 59.70元"        
[[2]]
[1] "余华 "   " 作家出版社 " " 2012-8-1 "   " 20.00元"    
[[3]]
[1] "刘慈欣 "   " 重庆出版社 " " 2012-1-1 "   " 168.00元"   
[[4]]
[1] "[明] 罗贯中 " " 人民文学出版社 " " 1998-05 "    " 39.50元"        
[[5]]
[1] "林奕含 "  " 北京联合出版公司 " " 2018-1 "     " 45.00元"          
[[6]]
[1] "金庸 "   " 生活.读书.新知三联书店 " " 1994-5 "  " 96.0"

可发现每层第一个是作者信息,第二个是出版社信息,第三个是出版时间,第四个是价格。
观察含有四个以上/的结构

[[1]]
[1] "[哥伦比亚] 加西亚·马尔克斯 " " 范晔 "  " 南海出版公司 "   " 2011-6 "                   
[5] " 39.50元"                   

[[2]]
[1] "[英] 乔治·奥威尔 "    " 刘绍铭 "     " 北京十月文艺出版社 " " 2010-4-1 "     " 28.00"              

[[3]]
[1] "[美国] 玛格丽特·米切尔 " " 李美华 "     " 译林出版社 "            " 2000-9 "    " 40.00元"               

[[4]]
[1] "[日] 东野圭吾 "         " 刘姿君 "     " 南海出版公司 "          " 2013-1-1 "   " 39.50元"      

[[5]]
[1] "[英] 阿·柯南道尔 "      " 丁钟华 等 "    " 群众出版社 "           " 1981-8 "    " 53.00元"     "68.00元"          

[[6]]
[1] "[英] 乔治·奥威尔 "      " 荣如德 "       " 上海译文出版社 "       " 2007-3 "    " 10.00元"

可发现每层第一个是作者信息,第二个是译者信息,第三个是出版社信息,第四个是出版时间,第五个是价格,如果有第六个,那就是有两种价格,这里暂不考虑。
可以利用循环语句,对含不同”/“数量的信息分别进行提取。 程序如下:
含有两个"/"时:

#字符串中有2个/的
    writer<-c()
    public<-c()
    date<-c()
    price<-c()
    translate<-c()
    for(i in 1:length(palce2)){
      #writer<-c(writer,palce2[[i]][1])#每层中第一个为作家名称
      public<-c(public,palce2[[i]][1])#每层中第二个为出版社信息
      date<-c(date,palce2[[i]][2])#每层第三个是出版日期
      price<-c(price,palce2[[i]][3])#每层第四个是价格信息
    }
    other2<-data.frame(public,date,price)
    a<-length(date)
    other2$translate<-rep("NULL",a)#这部分没有翻译人员名称,所以填充为NULL
    other2$writer<-rep("NULL",a)#这部分没有翻译人员名称,所以填充为NULL

含有三个"/"时:

#字符串中有3个/的
    writer<-c()
    public<-c()
    date<-c()
    price<-c()
    translate<-c()
    
    for(i in 1:length(palce3)){
      writer<-c(writer,palce3[[i]][1])#每层中第一个为作家名称
      public<-c(public,palce3[[i]][2])#每层中第二个为出版社信息
      date<-c(date,palce3[[i]][3])#每层第三个是出版日期
      price<-c(price,palce3[[i]][4])#每层第四个是价格信息
    }
    other3<-data.frame(writer,public,date,price)
    a<-length(date)
    other3$translate<-rep("NULL",a)#这部分没有翻译人员名称,所以填充为NULL

含有四个"/"时:

#字符串中有4个/的
    writer<-c()
    public<-c()
    date<-c()
    price<-c()
    translate<-c()
    for(i in 1:length(palce4)){
      writer<-c(writer,palce4[[i]][1])#每层第一个是作家名称
      public<-c(public,palce4[[i]][3])#每层第三个是出版社信息
      date<-c(date,palce4[[i]][4])#每层第四个是出版日期
      price<-c(price,palce4[[i]][5])#每层第5个是价格信息
      translate<-c(translate,palce4[[i]][2])#每层第二个是翻译人员名字
    }
    
    other4<-data.frame(writer,public,date,price,translate)

再将得到的各情况下的数据进行按行合并整理:

other<-rbind(other2,other3,other4)#按行合并

尤其注意此时由于分类别操作已经改变了顺序,故得按照类别顺序进行合并,程序如下:

work<-rbind(work2,work3,work4)#按行合并
#再将work和other数据框按列合并,即可得到完整的数据
TOPdata<-cbind(work,other)#按列合并

结果如下:

python 爬虫获取当当的书籍信息 爬取书籍数据_数据分析_05


由此即可划分开字符串了,便于对其进行进一步的分析。