用R抓取NBA球员数据

January 14, 2014

Tags:菜鸟学R语言

上周末抽了一天时间学习django,可惜自己编程基础不好,对python语法很不熟悉,搞得自己灰头土脸,没啥成就感。搜到了一篇博文,博主是在虎扑体育任职,文中简要描述了如何用django框架创建app来抓取NBA球员数据的例子。我依葫芦画瓢都没能复制成功,在R中一个script就搞定一切的思路,在其他真正的编程语言面前,顿显R在网页动态呈现方面的软肋。

其实,论功能和解决问题的专注程度,我眼中的R是最简单直接的,可能因为我对python不熟悉吧,但有人表示python正在蚕食R在科研计算方面的蛋糕,也验证了我平时如果想用R做除了分析数据,画图,抓取网页数据之外的事情时,比如建网站,打包程序等,发现R有点捉襟见肘的想法了。所以,我才动了要学python的念头,但学一门外语是多么不容易啊。

R应该是我计算机语言中的母语,虽然大学里学过fortran,但为了考试学的知识毕竟不是那么牢固。我学习R的过程,就是先有问题,再去寻找R中可能的解决方案的,然后在这个过程中慢慢体会这门语言的过程。感觉就像老板拍脑门要做个什么事,就告诉下属去做,然后给我结果一样,只不过我既是老板又是下属。

接着第一段说,有了学django的挫败感之后,我为了寻找慰藉,又打开了Rstudio,希望能找回那顺畅熟悉的感觉。果然,同样的事情,很快就解决了,可谓驾轻就熟。可能django里牵扯到更多的编程思想,从model.py,view.py到setting.py,admin.py,这些只是网页后台框架,还要写app的model.py。在我上手试用后,感觉非常生涩,对library、function、model的命名规则不太习惯,说白了,压根没入门。所以,我还要多花时间去啃着块骨头。

好了,以上都是本文的题外话或者前情提要吧。我既然又用回R来抓取数据了,就将代码分享出来吧。

先分析再抓取

点开球员页面的链接,发现是按照姓名排序的。

basketball-reference-1

而且,点进去某个字母链接,就可以直接得到所有的球员,这是最赞的,也是对抓取技能要求最低的了。所以,第一步就要抓取这个页面上的所有字母链接,再打开每个字母链接,抓取所有数据。很纳闷,居然NBA球员没有姓Xavier的,导致X字母下面居然没有一个人。

library(XML)
library(RCurl)

url = "http://www.basketball-reference.com/players/"
get_url = getURL(url, encoding = "UTF-8")
get_url_parse = htmlParse(get_url, encoding = "UTF-8")
url_player_initial = xpathSApply(get_url_parse, "//tr/td[1]/a", function(x) c(xmlAttrs(x)[["href"]]))
url_modify = "http://www.basketball-reference.com"
url_player_all = sapply(url_player_initial, function(x) paste(url_modify,x,sep=""))

data_player_all = sapply(url_player_all, function(x) readHTMLTable(x))

在表中有粗体的名字,意思是现役球员,也可以单独标注出来,这里我就通过判断是否在2014年还在服役来确定是否现役,最后结果比直接抓取现役球员要少,可能因为有的球员有伤病,或者被下放到低级联赛锻炼一年等原因,合同停滞在2012/13年,大概有一百多位这样的球员。

basketball-reference-2

将抓取的表数据格式化

我不知道python的scrapy或者beautifulsoup是什么样,R抓取的网页数据,如果想拿来做数据分析,似乎都要这个过程,把抓下来的html节点,as.numeric(as.character())一下,不知道有木有更简便的方法啊?

data_player_all_full = as.data.frame(data_player_all[1])
for(i in 2:length(data_player_all)){
  tmp = as.data.frame(data_player_all[i])
  names(data_player_all_full) = names(tmp)
  data_player_all_full = rbind(data_player_all_full, tmp)
}

names(data_player_all_full) = c("player","from","to","position","height","weight","birthdate","college")
data_player_all_full$from = as.numeric(as.character(data_player_all_full$from))
data_player_all_full$to = as.numeric(as.character(data_player_all_full$to))
data_player_all_full$weight = as.numeric(as.character(data_player_all_full$weight))
data_player_all_full$height = sapply(data_player_all_full$height, FUN=function(x) sub("-",".",x))
data_player_all_full$height = as.numeric(as.character(data_player_all_full$height))

这一段纯粹为了擦屁股,但为了能分析数据,也是很至关重要的。

哪个星座的NBA球员最多?

知道我有多无聊了吧。数据里的出生日期是英文格式,所以又需要把它转成数字格式,我没找到有函数可以方便的转换,应该没有这么智能的,于是还是手动转吧。

from = c('January','February','March','April','May','June',
         'July','August','September','October','November','December',',',' ')
to = c('1','2','3','4','5','6','7','8','9','10','11','12','','/')
gsub2 = function(pattern, replacement,x,...){
  for(i in 1:length(pattern))
    x = gsub(pattern[i],replacement[i],x,...)
  x
}

data_player_all_full$birthdate = gsub2(from, to, data_player_all_full$birthdate)
data_player_all_full[data_player_all_full$birthdate == "",]$birthdate = "00/00/0000"

转完之后,再把mm/dd/yyyy格式的日期,分割成三列,用来根据月份和日期判断星座。

c= do.call(rbind.data.frame, strsplit(data_player_all_full$birthdate,"/"))
names(c) = c('birthmonth','birthday','birthyear')
c$birthmonth = as.numeric(as.character(c$birthmonth))
c$birthday = as.numeric(as.character(c$birthday))
c$birthyear = as.numeric(as.character(c$birthyear))
data_player_all_full = cbind(data_player_all_full,c)

constellation = function(x,y){
  if(x == 3 & y >= 21) return("Aries") else
  if(x == 4 & y <= 19) return("Aries") else
  if(x == 4 & y >= 20) return("Taurus") else
  if(x == 5 & y <= 20) return("Taurus") else
  if(x == 5 & y >= 21) return("Gemini") else
  if(x == 6 & y <= 21) return("Gemini") else
  if(x == 6 & y >= 22) return("Cancer") else
  if(x == 7 & y <= 22) return("Cancer") else
  if(x == 7 & y >= 23) return("Leo") else
  if(x == 8 & y <= 22) return("Leo") else
  if(x == 8 & y >= 23) return("Virgo") else
  if(x == 9 & y <= 22) return("Virgo") else
  if(x == 9 & y >= 23) return("Libra") else
  if(x == 10& y <= 23) return("Libra") else
  if(x == 10& y >= 24) return("Scorpio") else
  if(x == 11& y <= 21) return("Scorpio") else
  if(x == 11& y >= 22) return("Sagittarius") else
  if(x == 12& y <= 21) return("Sagittarius") else
  if(x == 12& y >= 22) return("Capricorn") else
  if(x ==  1& y <= 19) return("Capricorn") else
  if(x ==  1& y >= 20) return("Aquarius") else
  if(x ==  2& y <= 18) return("Aquarius") else
  if(x ==  2& y >= 19) return("Pisces") else
  if(x ==  3& y <= 20) return("Pisces") else return("Alien")
}

for(i in 1:length(data_player_all_full$birthmonth)) {
  data_player_all_full$constellation[i] = constellation(data_player_all_full$birthmonth[i],
  data_player_all_full$birthday[i])
}

好了,就这样。其实想想,应该可以直接通过字符串语句的来判断星座的,必须得有更好的方法。

先做个简单的统计图,看看哪个星座的NBA球员最多。

NBA-constellation

看来处女座的NBA球员最多,而摩羯座的最少啊,不过差别不多啦。

· The end ·