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

简介: API是获得Web数据的重要途径之一。想不想了解如何用R调用API,提取和整理你需要的免费Web数据呢?本文一步步为你详尽展示操作流程。权衡俗话说“巧妇难为无米之炊”。

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

img_3a044b394a72867bf54cc34f16551c35.jpe

权衡

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

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

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

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

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

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

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

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

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

API是什么?

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

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

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

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

img_388cc293c253d75b7a363e45507f1e53.jpe

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

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

来源

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

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

img_2915d0ca6f1f3b8288117254a959abed.jpe

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

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

img_e914f3e9f152520a722d38f5ee63dd35.jpe

页面里有一个样例。假设你需要获得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后面这一长串的网址,输入到浏览器的地址栏,然后回车,看看会得到什么结果。

img_91723e22d30e7f789f696df1d1435b30.jpe

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

恭喜你,这就是我们需要获得的数据了。只不过,它使用了一种特殊的数据格式,叫做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基础安装包。

img_36da828e875fcf8ad366bd963a3475b2.jpe

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

img_d1e35d9525d5bcddb727956b7ce92617.jpe

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

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

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

img_7fade9eb9aeed5bee69644a40abf07c9.jpe

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

img_b99bd2fcf7921fbb1f09b8ff4ebcd3a3.jpe

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

img_007de11ec14b01b8f5a274c7b9ce541f.jpe

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

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

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

img_a301f277e698fec602015afda3d4779d.jpe

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

img_813386161ee4f3629173a33ea5cb40e4.jpe

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

img_32d697d674d0a6f8ee9d5b80dc373d7f.jpe

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

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

img_dd2fcd82eda6b5804aab48a8a821b0fb.jpe

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

img_e45861c6e19831951540e2eda32a1043.jpe

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

操作

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

img_ab49ed889820180b6d1f342f97c07887.jpe

我们首先在浏览器里尝试一下,能否修改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

返回结果如下:

img_799aa0bdf99c645968d91c47988f4313.jpe

数据能够正常返回,下面我们在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,那就有问题了,你需要排查错误。

img_b90d5d1ebdb657c969c807a6ddd2bb2a.jpe

既然我们很幸运地没有遇到问题,下面就打开返回内容看看里面都有什么吧。因为我们知道返回的内容是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()
img_1c042f6633ce8461bc68dc5194951034.png

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

小结

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

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

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

讨论

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

喜欢请点赞。还可以微信关注和置顶我的公众号“玉树芝兰”(nkwangshuyi)

如果你对数据科学感兴趣,不妨阅读我的系列教程索引贴《如何高效入门数据科学?》,里面还有更多的有趣问题及解法。

目录
相关文章
|
3天前
|
分布式计算 DataWorks API
DataWorks产品使用合集之使用REST API Reader往ODPS写数据时,如何获取入库时间
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
1天前
|
API 网络架构 C++
SharePoint Online SPFx Web部件绑定数据
【7月更文挑战第6天】在Markdown格式下,以下是关于创建SharePoint Online SPFx Web部件绑定数据步骤的摘要: 1. 创建数据列表。 2. 使用VS Code打开Web部件。 3. 定义列表模型(如`IList`和`IListItem`接口)。 4. 引入`spHttpClient`以执行REST API请求。 5. 实现`_getListData()`方法,调用REST服务获取列表数据。 6. 设计`_renderList()`方法来渲染数据。 7. 在`render()`方法中获取并渲染数据到Web部件。
|
5天前
|
前端开发 JavaScript API
惊呆了!学会AJAX与Fetch API,你的Python Web项目瞬间高大上!
【7月更文挑战第15天】AJAX和Fetch API是Web开发中的关键工具,用于异步前后端交互。AJAX借助XMLHttpRequest实现页面局部更新,而Fetch API是现代的、基于Promise的HTTP请求接口,提供更强大功能和简洁语法。结合Python Web框架如Django或Flask,利用这两者能创造无缝体验,提升项目性能和用户体验。学习并应用这些技术,将使你的Web应用焕发新生。**
14 5
|
7天前
|
前端开发 API 开发者
Python Web开发者必看!AJAX、Fetch API实战技巧,让前后端交互如丝般顺滑!
【7月更文挑战第13天】在Web开发中,AJAX和Fetch API是实现页面无刷新数据交换的关键。在Flask博客系统中,通过创建获取评论的GET路由,我们可以展示使用AJAX和Fetch API的前端实现。AJAX通过XMLHttpRequest发送请求,处理响应并在成功时更新DOM。Fetch API则使用Promise简化异步操作,代码更现代。这两个工具都能实现不刷新页面查看评论,Fetch API的语法更简洁,错误处理更直观。掌握这些技巧能提升Python Web项目的用户体验和开发效率。
20 7
|
4天前
|
前端开发 JavaScript UED
Python Web应用中的WebSocket实战:前后端分离时代的实时数据交换
【7月更文挑战第16天】在前后端分离的Web开发中,WebSocket解决了实时数据交换的问题。使用Python的Flask和Flask-SocketIO库,后端创建WebSocket服务,监听并广播消息。前端HTML通过JavaScript连接到服务器,发送并显示接收到的消息。WebSocket适用于实时通知、在线游戏等场景,提升应用的实时性和用户体验。通过实战案例,展示了如何实现这一功能。
|
7天前
|
XML 前端开发 API
惊艳全场的秘诀!AJAX、Fetch API与Python后端,打造令人惊叹的Web应用!
【7月更文挑战第13天】构建现代Web应用的关键在于提供无缝用户体验,这涉及AJAX和Fetch API的异步数据交换以及Python(如Flask)的后端支持。Fetch API以其基于Promise的简洁接口,改进了AJAX的复杂性。例如,一个Flask应用可提供用户数据,前端利用Fetch API在不刷新页面的情况下显示信息。这种结合提升了效率,减少了服务器负载,是现代Web开发的趋势。随着技术发展,预期将有更多工具优化这一过程。
27 3
|
15天前
|
JSON JavaScript 前端开发
若依修改,若依如何发送get和post请求,发送数据请求的写法,若依请求的API在src的api文件下,建立请求的第一步,在API中新建一个文件,第二步新建JavaScript文件
若依修改,若依如何发送get和post请求,发送数据请求的写法,若依请求的API在src的api文件下,建立请求的第一步,在API中新建一个文件,第二步新建JavaScript文件
|
6天前
|
前端开发 API 开发者
从零到精通,AJAX与Fetch API让你的Python Web前后端交互无所不能!
【7月更文挑战第14天】在Web开发中,AJAX和Fetch API扮演着关键角色,用于前后端异步通信。AJAX通过XMLHttpRequest实现页面局部更新,但回调模式和复杂API有一定局限。Fetch API作为现代替代,利用Promise简化异步处理,提供更丰富功能和错误处理。Python后端如Flask、Django支持这些交互,助力构建高性能应用。从AJAX到Fetch API的进步,结合Python,提升了开发效率和用户体验。
10 0
|
6天前
|
XML 前端开发 API
颠覆传统!AJAX、Fetch API与Python后端,开启Web开发新篇章!
【7月更文挑战第14天】Web开发中,AJAX作为异步通信先驱,与新兴的Fetch API一起革新交互体验。Fetch基于Promise,简化了请求处理。Python后端,如Flask,提供稳定支撑。这三者的融合,推动Web应用达到新高度,实现高效、实时交互。通过示例展示,我们看到从发送请求到更新UI的流畅过程,以及Python如何轻松返回JSON数据。这种组合揭示了现代Web开发的潜力和魅力。
12 0
|
16天前
|
API
Vue2和Vue3的区别,OptionsAPI与CompositionAPI的区别,Vue2所有的数据,都写在data和method方法中,setup是一个全新的配置项,Vue2是选项式API的写法
Vue2和Vue3的区别,OptionsAPI与CompositionAPI的区别,Vue2所有的数据,都写在data和method方法中,setup是一个全新的配置项,Vue2是选项式API的写法