美文网首页PythonR语言论文写作
如何用R和API免费获取Web数据?

如何用R和API免费获取Web数据?

作者: 王树义 | 来源:发表于2017-10-13 14:40 被阅读870次

    API是获得Web数据的重要途径之一。想不想了解如何用R调用API,提取和整理你需要的免费Web数据呢?本文一步步为你详尽展示操作流程。

    权衡

    俗话说“巧妇难为无米之炊”。即便你已经掌握了数据分析的十八般武艺,没有数据也是苦恼的事情。“拔剑四顾心茫然”说的大概就是这种情境吧。

    数据的来源有很多。Web数据是其中数量庞大,且相对容易获得的类型。更妙的是,许多的Web数据,都是免费的。

    在这个号称大数据的时代,你是如何获得Web数据的呢?

    许多人会使用那些别人整理好并且发布的数据集。

    他们很幸运,工作可以建立在别人的基础上。这样效率最高。

    但是不见得每个人都有这样的幸运。如果你需要用到的数据,偏巧没有人整理和发布过,怎么办?

    其实,这样的数据数量更为庞大。我们难道对它们视而不见吗?

    如果你想到了爬虫,那么你的思考方向是对的。爬虫几乎可以把一切看得见的(甚至是看不见的) Web数据,都统统帮你弄下来。然而编写和使用爬虫是有很高的成本的。包括时间资源、技术能力等。如果面对任何Web数据获取问题,你都不假思索“上大锤”,有时候很可能是“杀鸡用了牛刀”。

    在“别人准备好的数据”和“需要自己爬取的数据”之间,还有很宽广的一片地带,这里就是API的天地。

    API是什么?

    它是Application Programming Interface的缩写。具体而言,就是某个网站,有不断积累和变化的数据。这些数据如果整理出来,不仅耗时,而且占地方,况且刚刚整理好就有过期的危险。大部分人需要的数据,其实都只是其中的一小部分,时效性的要求却可能很强。因此整理储存,并且提供给大众下载,是并不经济划算的。

    可是如果不能以某种方式把数据开放出来,又会面对无数爬虫的骚扰。这会给网站的正常运行带来很多烦恼。折中的办法,就是网站主动提供一个通道。当你需要某一部分数据的时候,虽然没有现成的数据集,却只需要利用这个通道,描述你自己想要的数据,然后网站审核(一般是自动化的,瞬间完成)之后,认为可以给你,就立刻把你明确索要的数据发送过来。双方皆大欢喜。

    今后你找数据的时候,也不妨先看看目标网站是否提供了API,以避免做无用功。

    这个github项目里,有一份非常详尽的列表,涵盖了目前常见的主流网站API资源状况。作者还在不断整理修订,你可以把它收藏起来,慢慢看。

    如果我们得知某个网站提供API,并且通过看说明文档,知道了我们需要的数据就在其中,那问题就变成了——该如何通过API来获得数据呢?

    下面我们用一个实际的例子,为你全程展示操作步骤。

    来源

    我们找的样例,是维基百科。

    维基百科的API总览,请参考这个页面

    假设我们关心的,是某一个时间段内,指定维基百科文章页面的访问量。

    维基百科专门为我们提供了一类数据,叫做度量数据(metrics),其中就涵盖了页面访问次数这个关键值。对应API的介绍页面,在这里

    页面里有一个样例。假设你需要获得2015年10月,爱因斯坦这个词条页面的访问数量,就可以这样调用:

    GET http://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents/Albert_Einstein/daily/2015100100/2015103100
    

    我们可以把GET后面这一长串的网址,输入到浏览器的地址栏,然后回车,看看会得到什么结果。

    我们在浏览器里,看到上图中那一长串文字。你可能感觉很奇怪——这是什么玩意儿?

    恭喜你,这就是我们需要获得的数据了。只不过,它使用了一种特殊的数据格式,叫做JSON。

    JSON是目前互联网上数据交互的主流格式之一。如果你想搞清楚JSON的含义和用法,可以参考这个教程

    我们在浏览器里,初始只能看到数据最开头的一部分。但是里面已经包含了很有价值的内容:

    {"items":[{"project":"en.wikipedia","article":"Albert_Einstein","granularity":"daily","timestamp":"2015100100","access":"all-access","agent":"all-agents","views":18860}
    

    这一段里,我们看到项目名称(en.wikipedia),文章标题(Albert Einstein),统计粒度(天),时间戳(2015年10月1日),访问类型(全部),终端类型(全部),以及访问数量(18860)。

    我们用滑动条拖拽返回的文本到最后,会看到如下的信息:

    {"project":"en.wikipedia","article":"Albert_Einstein","granularity":"daily","timestamp":"2015103100","access":"all-access","agent":"all-agents","views":16380}]}
    

    与10月1日的数据对比,只有时间戳(2015年10月31日)和访问数量(16380)发生了变化。

    中间我们跳过的,是10月2日到10月30日之间的数据。存储格式都是一样的,也只是日期和访问量两项数据值在变化。

    需要的数据都在这里,你只需要提取出相应的信息,就可以了。但是如果让你手动来做(例如拷贝需要的项,粘贴到Excel中),显然效率很低,而且很容易出错。下面我们来展示一下,如何用R编程环境来自动化完成这一过程。

    准备

    在正式用R调用API前,我们需要进行一些必要的准备工作。

    首先是安装R。

    请先到这个网址下载R基础安装包。

    R的下载位置有很多。建议你选择清华大学的镜像,可以获得比较高的下载速度。

    请根据你的操作系统平台,选择其中对应的版本下载。我用的是macOS版本。

    下载得到pkg文件。双击就可以安装。

    安装了基础包之后,我们继续安装集成开发环境RStudio。它可以帮助你轻松地以交互方式和R沟通。RStudio的下载地址在这里

    依据你的操作系统情况,选择对应的安装包。macOS安装包为dmg文件。双击打开后,把其中的RStudio.app图标拖动到Applications文件夹中,安装就完成了。

    下面我们从应用目录中,双击运行RStudio。

    我们先在RStudio的Console中,运行如下语句,安装一些需要用到的软件包:

    install.packages("tidyverse")
    install.packages("rlist")
    

    安装完毕后,选择菜单里的File->New,从以下界面中选择 R Notebook。

    R Notebook默认提供给我们一个模板,附带一些基础使用说明。

    我们尝试点击编辑区域(左侧)代码部分(灰色)的运行按钮。

    立即就可以看到绘图的结果了。

    我们点击菜单栏上的Preview按钮,来看整个儿代码的运行结果。运行结果会以图文并茂的HTML文件方式展示出来。

    熟悉了环境后,我们该实际操作运行自己的代码了。我们把左侧编辑区的开头说明区保留,把其余部分删除,并且把文件名改成有意义的web-data-api-with-R

    至此,准备工作就绪。下面我们就要开始实际操作了。

    操作

    实际操作过程中,我们从维基百科上换另外一篇维基文章作为样例,以证明本操作方法的通用性。选择的文章是我们在介绍词云制作时使用过的,叫做“Yes, Minisiter”。这是一部1980年代的英国喜剧。

    我们首先在浏览器里尝试一下,能否修改API样例里的参数,来获得“Yes, Minister”文章访问统计数据。作为测试,我们暂时只收集2017年10月1日到2017年10月3日 ,共3天的数据。

    相对样例,我们需要替换的内容包括起止时间文章标题

    我们在浏览器的地址栏输入:

    https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents/Yes_Minister/daily/2017100100/2017100300
    

    返回结果如下:

    数据能够正常返回,下面我们在RStudio中采用语句方式来调用。

    注意下面的代码中,程序输出部分的开头会有##标记,以便和执行代码本身相区别。

    一上来,我们就需要设置一下时区。不然后面处理时间数据的时候,会遇到错误。

    Sys.setenv(TZ="Asia/Shanghai")
    

    然后,我们调用tidyverse软件包,它是个合集,一次性加载许多我们后面要用到的功能。

    library(tidyverse)
    
    ## Loading tidyverse: ggplot2
    ## Loading tidyverse: tibble
    ## Loading tidyverse: tidyr
    ## Loading tidyverse: readr
    ## Loading tidyverse: purrr
    ## Loading tidyverse: dplyr
    
    ## Conflicts with tidy packages ----------------------------------------------
    
    ## filter(): dplyr, stats
    ## lag():    dplyr, stats
    

    这里可能会遇到一些警告内容,不要理会就可以。对咱们的操作毫不影响。

    根据前面的例子,我们定义需要查询的时间跨度,并且指定要查找的维基文章名称。

    注意与Python不同,R语言中,赋值采用<-标记,而不是=。不过R语言其实挺随和,你要是非得坚持用=,它也能认得,并不会报错。

    starting <- "20171001"
    ending <- "20171003"
    article_title <- "Yes Minister"
    

    根据已经设定的参数,我们就可以生成调用的API地址了。

    url <- paste("https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents",
                 article_title,
                 "daily",
                 starting,
                 ending,
                 sep = "/")
    

    这里我们使用的是paste函数,它帮助我们把几个部分串接起来,最后的sep指的是链接几个字符串部分时,需要使用的连接符。因为我们要形成的是类似于目录格式的网址数据,所以这里用的是分隔目录时常见的斜线。

    我们检查一下生成的url地址是不是正确:

    url
    
    ## [1] "https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents/Yes Minister/daily/20171001/20171003"
    

    检查完毕,结果正确。下面我们需要实际执行GET函数,来调用API,获得维基百科的反馈数据。

    要执行这一功能,我们需要加载另外一个软件包,httr。它类似于Python中的request软件包,类似于Web浏览器,可以完成和远端服务器的沟通。

    library(httr)
    

    然后我们开始调用。

    response <-GET(url, user_agent="my@email.com this is a test")
    

    我们看看调用API的结果:

    response
    
    ## Response [https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents/Yes Minister/daily/20171001/20171003]
    ##   Date: 2017-10-13 03:10
    ##   Status: 200
    ##   Content-Type: application/json; charset=utf-8
    ##   Size: 473 B
    

    注意其中的status一项。我们看到它的返回值为200。以2开头的状态编码是最好的结果,意味着一切顺利;如果状态值的开头是数字4或者5,那就有问题了,你需要排查错误。

    既然我们很幸运地没有遇到问题,下面就打开返回内容看看里面都有什么吧。因为我们知道返回的内容是JSON格式,所以我们加载jsonlite软件包,以便用清晰的格式把内容打印出来。

    library(jsonlite)
    
    ##
    ## Attaching package: 'jsonlite'
    
    ## The following object is masked from 'package:purrr':
    ##
    ##     flatten
    

    然后我们打印返回JSON文本的内容。

    toJSON(fromJSON(content(response, as="text")), pretty = TRUE)
    
    ## {
    ##   "items": [
    ##     {
    ##       "project": "en.wikipedia",
    ##       "article": "Yes_Minister",
    ##       "granularity": "daily",
    ##       "timestamp": "2017100100",
    ##       "access": "all-access",
    ##       "agent": "all-agents",
    ##       "views": 654
    ##     },
    ##     {
    ##       "project": "en.wikipedia",
    ##       "article": "Yes_Minister",
    ##       "granularity": "daily",
    ##       "timestamp": "2017100200",
    ##       "access": "all-access",
    ##       "agent": "all-agents",
    ##       "views": 644
    ##     },
    ##     {
    ##       "project": "en.wikipedia",
    ##       "article": "Yes_Minister",
    ##       "granularity": "daily",
    ##       "timestamp": "2017100300",
    ##       "access": "all-access",
    ##       "agent": "all-agents",
    ##       "views": 578
    ##     }
    ##   ]
    ## }
    

    可以看到,3天的访问数量统计信息,以及包含的其他元数据,都正确地从服务器用API反馈给了我们。

    我们把这个JSON内容存储起来。

    result <- fromJSON(content(response, as="text"))
    

    检查一下存储的内容:

    result
    
    ## $items
    ##        project      article granularity  timestamp     access      agent
    ## 1 en.wikipedia Yes_Minister       daily 2017100100 all-access all-agents
    ## 2 en.wikipedia Yes_Minister       daily 2017100200 all-access all-agents
    ## 3 en.wikipedia Yes_Minister       daily 2017100300 all-access all-agents
    ##   views
    ## 1   654
    ## 2   644
    ## 3   578
    

    我们看看解析之后,存储的类型是什么:

    typeof(result)
    
    ## [1] "list"
    

    存储的类型是列表(list)。可是为了后续的分析,我们希望把其中需要的信息提取出来,组成数据框(dataframe)。方法很简单,使用rlist这个R包,就可以轻松办到。

    library(rlist)
    

    我们需要使用其中的两个方法,一个是list.select,用来把指定的信息抽取出来;一个是list.stack,用来把列表生成数据框。

    df <- list.stack(list.select(result, timestamp, views))
    

    我们看看结果:

    df
    
    ##    timestamp views
    ## 1 2017100100   654
    ## 2 2017100200   644
    ## 3 2017100300   578
    

    数据抽取是正确的,包括了日期和浏览数量。但是这个日期格式不是标准格式,后面分析会有问题。我们需要做转化。

    处理时间日期格式,最好的办法是用lubridate软件包。我们先调用它。

    library(lubridate)
    
    ##
    ## Attaching package: 'lubridate'
    
    ## The following object is masked from 'package:base':
    ##
    ##     date
    

    由于日期字符串后面还有表示时区的两位(这里都是0),我们需要调用stringr软件包,将其截取掉。然后才能正确转换。

    library(stringr)
    

    然后我们开始转换,先用str_sub函数(来自于stringr软件包)把日期字符串的后两位抹掉,然后用lubridate软件包里面的ymd函数,将原先的字符串转换为标准日期格式。修改后的数据,我们存储回df$timestamp

    df$timestamp <- ymd(str_sub(df$timestamp, 1, -3))
    

    我们再来看看此时的df内容:

    df
    
    ##    timestamp views
    ## 1 2017-10-01   654
    ## 2 2017-10-02   644
    ## 3 2017-10-03   578
    

    至此,我们需要的数据都格式正确地保留下来了。

    不过,如果为了处理每一篇文章的阅读数量,我们都这样一条条跑语句,效率很低,而且难免会出错。我们把刚才的输入语句整理成函数,后面使用起来会更加方便。

    整理函数的时候,我们顺便采用dplyr包的“管道”(即你会看到的%>%符号)格式改写一下前面的内容,这样可以省却中间变量,而且看起来更为清晰明确。

    get_pv <- function(article_title, starting, ending){
      url <- paste("https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents",
                 article_title,
                 "daily",
                 starting,
                 ending,
                 sep = "/")
     df <- url %>%
        GET(user_agent="my@email.com this is a test") %>%
        content(as="text") %>%
        fromJSON() %>%
        list.select(timestamp, views) %>%
        list.stack() %>%
        mutate(timestamp = timestamp %>%
                 str_sub(1,-3) %>%
                 ymd())
      df
    }
    

    我们用新定义的函数,重新尝试一下刚才的API数据获取:

    starting <- "20171001"
    ending <- "20171003"
    article_title <- "Yes Minister"
    get_pv(article_title, starting, ending)
    
    ##    timestamp views
    ## 1 2017-10-01   654
    ## 2 2017-10-02   644
    ## 3 2017-10-03   578
    

    结果正确。

    不过如果只是抓取3天的数据,我们这么大费周章就没有意思了。下面我们扩展时间范围,尝试抓取自2014年初至2017年10月10日的数据。

    starting <- "20141001"
    ending <- "20171010"
    article_title <- "Yes Minister"
    df <- get_pv(article_title, starting, ending)
    

    我们看看运行结果:

    head(df)
    
    ##    timestamp views
    ## 1 2015-07-01   538
    ## 2 2015-07-02   588
    ## 3 2015-07-03   577
    ## 4 2015-07-04   473
    ## 5 2015-07-05   531
    ## 6 2015-07-06   500
    

    有意思的是,数据的统计并不是从2014年开始,而是2015年7月。这究竟是由于"Yes, Minister"维基文章是2015年7月才发布?还是因为我们调用的API对检索时间范围有限制?抑或是其他原因?这个问题留作思考题,欢迎把你的答案和分析过程分享给大家。

    下面,我们把获得的数据用ggplot2软件包绘制图形。用一行语句,看看几年之内,"Yes,
    Minister"维基文章访问数量的变化趋势。

    ggplot(data=df, aes(timestamp, views)) + geom_line()
    

    作为一部30多年前的剧集,今天还不断有人访问其维基页面,可见它的魅力。从图中可以非常明显看到几个峰值,你能解释它们出现的原因吗?这将作为今天的另外一道习题,供你思考。

    小结

    简单回顾一下,本文我们接触到了以下重要知识点:

    • 获取Web数据的三种常见方式及其应用场景;
    • 常见API的目录资源获取地址和使用方法;
    • 如何用R来调用API,并且从服务器反馈结果中抽取关心的数据。

    希望读过本文,你能初步掌握上述内容,并且根据文中提供的链接和教程资源拓展学习相关知识。

    讨论

    你之前利用API获取过Web数据吗?除了R以外,你还使用过哪些API的调用工具?与本文的介绍比起来,这些工具有什么特点?欢迎留言,把你的心得经验分享给大家,我们一起交流讨论。

    如果你对我的文章感兴趣,欢迎点赞,并且微信关注和置顶我的公众号“玉树芝兰”(nkwangshuyi)。

    如果本文可能对你身边的亲友有帮助,也欢迎你把本文通过微博或朋友圈分享给他们。让他们一起参与到我们的讨论中来。

    延伸阅读

    如何用《玉树芝兰》入门数据科学?

    数据科学相关文章合集(玉树芝兰)

    相关文章

      网友评论

      本文标题:如何用R和API免费获取Web数据?

      本文链接:https://www.haomeiwen.com/subject/uguzyxtx.html