查看原文
其他

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

王树义 玉树芝兰 2019-05-05

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)。

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


如果喜欢我的文章,请微信扫描下方二维码,关注并置顶我的公众号“玉树芝兰”。

如果你希望支持我继续输出更多的优质内容,欢迎长按并识别下面的小程序码,赞赏本文。感谢你的支持!

欢迎微信扫码加入我的“知识星球”圈子。第一时间分享给你我的发现和思考。

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

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