如何用R和API免費獲取Web資料?

王樹義發表於2018-06-28

API是獲得Web資料的重要途徑之一。想不想了解如何用R呼叫API,提取和整理你需要的免費Web資料呢?本文一步步為你詳盡展示操作流程。

如何用R和API免費獲取Web資料?

權衡

俗話說“巧婦難為無米之炊”。即便你已經掌握了資料分析的十八般武藝,沒有資料也是苦惱的事情。“拔劍四顧心茫然”說的大概就是這種情境吧。

資料的來源有很多。Web資料是其中數量龐大,且相對容易獲得的型別。更妙的是,許多的Web資料,都是免費的。

在這個號稱大資料的時代,你是如何獲得Web資料的呢?

許多人會使用那些別人整理好並且釋出的資料集。

他們很幸運,工作可以建立在別人的基礎上。這樣效率最高。

但是不見得每個人都有這樣的幸運。如果你需要用到的資料,偏巧沒有人整理和釋出過,怎麼辦?

其實,這樣的資料數量更為龐大。我們難道對它們視而不見嗎?

如果你想到了爬蟲,那麼你的思考方向是對的。爬蟲幾乎可以把一切看得見的(甚至是看不見的) Web資料,都統統幫你弄下來。然而編寫和使用爬蟲是有很高的成本的。包括時間資源、技術能力等。如果面對任何Web資料獲取問題,你都不假思索“上大錘”,有時候很可能是“殺雞用了牛刀”。

在“別人準備好的資料”和“需要自己爬取的資料”之間,還有很寬廣的一片地帶,這裡就是API的天地。

API是什麼?

它是Application Programming Interface的縮寫。具體而言,就是某個網站,有不斷積累和變化的資料。這些資料如果整理出來,不僅耗時,而且佔地方,況且剛剛整理好就有過期的危險。大部分人需要的資料,其實都只是其中的一小部分,時效性的要求卻可能很強。因此整理儲存,並且提供給大眾下載,是並不經濟划算的。

可是如果不能以某種方式把資料開放出來,又會面對無數爬蟲的騷擾。這會給網站的正常執行帶來很多煩惱。折中的辦法,就是網站主動提供一個通道。當你需要某一部分資料的時候,雖然沒有現成的資料集,卻只需要利用這個通道,描述你自己想要的資料,然後網站稽核(一般是自動化的,瞬間完成)之後,認為可以給你,就立刻把你明確索要的資料傳送過來。雙方皆大歡喜。

今後你找資料的時候,也不妨先看看目標網站是否提供了API,以避免做無用功。

這個github專案裡,有一份非常詳盡的列表,涵蓋了目前常見的主流網站API資源狀況。作者還在不斷整理修訂,你可以把它收藏起來,慢慢看。

如何用R和API免費獲取Web資料?

如果我們得知某個網站提供API,並且通過看說明文件,知道了我們需要的資料就在其中,那問題就變成了——該如何通過API來獲得資料呢?

下面我們用一個實際的例子,為你全程展示操作步驟。

來源

我們找的樣例,是維基百科。

維基百科的API總覽,請參考這個頁面

如何用R和API免費獲取Web資料?

假設我們關心的,是某一個時間段內,指定維基百科文章頁面的訪問量。

維基百科專門為我們提供了一類資料,叫做度量資料(metrics),其中就涵蓋了頁面訪問次數這個關鍵值。對應API的介紹頁面,在這裡

如何用R和API免費獲取Web資料?

頁面裡有一個樣例。假設你需要獲得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後面這一長串的網址,輸入到瀏覽器的位址列,然後回車,看看會得到什麼結果。

如何用R和API免費獲取Web資料?

我們在瀏覽器裡,看到上圖中那一長串文字。你可能感覺很奇怪——這是什麼玩意兒?

恭喜你,這就是我們需要獲得的資料了。只不過,它使用了一種特殊的資料格式,叫做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和API免費獲取Web資料?

R的下載位置有很多。建議你選擇清華大學的映象,可以獲得比較高的下載速度。

如何用R和API免費獲取Web資料?

請根據你的作業系統平臺,選擇其中對應的版本下載。我用的是macOS版本。

下載得到pkg檔案。雙擊就可以安裝。

安裝了基礎包之後,我們繼續安裝整合開發環境RStudio。它可以幫助你輕鬆地以互動方式和R溝通。RStudio的下載地址在這裡

如何用R和API免費獲取Web資料?

依據你的作業系統情況,選擇對應的安裝包。macOS安裝包為dmg檔案。雙擊開啟後,把其中的RStudio.app圖示拖動到Applications資料夾中,安裝就完成了。

如何用R和API免費獲取Web資料?

下面我們從應用目錄中,雙擊執行RStudio。

如何用R和API免費獲取Web資料?

我們先在RStudio的Console中,執行如下語句,安裝一些需要用到的軟體包:

install.packages("tidyverse")
install.packages("rlist")
複製程式碼

安裝完畢後,選擇選單裡的File->New,從以下介面中選擇 R Notebook。

如何用R和API免費獲取Web資料?

R Notebook預設提供給我們一個模板,附帶一些基礎使用說明。

如何用R和API免費獲取Web資料?

我們嘗試點選編輯區域(左側)程式碼部分(灰色)的執行按鈕。

如何用R和API免費獲取Web資料?

立即就可以看到繪圖的結果了。

我們點選選單欄上的Preview按鈕,來看整個兒程式碼的執行結果。執行結果會以圖文並茂的HTML檔案方式展示出來。

如何用R和API免費獲取Web資料?

熟悉了環境後,我們該實際操作執行自己的程式碼了。我們把左側編輯區的開頭說明區保留,把其餘部分刪除,並且把檔名改成有意義的web-data-api-with-R

如何用R和API免費獲取Web資料?

至此,準備工作就緒。下面我們就要開始實際操作了。

操作

實際操作過程中,我們從維基百科上換另外一篇維基文章作為樣例,以證明本操作方法的通用性。選擇的文章是我們在介紹詞雲製作時使用過的,叫做“Yes, Minisiter”。這是一部1980年代的英國喜劇。

如何用R和API免費獲取Web資料?

我們首先在瀏覽器裡嘗試一下,能否修改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
複製程式碼

返回結果如下:

如何用R和API免費獲取Web資料?

資料能夠正常返回,下面我們在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,那就有問題了,你需要排查錯誤。

如何用R和API免費獲取Web資料?

既然我們很幸運地沒有遇到問題,下面就開啟返回內容看看裡面都有什麼吧。因為我們知道返回的內容是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()
複製程式碼

如何用R和API免費獲取Web資料?

作為一部30多年前的劇集,今天還不斷有人訪問其維基頁面,可見它的魅力。從圖中可以非常明顯看到幾個峰值,你能解釋它們出現的原因嗎?這將作為今天的另外一道習題,供你思考。

小結

簡單回顧一下,本文我們接觸到了以下重要知識點:

  • 獲取Web資料的三種常見方式及其應用場景;
  • 常見API的目錄資源獲取地址和使用方法;
  • 如何用R來呼叫API,並且從伺服器反饋結果中抽取關心的資料。

希望讀過本文,你能初步掌握上述內容,並且根據文中提供的連結和教程資源擴充學習相關知識。

討論

你之前利用API獲取過Web資料嗎?除了R以外,你還使用過哪些API的呼叫工具?與本文的介紹比起來,這些工具有什麼特點?歡迎留言,把你的心得經驗分享給大家,我們一起交流討論。

喜歡請點贊。還可以微信關注和置頂我的公眾號“玉樹芝蘭”(nkwangshuyi)

如果你對資料科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門資料科學?》,裡面還有更多的有趣問題及解法。

相關文章