查看原文
其他

近三十年6000部国产电视剧告诉了我们些什么 | R爬虫&可视化第四季

徐麟 R语言中文社区 2019-04-22

作者:徐麟,数据分析师,就职于上海唯品会。热爱数据挖掘和分析,喜欢用R、Python玩点不一样的数据。个人公众号:数据森麟(微信ID:shujusenlin) 


往期回顾:

卫视实时收视率对比 | R爬虫&可视化第1季

同花顺股票分数可视化 | R爬虫&可视化第3季

当古代文人参加“中国好诗人”节目 | R爬虫&可视化第2季

前言

本期内容是一个我个人非常感兴趣的话题,通过爬取6000部国产电视剧(包括大陆和港台剧集,不包括动画片、纪录片、短片)的相关豆瓣信息,使得一直想要做的电视剧分析得以成行。本次内容依旧分为数据爬取和数据分析两部分内容,其中分析包括了整体分析,内容分词和演员分数排名。


数据爬取

Part1:豆瓣电视剧ID提取


相信豆瓣数据的爬取对于很多人来说都不陌生,我本人学习爬虫的初衷就是为了爬取豆瓣的数据。由于豆瓣电视剧集的ID并没有特定的规律,并且是电视剧和电影、纪录片ID进行了混排,所以如何获取一份全面的剧集ID清单是第一个需要解决的问题。

之后发现豆瓣的分类栏目中有电视剧的清单,如下图所示:

剧集列表的全部展示是通过最下面加载更多的按钮进行展开,不点击加载更多无法获取隐藏的剧集目录,如下图所示:

豆瓣每一次点击加载更多可以展示出额外20个点击剧集,按照6000计算,需要点击300次,所以我们要做的就是通过RSelenium的模拟点击功能做到自动的实现:

  1. ##打开浏览器

  2. remDr$open()

  3. url <- paste('https://movie.douban.com/tag/#/?sort=T&range=0,

  4.                  10&tags=%E5%8F%B0%E6%B9%BE,%E7%94%B5%E8%A7%86%E5%89%A7')

  5. remDr$navigate(url)

  6. ##模拟打开操作点击加更多操作

  7. while(1==1){

  8.  ##默认能够找到加载更多按钮时就一直进行,直到加载完成跳出循环

  9.  Sys.sleep(2)

  10.  b <- remDr$findElement('xpath','//*[@id="app"]/div/div[1]/a')

  11.  trynext <- try(b$clickElement(),silent=TRUE)

  12.  if ('try-error' %in% class(trynext)) next

  13.  }


后来我们通过这份清单得到了大陆+香港+台湾共6500多部剧集的ID。然而如大家所见,舌尖上的中国这种纪录片也出现在了电视剧的分类下面,同样的情况也发生在了动画片中,基于该情况我们后期会根据剧集分类和剧情总结进行筛选。

另一个问也同样需要解决,在我们查看剧集清单时发现猎场这部最近热播剧集没有出现在清单中。同样的问题也发生在其他的一些热播剧,可能是网站的标签匹配没有更新,我们需要在热播剧集的目录中采用图样的方法,网页如下图:

Part2:剧集信息的爬取


有了剧集的ID,我们就可以开始爬取电视剧的详细信息了,豆瓣每个网页内部的html代码还是非常的规整。所以相比于获取ID,根据ID获取剧集信息并不复杂。

只是要注意的是豆瓣对爬取进行了限速,目前采取的还是最原始的方法就是限速,每分钟控制在30次以下,正在学习如何解决这类问题。

代码在下方展示,我们一次性的获取了剧集基本信息(集数、地区、年份、时长等)、剧情总结用于后面分词、演员列表用于之后演员分数统计。

  1. ## 读取之前存储的ID清单

  2. drama_list <- read.csv('电视剧列表.csv',stringsAsFactors=FALSE)

  3. ## 建立数据框,之后循环填补数据

  4. drama_all <- drama_list

  5. names(drama_all)[1] <- 'id'

  6. names(drama_all)[2] <- 'name'

  7. drama_all$genres <-0

  8. drama_all$year <- 0

  9. drama_all$episodes <- 0

  10. drama_all$area <- 0

  11. drama_all$time <- 0

  12. drama_all$score <- 0

  13. drama_all$rating_count <- 0

  14. drama_all$actors <- 0

  15. drama_all$summary <- 0


  16. ## 循环爬取剧集数据

  17. for(i in 1:nrow(drama_all)){

  18.  ## 防止爬虫超速

  19.  Sys.sleep(3)

  20.  trynext <- try({

  21.    ## 获得网页信息并提取

  22.    api_loc <- sprintf('https://movie.douban.com/subject/%d/',

  23.                       drama_list$id[i])

  24.    loc <- getURL(api_loc)

  25.    html <- htmlParse(loc)


  26.    ## 得到剧集总结

  27.    this_summary <- gsub(' ','', sapply(getNodeSet(html,

  28.                         '//span[@property="v:summary"]'),xmlValue))

  29.    drama_all$summary[i] <- paste(this_summary,'',sep='')


  30.    ## 得到剧集分数、评论数量

  31.    this_score <- sapply(getNodeSet(html,'//strong[@property="v:average"]'),xmlValue)

  32.    drama_all$score[i] <- paste(this_score,'',sep='')

  33.    this_rating_count <- sapply(getNodeSet(html,'//span[@property="v:votes"]'),xmlValue)

  34.    drama_all$rating_count[i] <- paste(this_rating_count,'',sep='')


  35.    ## 提取剧集基本信息框并以key-value形式存储

  36.    info <- gsub('\n| |:|编剧群|编剧小组|编剧团队|编剧工作室','',sapply(getNodeSet(html,'//div[@id="info"]'),xmlValue))

  37.    reg <- paste(sapply(getNodeSet(html,'//span[@class="pl"]'),xmlValue),collapse = '|')

  38.    reg <- gsub('\n| |:|豆瓣','',reg)

  39.    reg <- gsub('\\|\\||\\|\\|\\|','\\|',reg)

  40.    info <- str_split(info,reg)

  41.    lis <- gsub(':','',sapply(getNodeSet(html,'//span[@class="pl"]'),xmlValue))

  42.    len <- length(info[[1]])

  43.    h = hash(keys=lis[1:len-1],values=info[[1]][2:(len)])


  44.    ## 从hash表提取基本信息

  45.    drama_all$episodes[i] <- paste(h$集数,'',sep='')

  46.    drama_all$genres[i] <- paste(h$类型,'',sep='' )

  47.    drama_all$area[i] <- paste(h$`制片国家/地区`,'',sep='')

  48.    drama_all$time[i] <- gsub('分钟','',paste(h$单集片长,'',sep=''))

  49.    drama_all$year[i] <- paste(h$年份,'',sep='')


  50.    ## 提取演员列表,并以姓名_演员id 如(胡歌_1274477)形式存储避免重名影响

  51.    this_actors_id <- gsub('/|celebrity','',sapply(getNodeSet(html,

  52.                           '//a[@rel="v:starring"]'),xmlAttrs)[1,])

  53.    this_actors_name <- gsub(' ','', sapply(getNodeSet(html,

  54.                             '//a[@rel="v:starring"]'),xmlValue))

  55.    this_actors <- paste(this_actors_name,this_actors_id,sep='_',collapse = ' ')

  56.    drama_all$actors[i] <- this_actors

  57.  })

  58.  if ('try-error' %in% class(trynext)) next

  59.  print(i)

  60. }


数据分析

PART1:总体数据概览


得到了数据,我们终于可以开始分析和可视化的进程。之所以说终于开始是因为这之前有许多的人肉工作,包括之前提到的动画片和纪录片混入电视剧的清单以及演员的匹配,需要一些耐心。

我们看一下电视剧集的豆瓣整体评分和时间的关系。

不出所料,电视剧的评分正随着时间的退役在逐步下滑,2016年的中位数更是直接到了6分以下。造成这种情况的原因可能性包括了:1.豆瓣受众比较年轻,老的剧集大家只会关注经典的剧集,过去的非优质(lan)剧评分人数较少 2.现在的非优质剧集占比越来越高。


PART2:词云生成


R语言的jiebaR+WordCloud2包可以实现分词+词云的功能,关于分词的原理我们在今后的内容中可以一起分享,本次内容以应用为主。

在所有的分词和词云生成过程中,停用词都是比较关键的一步,否则分出的结果没有实际的意义,我们采取了网上下载通用停用词与线下针对于电视剧剧情的停用词提取相结合的方式。线下通过对比多部剧集的介绍,提取了例如故事、生活、该剧等400多个词语。 


首先我们对比的是两岸三地剧情关键词:   

内地版:

香港版:


台湾版:


对比发现三组词语,我们可以发现内地剧集更加偏重家庭类,父母、孩子、家庭、婚姻这类词语占了很大的比重。同时抗战题材电视剧已经成了内地剧集的特色,相关的词语也占了很大的比重。另一方面,上海和北京这两个地点也频繁出现在剧集介绍中,可见这两座城市的重要性。


到了香港剧集,好友占据了中心的位置,同时商战相关的词语频繁出现,如公司、事业、老板、生意等。台湾剧集则基本上可以描述为围绕爱情展开的各种排列组合,意外、幸福、喜欢、善良,并且出现了青梅竹马这种和爱情强香港的词语。总结下来,内地剧主打亲情,港剧主打友情,台剧主打爱情。


WordCloud2同时提供了自定义图片功能,大家可以猜一下下面这个词语是哪位演员的轮廓。


PART3:演员分数排名


终于到了演员评分阶段,根据根据参演剧集的豆瓣评分计算演员分数。豆瓣分数确实存在一些不合理的地方,如一些经典老剧分数偏低等,但是相对而言,豆瓣分数从投票人数和防刷分等方面具备了一定的参考性 。


我们根据演员在豆瓣演员列表的排位分为主要(1-2)、次主演(3-5)、联合出演(7-10),相应将权重设为1.0,0.6,0.3,为了防止单部剧集占比过大,我们将评论数超过90分位数(7000)的,统一拉齐至7000,最终计算考虑了评论数和剧集中演员排位的加权平均分。


TOP30的演员列表如下:

排名靠前的基本上都是老戏骨,演技得到了观众的认可,之前非常火的达康书记排名第三。另一方面,我们看一下排名在后30位的演员名单。

对此,本公众号认为榜单中的演员比较年轻,希望以后能多拍好剧实现翻身。本次的分析就到这里了,关于电视剧,相信大家都会有更多的Point,希望能够留言提出,大家共同交流。


公众号后台回复关键字即可学习

回复 R               R语言快速入门免费视频 
回复 统计          统计方法及其在R中的实现
回复 用户画像   民生银行客户画像搭建与应用 
回复 大数据      大数据系列免费视频教程
回复 可视化      利用R语言做数据可视化
回复 数据挖掘   数据挖掘算法原理解释与应用
回复 机器学习   R&Python机器学习入门 


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存