
本文以一款阿里雲市場歷史天氣查詢產品為例,為你逐步介紹如何用 Python 呼叫 API 收集、分析與視覺化資料。希望你舉一反三,輕鬆應對今後的 API 資料收集與分析任務。
雷同
上週的研究生課,學生分組展示實踐環節第二次作業,主題是利用 API 獲取、分析與視覺化資料。
大家做的內容,確實五花八門。
例如這個組,調查物件是動畫片《小豬佩奇》(英文名 “Peppa Pig”,又譯作《粉紅豬小妹》)。這部片子據說最近很火。

猜猜看,下面這一組調查物件是什麼?

沒錯,是《權力的遊戲》(Game of Thrones)。一部很好看的美劇。
主題豐富多彩,做得有聲有色。
作為老師,我在下面,應該很開心吧?
不,我簡直哭笑不得。
14個組中,有一多半都和他們一樣,做的是維基百科頁面訪問量分析。

為什麼會這樣呢?
因為我在佈置作業的時候,很貼心地給了一個樣例,是我之前寫的一篇教程《如何用R和API免費獲取Web資料?》。

於是,他們就都用 R 語言,來分析維基百科頁面訪問量了。
這些同學是不是太懶惰了?
聽了他們的講述,我發覺,其中不少同學,是非常想做些新東西的。
他們找了國內若干個雲市場,去找 API 產品。
其中要價過高的 API ,被他們自動過濾了。
可即適合練手的低價或免費 API ,也不少。
問題是,他們花了很長時間,也沒能搞定。
考慮到作業展示日程迫近,他們只好按照我的教程,去用 R 分析維基百科了。
於是,多組作業,都雷同。
講到這裡,他們一副不好意思的表情。
我卻發覺,這裡蘊藏著一個問題。
幾乎所有國內雲市場的 API 產品,都有豐富的文件。不少還乾脆給出了各種程式語言對應呼叫程式碼。
既然示例程式碼都有了,為什麼你還做不出來呢?
下課後,我讓有疑問的同學留下,我帶著他們實際測試了一款 API 產品,嘗試找到讓他們遭遇困境的原因。
市場
我們嘗試的,是他們找到的阿里雲市場的一款 API 產品,提供天氣資料。
它來自於易源資料,連結在這裡。

這是一款收費 API ,100次呼叫的價格為1分錢。
作為作業練習,100次呼叫已經足夠了。
這價格,他們表示可以接受。
我自己走了一遍流程。
點選“立即購買”按鈕。
你會被引領到付費頁面。如果你沒有登入,可以根據提示用淘寶賬號登入。
支付1分錢以後,你會看到如下的成功提示。

之後,系統會提示給你一些非常重要的資訊。

注意上圖中標紅的欄位。
這是你的AppCode,是後面你呼叫 API 介面獲取資料,最為重要的身份認證手段,請點選“複製”按鈕把它儲存下來。
點選上圖中的商品名稱連結,回到產品介紹的頁面。
這個產品的 API 介面,提供多種資料獲取功能。
學生們嘗試利用的,是其中“利用id或地名查詢歷史天氣”一項。

請注意這張圖裡,有幾樣重要資訊:
- 呼叫地址:這是我們訪問 API 的基本資訊。就好像你要去見朋友,總得知道見面的地址在哪裡;
- 請求方式:本例中的 GET ,是利用 HTTP 協議請求傳遞資料的主要形式之一;
- 請求引數:這裡你要提供兩個資訊給 API 介面,一是“地區名稱”或者“地區id”(二選一),二是月份資料。需注意格式和可供選擇的時間範圍。
我們往下翻頁,會看到請求示例。
預設的請求示例,是最簡單的 curl 。

如果你的作業系統裡面已經安裝了 curl (沒有安裝的話,可以點選這個連結,尋找對應的作業系統版本下載安裝),嘗試把上圖中 curl 開頭的那一行程式碼拷貝下來,複製到文字編輯器裡面。
就像這樣:
curl -i -k --get --include 'https://ali-weather.showapi.com/weatherhistory?area=%E4%B8%BD%E6%B1%9F&areaid=101291401&month=201601' -H 'Authorization:APPCODE 你自己的AppCode'
複製程式碼
然後,一定要把其中的“你自己的AppCode”這個字串,替換為你真實的 AppCode 。
把替換好的語句複製貼上到終端視窗裡面執行。
執行結果,如下圖所示:

看見視窗下方包含中文的資料了嗎?
利用 API 獲取資料,就是這麼簡單。
既然終端執行一條命令就可以,那我們幹嘛還要程式設計呢?
好問題!
因為我們需要的資料,可能不是一次呼叫就能全部獲得。
你需要重複多次呼叫 API ,而且還得不斷變化引數,積累獲得資料。
每次若是都這樣手動執行命令,效率就太低了。
API 的提供方,會為使用者提供詳細的文件與說明,甚至還包括樣例。

上圖中,除了剛才我們使用的 curl ,還包括以下語言訪問 API 介面的樣例說明:
- Java
- C#
- PHP
- Python
- Object C
我們以 Python 作為例子,點開標籤頁看看。

你只需要把樣例程式碼全部拷貝下來,用文字編輯器儲存為“.py”為副檔名的 Python 指令碼檔案,例如 demo.py 。

再次提醒,別忘了,把其中“你自己的AppCode”這個字串,替換為你真實的 AppCode,然後儲存。
在終端下,執行:
python demo.py
複製程式碼
如果你用的是 2.7 版本的 Python ,就立即可以正確獲得結果了。

為什麼許多學生做不出來結果呢?
我讓他們實際跑了一下,發現確實有的學生粗心大意,忘了替換自己的 AppCode 。
但是大部分同學,由於安裝最新版本的 Anaconda (Python 3.6版),都遇到了下面的問題:

你可能會認為這是因為沒有正確安裝 urllib2 模組,於是執行
pip install urllib2
複製程式碼
你可能會看到下面的報錯提示:

你也許嘗試去掉版本號,只安裝 urllib,即:
pip install urllib
複製程式碼
但是結果依然不美妙:

有些 Python 開發者看到這裡,可能會嘲笑我們:Python 3版本里面,urllib 被拆分了啊!地球人都知道,你應該……
請保持一顆同理心。
想想一個普通使用者,憑什麼要了解不同版本 Python 之間的語句差異?憑什麼要對這種版本轉換的解決方式心裡有數?
在他們看來,官方網站提供的樣例,就應該是可以執行的。報了錯,又不能通過自己的軟體包安裝“三板斧”來解決,就會慌亂和焦慮。
更進一步,他們也不太瞭解 JSON 格式。
雖然,JSON已是一種非常清晰的、人機皆可通讀的資料儲存方式了。
他們想了解的,是怎麼把問題遷移到自己能夠解決的範圍內。
例如說,能否把 JSON 轉換成 Excel 形式的資料框?
如果可以,他們就可以呼叫熟悉的 Excel 命令,來進行資料篩選、分析與繪圖了。
他們還會想,假如 Python 本身,能一站式完成資料讀取、整理、分析和視覺化全流程,那自然更好。
但是,樣例,樣例在哪裡呢?
在我《Python程式設計遇問題,文科生怎麼辦?》一文中,我曾經提到過,這種樣例,對於普通使用者的重要性。
沒有“葫蘆”,他們又如何“照葫蘆畫瓢”呢?
既然這個例子中,官方文件沒有提供如此詳細的程式碼和講解樣例,那我就來為你繪製個“葫蘆”吧。
下面,我給你逐步展示,如何在 Python 3 下,呼叫該 API 介面,讀取、分析資料,和繪製圖形。
環境
首先我們來看看程式碼執行環境。
前面提到過,如果樣例程式碼的執行環境,和你本地的執行環境不一,計時程式碼本身沒問題,也無法正常執行。
所以,我為你構建一個雲端程式碼執行環境。(如果你對這個程式碼執行環境的構建過程感興趣,歡迎閱讀我的《如何用iPad執行Python程式碼?》一文。)
請點選這個連結(t.cn/R3us4Ao),直接進入我們們的實驗環境。
你不需要在本地計算機安裝任何軟體包。只要有一個現代化瀏覽器(包括Google Chrome, Firefox, Safari和Microsoft Edge等)就可以了。全部的依賴軟體,我都已經為你準備好了。
開啟連結之後,你會看見這個頁面。

這個介面來自 Jupyter Lab。
圖中左側分欄,是工作目錄下的全部檔案。
右側開啟的,是我們們要使用的ipynb檔案。
根據我的講解,請你逐條執行,並仔細觀察執行結果。
本例中,我們主要會用到以下兩個新的軟體包。
首先是號稱“給人用”(for humans)的HTTP工具包requests。

這款工具,不僅符合人類的認知與使用習慣,而且對 Python 3 更加友好。作者 Kenneth Reitz 甚至在敦促所有的 Python 2 使用者,趕緊轉移到 Python 3 版本。
The use of Python 3 is highly preferred over Python 2. Consider upgrading your applications and infrastructure if you find yourself still using Python 2 in production today. If you are using Python 3, congratulations — you are indeed a person of excellent taste. —Kenneth Reitz
我們將用到的一款繪圖工具,叫做 plotnine 。
它實際上本不是 Python 平臺上的繪圖工具,而是從 R 平臺的 ggplot2 移植過來的。
要知道,此時 Python 平臺上,已經有了 matplotlib, seaborn, bokeh, plotly 等一系列優秀的繪圖軟體包。
那為什麼還要費時費力地,移植 ggplot2 過來呢?
因為 ggplot2 的作者,是大名鼎鼎的 R 語言大師級人物 Hadley Wickham 。

他創造 ggplot2,並非為 R 提供另一種繪圖工具,而是提供另一種繪圖方式。
ggplot2 完全遵守並且實現了 Leland Wilkinson 提出的“繪圖語法”(Grammar of Graphics),影像的繪製,從原本的部件拆分,變成了層級拆分。

這樣一來,資料視覺化變得前所未有地簡單易學,且功能強大。
我會在後文的“程式碼”部分,用詳細的敘述,為你展示如何使用這兩個軟體包。
我建議你先完全按照教程跑一遍,執行出結果。
如果一切正常,再將其中的資料,替換為你自己感興趣的內容。
之後,嘗試開啟一個空白 ipynb 檔案,根據教程和文件,自己敲程式碼,並且嘗試做調整。
這樣會有助於你理解工作流程和工具使用方法。
下面我們來看程式碼。
程式碼
首先,讀入HTTP工具包requests。
import requests
複製程式碼
第二句裡面,有“Your AppCode here”字樣,請把它替換為你自己的AppCode,否則下面執行會報錯。
appcode = 'Your AppCode here'
複製程式碼
我們嘗試獲取麗江5月份的天氣資訊。
在API資訊頁面上,有城市和程式碼對應的表格。
位置比較隱蔽,在公司簡介的上方。

我把這個 Excel 文件的網址放在了這裡(http://t.cn/R3T7e39),你可以直接點選下載。
下載該 Excel 檔案後開啟,根據表格查詢,我們知道“101291401”是麗江的城市程式碼。

我們將其寫入areaid
變數。
日期我們選擇本文寫作的月份,即2018年5月。
areaid = "101291401"
month = "201805"
複製程式碼
下面我們就設定一下 API 介面呼叫相關的資訊。

根據API資訊頁面上的提示,我們的要訪問的網址為:https://ali-weather.showapi.com/weatherhistory
,需要輸入的兩個引數,就是剛才已經設定的areaid
和month
。
另外,我們需要驗證身份,證明自己已經付費了。
點選上圖中藍色的“API 簡單身份認證呼叫方法(APPCODE)”,你會看到以下示例頁面。

看來我們需要在HTTP資料頭(header)中,加入 AppCode。
我們依次把這些資訊都寫好。
url = 'https://ali-weather.showapi.com/weatherhistory'
payload = {'areaid': areaid, 'month': month}
headers = {'Authorization': 'APPCODE {}'.format(appcode)}
複製程式碼
下面,我們就該用 requests 包來工作了。
requests 的語法非常簡潔,只需要指定4樣內容:
- 呼叫方法為“GET”
- 訪問地址 url
- url中需要附帶的引數,即 payload (包含
areaid
和month
的取值) - HTTP資料頭(header)資訊,即 AppCode
r = requests.get(url, params=payload, headers=headers)
複製程式碼
執行後,好像……什麼也沒有發生啊!
我們來檢視一下:
r
複製程式碼
Python 告訴我們:
<Response [200]>
複製程式碼
返回碼“200”的含義為訪問成功。
回顧一下,《如何用R和API免費獲取Web資料?》一文中,我們提到過:
以2開頭的狀態編碼是最好的結果,意味著一切順利;如果狀態值的開頭是數字4或者5,那就有問題了,你需要排查錯誤。
既然呼叫成功,我們看看 API 介面返回的具體資料內容吧。
呼叫返回值的 content 屬性:
r.content
複製程式碼

這一螢幕,密密麻麻的。
其中許多字元,甚至都不能正常顯示。這可怎麼好?
沒關係,從 API 資訊頁上,我們得知返回的資料,是 JSON 格式。

那就好辦了,我們呼叫 Python 自帶的 json 包。
import json
複製程式碼
用 json 包的字串處理功能(loads)解析返回內容,結果存入 content_json
。
content_json = json.loads(r.content)
複製程式碼
看看 content_json
結果:
content_json
複製程式碼

可以看到,返回的資訊很完整。而且剛剛無法正常顯示的中文,此時也都顯現了廬山真面目。
下一步很關鍵。
我們把真正關心的資料提取出來。
我們不需要返回結果中的錯誤碼等內容。
我們要的,是包含每一天天氣資訊的列表。
觀察發現,這一部分的資料,儲存在 'list' 中,而 'list' ,又儲存在 'showapi_res_body
' 裡面
所以,為選定列表,我們需要指定其中的路徑:
content_json['showapi_res_body']['list']
複製程式碼

冗餘資訊都被去掉了,只剩下我們想要的列表。
但是對著一個列表操作,不夠方便與靈活。
我們希望將列表轉換為資料框。這樣分析和視覺化就簡單多了。
大不了,我們還可以把資料框直接匯出為 Excel 檔案,扔到熟悉的 Excel 環境裡面,去繪製圖形。
讀入 Python 資料框工具 pandas 。
import pandas as pd
複製程式碼
我們讓 Pandas 將剛剛保留下來的列表,轉換為資料框,存入 df 。
df = pd.DataFrame(content_json['showapi_res_body']['list'])
複製程式碼
看看內容:
df
複製程式碼

此時,資料顯示格式非常工整,各項資訊一目瞭然。
寫到這裡,你基本上搞懂了,如何讀取某個城市、某個月份的資料,並且整理到 Pandas 資料框中。
但是,我們要做分析,顯然不能侷限在單一月份與單一城市。
每次加入一組資料,如果都得從頭這樣做一遍,會很辛苦。而且語句多了,執行起來,難免顧此失彼,出現錯誤。
所以,我們需要把剛剛的程式碼語句整合起來,將其模組化,形成函式。
這樣,我們只需要在呼叫函式的時候,傳入不同的引數,例如不同的城市名、月份等資訊,就能獲得想要的結果了。
綜合上述語句,我們定義一個傳入城市和月份資訊,獲得資料框的完整函式。
def get_df(areaid, areaname_dict, month, appcode):
url = 'https://ali-weather.showapi.com/weatherhistory'
payload = {'areaid': areaid, 'month': month}
headers = {'Authorization': 'APPCODE {}'.format(appcode)}
r = requests.get(url, params=payload, headers=headers)
content_json = json.loads(r.content)
df = pd.DataFrame(content_json['showapi_res_body']['list'])
df['areaname'] = areaname_dict[areaid]
return df
複製程式碼
注意除了剛才用到的語句外,我們為函式增加了一個輸入引數,即areaname_dict
。
它是一個字典,每一項分別包括城市程式碼,和對應的城市名稱。
根據我們輸入的城市程式碼,函式就可以自動在結果資料框中新增一個列,註明對應的是哪個城市。
當我們獲取多個城市的資料時,某一行的資料說的是哪個城市,就可以一目瞭然。
反之,如果只給你看城市程式碼,你很快就會眼花繚亂,不知所云了。
但是,只有上面這一個函式,還是不夠高效。
畢竟我們可能需要查詢若干月、若干城市的資訊。如果每次都呼叫上面的函式,也夠累的。
所以,我們下面再編寫一個函式,幫我們自動處理這些髒活兒累活兒。
def get_dfs(areaname_dict, months, appcode):
dfs = []
for areaid in areaname_dict:
dfs_times = []
for month in months:
temp_df = get_df(areaid, areaname_dict, month, appcode)
dfs_times.append(temp_df)
area_df = pd.concat(dfs_times)
dfs.append(area_df)
return dfs
複製程式碼
說明一下,這個函式接受的輸入,包括城市程式碼-名稱字典、一系列的月份,以及我們的 AppCode。
它的處理方式,很簡單,就是個雙重迴圈。
外層迴圈負責遍歷所有要求查詢的城市,內層迴圈遍歷全部指定的時間範圍。
它返回的內容,是一個列表。
列表中的每一項,都分別是某個城市一段時間(可能包含若干個月)的天氣資訊資料框。
我們先用單一城市、單一月份來試試看。
還是2018年5月的麗江。
areaname_dict = {"101291401":"麗江"}
months = ["201805"]
複製程式碼
我們將上述資訊,傳入 get_dfs
函式。
dfs = get_dfs(areaname_dict, months, appcode)
複製程式碼
看看結果:
dfs
複製程式碼

返回的是一個列表。
因為列表裡面只有一個城市,所以我們只讓它返回第一項即可。
dfs[0]
複製程式碼
這次顯示的,就是資料框了:

測試通過,下面我們趁熱打鐵,把天津、上海、麗江2018年初至今所有資料都讀取出來。
先設定城市:
areaname_dict = {"101030100":"天津", "101020100":"上海", "101291401":"麗江"}
複製程式碼
再設定時間範圍:
months = ["201801", "201802", "201803", "201804", "201805"]
複製程式碼
我們們再次執行 get_dfs
函式。
dfs = get_dfs(areaname_dict, months, appcode)
複製程式碼
看看這次的結果:
dfs
複製程式碼
結果還是一個列表。
列表中的每一項,對應某個城市2018年年初到5月份本文寫作時,這一段時間範圍天氣資料。

假設我們要綜合分析幾個城市的天氣資訊,那麼就可以把這幾個資料框整合在一起。
用到的方法,是 Pandas 內建的 concat
函式。
它接收一個資料框列表,把其中每一個個資料框沿著縱軸(預設)連線在一起。
df = pd.concat(dfs)
複製程式碼
看看此時的總資料框效果:
df
複製程式碼
這是開頭部分:

這是結尾部分:

3個城市,4個多月的資料都正確讀取和整合了。
下面我們嘗試做分析。
首先,我們得搞清楚資料框中的每一項,都是什麼格式:
df.dtypes
複製程式碼
aqi object
aqiInfo object
aqiLevel object
max_temperature object
min_temperature object
time object
weather object
wind_direction object
wind_power object
areaname object
dtype: object
複製程式碼
所有的列,全都是按照 object
處理的。
什麼叫 object
?
在這個語境裡,你可以將它理解為字串型別。
但是,我們們不能把它們都當成字串來處理啊。
例如日期,應該按照日期型別來看待,否則怎麼做時間序列視覺化?
AQI的取值,如果看作字串,那怎麼比較大小呢?
所以我們需要轉換一下資料型別。
先轉換日期列:
df.time = pd.to_datetime(df.time)
複製程式碼
再轉換 AQI 數值列:
df.aqi = pd.to_numeric(df.aqi)
複製程式碼
看看此時 df 的資料型別:
df.dtypes
複製程式碼
aqi int64
aqiInfo object
aqiLevel object
max_temperature object
min_temperature object
time datetime64[ns]
weather object
wind_direction object
wind_power object
areaname object
dtype: object
複製程式碼
這次就對了,日期和 AQI 都分別變成了我們需要的型別。其他資料,暫時保持原樣。
有的是因為本來就該是字串,例如城市名稱。
另一些,是因為我們暫時不會用到。
下面我們繪製一個簡單的時間序列對比圖形。
讀入繪圖工具包 plotnine 。
注意我們同時讀入了 date_breaks
,用來指定圖形繪製時,時間標註的間隔。
import matplotlib.pyplot as plt
%matplotlib inline
from plotnine import *
from mizani.breaks import date_breaks
複製程式碼
正式繪圖:
(ggplot(df, aes(x='time', y='aqi', color='factor(areaname)')) + geom_line() +
scale_x_datetime(breaks=date_breaks('2 weeks')) +
xlab('日期') +
theme_matplotlib() +
theme(axis_text_x=element_text(rotation=45, hjust=1)) +
theme(text=element_text(family='WenQuanYi Micro Hei'))
)
複製程式碼
我們指定橫軸為時間序列,縱軸為 AQI,用不同顏色的線來區分城市。
繪製時間的時候,以“2周”作為間隔週期,標註時間上的資料統計量資訊。
我們修改橫軸的標記為中文的“日期”。
因為時間顯示起來比較長,如果按照預設樣式,會堆疊在一起,不好看,所以我們讓它旋轉45度角,這樣避免重疊,一目瞭然。
為了讓圖中的中文正常顯示,我們需要指定中文字型,這裡我們選擇的是開源的“文泉驛微米黑”。
資料視覺化結果,如下圖所示。

怎麼樣,這張對比圖,繪製得還像模像樣吧?
從圖中,你可以分析出什麼結果呢?
反正我看完這張圖,很想去麗江。
小結
讀過本教程,希望你已經掌握了以下知識:
- 如何在 API 雲市場上,根據提示選購自己感興趣的產品;
- 如何獲取你的身份驗證資訊 AppCode ;
- 如何用最簡單的命令列 curl 方式,直接呼叫 API 介面,獲得結果資料;
- 如何使用 Python 3 和更人性化的 HTTP 工具包 requests 呼叫 API 獲得資料;
- 如何用 JSON 工具包解析處理獲得的字串資料;
- 如何用 Pandas 轉換 JSON 列表為資料框;
- 如何將測試通過後的簡單 Python 語句打包成函式,以反覆呼叫,提高效率;
- 如何用 plotnine (ggplot2的克隆)繪製時間序列折線圖,對比不同城市 AQI 歷史走勢;
- 如何在雲環境中執行本樣例,並且照葫蘆畫瓢,自行修改。
希望這份樣例程式碼,可以幫你建立信心,嘗試自己去搜集與嘗試 API 資料獲取,為自己的科研工作添磚加瓦。
如果你希望在本地,而非雲端執行本樣例,請使用這個連結(t.cn/R3usDi9)下載本文用到的全部原始碼和執行環境配置檔案(Pipenv)壓縮包。
如果你知道如何使用github,也歡迎用這個連結(t.cn/R3usEti)訪問對應的github repo,進行clone或者fork等操作。

當然,要是能給我的repo加一顆星,就更好了。
討論
你之前嘗試過用 Python 和 API 獲取資料嗎?你使用了哪些更好用的軟體包進行資料獲取、處理、分析與視覺化呢?你還使用過哪些其他的資料產品市場?歡迎留言,把你的經驗和思考分享給大家,我們一起交流討論。
喜歡請點贊。還可以微信關注和置頂我的公眾號“玉樹芝蘭”(nkwangshuyi)。
如果你對資料科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門資料科學?》,裡面還有更多的有趣問題及解法。