P03 Practice: Accessing dict and list#
Q1. Accessing youbike data#
資料來源:(https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json)。
目的:使用該API可以取得當下所有YouBike站點的資訊,包括站點名稱、經緯度、可借車輛數、可停空位數等。我想要統計,目前全台北市的可借車輛數和可停空位數的總和,藉此來計算目前全台北市的YouBike使用率。此時我會需要用到Python中的List和Dictionary,來幫助我遍歷(traverse)YouBike資料,並進行加總計數。
擷取YouBike資料的步驟
利用
requests庫中的.get()方法來向指定的URL發送HTTP GET請求,以獲得該網址的回應。獲得的HTTP回應會存儲在
response物件中,這個物件的.text屬性包含了回應的文字內容,其資料型態為str(字串)。因為我們知道返回的文字內容是用JSON格式儲存的,我們可以使用兩種方式將其轉換成Python的資料結構:
使用
response.json()直接轉換。或是使用
json.loads(response.text),這會將JSON格式的字串轉換成Python的字典和列表結構。
一旦資料被轉換成Python的資料結構,我們就可以按照需求來存取其各個部分,通常這會是一個由多層的列表和字典組成的巢狀結構。
Getting Data#
這一節將教授如何從網路上獲取資料並將其轉換成Python的字典(dict)和列表(list)結構。注意,不要太在意抓取資料的細節暫,未來會有更詳細的教學。這裡主要是使用一個實際例子來展示如何存取字典(dict)。
requests是一個專門用來發送網路請求的Python函式庫。在這裡,我們使用.get()方法來向指定的URL發送HTTP GET請求,並獲得回應。你可以使用type()函數來查看response的資料型態,它應該會返回<class 'requests.models.Response'>。response是儲存網路請求回應的物件,其中包含了很多有用的資訊。關於這個物件的更多細節,你可以參考 Python_request_response。response.text:這個屬性包含了HTTP回應的主要文本內容。response.status_code:這個屬性會返回HTTP狀態碼,這個碼可以告訴你請求是成功還是失敗,或者是其他情況。對狀態碼的完整列表,請參見 Wikipedia: Network Status Code。response.json():如果你事先知道返回的內容是以JSON格式儲存的,那麼你可以使用.json()方法將其轉換為Python的字典和列表結構。JSON(JavaScript Object Notation)是一種廣泛用於JavaScript的資料格式,它的結構類似於Python的字典和列表,因此能夠很容易地轉換過來。
透過這些步驟,你就能夠從網路上獲取資料,並將其轉換為Python程式能夠操作的數據格式。
# import requests library to send web query
import requests
# import json library to parse json format
import json
response = requests.get('https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json')
print(type(response)) # <class 'requests.models.Response'>
print(type(response.text)) # <class 'str'>
data = response.json()
# data = json.loads(response.text) # Alternative way
print(type(data))
<class 'requests.models.Response'>
<class 'str'>
<class 'list'>
print(json.dumps(data[:5], indent=4, ensure_ascii=False))
[
{
"sno": "500101001",
"sna": "YouBike2.0_捷運科技大樓站",
"sarea": "大安區",
"mday": "2025-09-21 21:50:02",
"ar": "復興南路二段235號前",
"sareaen": "Daan Dist.",
"snaen": "YouBike2.0_MRT Technology Bldg. Sta.",
"aren": "No.235, Sec. 2, Fuxing S. Rd.",
"act": "1",
"srcUpdateTime": "2025-09-21 21:59:29",
"updateTime": "2025-09-21 21:59:52",
"infoTime": "2025-09-21 21:50:02",
"infoDate": "2025-09-21",
"Quantity": 28,
"available_rent_bikes": 2,
"latitude": 25.02605,
"longitude": 121.5436,
"available_return_bikes": 26
},
{
"sno": "500101002",
"sna": "YouBike2.0_復興南路二段273號前",
"sarea": "大安區",
"mday": "2025-09-21 21:59:15",
"ar": "復興南路二段273號西側",
"sareaen": "Daan Dist.",
"snaen": "YouBike2.0_No.273, Sec. 2, Fuxing S. Rd.",
"aren": "No.273, Sec. 2, Fuxing S. Rd. (West)",
"act": "1",
"srcUpdateTime": "2025-09-21 21:59:29",
"updateTime": "2025-09-21 21:59:52",
"infoTime": "2025-09-21 21:59:15",
"infoDate": "2025-09-21",
"Quantity": 21,
"available_rent_bikes": 0,
"latitude": 25.02565,
"longitude": 121.54357,
"available_return_bikes": 21
},
{
"sno": "500101003",
"sna": "YouBike2.0_國北教大實小東側門",
"sarea": "大安區",
"mday": "2025-09-21 19:20:04",
"ar": "和平東路二段96巷7號",
"sareaen": "Daan Dist.",
"snaen": "YouBike2.0_NTUE Experiment Elementary School (East)",
"aren": "No. 7, Ln. 96, Sec. 2, Heping E. Rd",
"act": "1",
"srcUpdateTime": "2025-09-21 21:59:29",
"updateTime": "2025-09-21 21:59:52",
"infoTime": "2025-09-21 19:20:04",
"infoDate": "2025-09-21",
"Quantity": 28,
"available_rent_bikes": 5,
"latitude": 25.02429,
"longitude": 121.54124,
"available_return_bikes": 23
},
{
"sno": "500101004",
"sna": "YouBike2.0_和平公園東側",
"sarea": "大安區",
"mday": "2025-09-21 21:20:04",
"ar": "和平東路二段118巷33號",
"sareaen": "Daan Dist.",
"snaen": "YouBike2.0_Heping Park (East)",
"aren": "No. 33, Ln. 118, Sec. 2, Heping E. Rd",
"act": "1",
"srcUpdateTime": "2025-09-21 21:59:29",
"updateTime": "2025-09-21 21:59:52",
"infoTime": "2025-09-21 21:20:04",
"infoDate": "2025-09-21",
"Quantity": 11,
"available_rent_bikes": 4,
"latitude": 25.02351,
"longitude": 121.54282,
"available_return_bikes": 7
},
{
"sno": "500101005",
"sna": "YouBike2.0_辛亥復興路口西北側",
"sarea": "大安區",
"mday": "2025-09-21 21:40:03",
"ar": "復興南路二段368號",
"sareaen": "Daan Dist.",
"snaen": "YouBike2.0_Xinhai Fuxing Rd. Intersection (Northwest)",
"aren": "No. 368, Sec. 2, Fuxing S. Rd.",
"act": "1",
"srcUpdateTime": "2025-09-21 21:59:29",
"updateTime": "2025-09-21 21:59:52",
"infoTime": "2025-09-21 21:40:03",
"infoDate": "2025-09-21",
"Quantity": 16,
"available_rent_bikes": 6,
"latitude": 25.02153,
"longitude": 121.54299,
"available_return_bikes": 10
}
]
Traversing JSON data:#
採用系統性的測試策略,逐步探索由列表(list)與字典(dict)所組成的資料結構。透過這兩個基本法則,我們可以有系統地探索和理解複雜的JSON資料結構:
法則一:用type()確認資料型態。
法則二:遇到列表型態(list)時,選擇第0筆資料作為出發點繼續深入探索。
法則三:遇到字典型態(dict)時,使用
dict.keys()方法列出所有的鍵(key)。接著,不失一般性地,抽取你感興趣的鍵(key)作為下一步的追蹤目標,通常會第0個。特別注意,如果.keys()的輸出顯示為一系列類似於識別碼(id)的鍵(例如,youbike資料),那通常意味著設計者使用這些id來映射對應的資料。在這種情況下,你同樣可以選擇第一個識別碼(id)作為下一步追蹤的起點。
# Your code here
Observing data#
實際觀察該JSON資料結構後,你可能會注意到以下幾個特點:
該資料結構由List of dictionaries構成,List中所包含的每一個dict為一個站台的即時資料。
'total'這個鍵(key)所對應的值(value)是總車格數,'available_rent_bikes'是可借車輛數,'available_return_bikes'是可停空位數。這些資料是即時更新的,因此你可能會注意到
'mday'這個key所對應的value是最後更新的時間。另外還有經緯度的資訊,以及站台的名稱、區域等。
[ { "sno": "500101001", "sna": "YouBike2.0_捷運科技大樓站", "sarea": "大安區", "mday": "2025-03-09 11:45:29", "ar": "復興南路二段235號前", "sareaen": "Daan Dist.", "snaen": "YouBike2.0_MRT Technology Bldg. Sta.", "aren": "No.235, Sec. 2, Fuxing S. Rd.", "act": "1", "srcUpdateTime": "2025-03-09 11:46:20", "updateTime": "2025-03-09 11:46:52", "infoTime": "2025-03-09 11:45:29", "infoDate": "2025-03-09", "total": 28, "available_rent_bikes": 17, "latitude": 25.02605, "longitude": 121.5436, "available_return_bikes": 11 }, { "sno": "500101002", "sna": "YouBike2.0_復興南路二段273號前", "sarea": "大安區", "mday": "2025-03-09 11:45:29", "ar": "復興南路二段273號西側", "sareaen": "Daan Dist.", "snaen": "YouBike2.0_No.273, Sec. 2, Fuxing S. Rd.", "aren": "No.273, Sec. 2, Fuxing S. Rd. (West)", "act": "1", "srcUpdateTime": "2025-03-09 11:46:20", "updateTime": "2025-03-09 11:46:52", "infoTime": "2025-03-09 11:45:29", "infoDate": "2025-03-09", "total": 21, "available_rent_bikes": 21, "latitude": 25.02565, "longitude": 121.54357, "available_return_bikes": 0 }, ... ]
Getting all keys and values#
使用.keys()與.values()與.items()分別取出dict的內容
# Your code here
Iterating data#
嘗試用for-loop將所有的'total'與'available_return_bikes'值給列印出來,觀察看看不使用.items()和使用.items()的用法有何差異。
# Your code here
Q2. Sum-up total number of sbi and tot#
我想要知道,在全天候24小時中,ubike的使用率何時較高、何時較低。所以我打算統計全台北市所有YouBike站點的可借車輛數(available_return_bikes)和可停空位數(total)的總和,並計算兩者的比例。這樣一來,我就可以知道該時刻全台北市的YouBike使用率了。未來,我只需要寫一隻爬蟲,每五分鐘就收一次YouBike的資料,並計算出YouBike的使用率,就可以知道全天候24小時中,YouBike的使用率何時較高、何時較低了。
所以接下來,我打算計算 data中所有 available_return_bikes 和 total 值的總和,以及這兩個總和的比例:
Initialize variables
available_sum和total_sum,都設為0。這兩個變數將用於累加特定的數值。Traversing data
資料使用for迴圈遍歷data字典的所有鍵(key)。available_sum累加每個key對應的available值(需先轉換為整數)。total_sum累加每個key對應的total值(需先轉換為整數)。
Sum-up data
最後,返回三個值:available_sum(available的總和)、total_sum(total的總和)以及available_sum/total_sum(available總和與total總和的比例)。
# Your code here
Q3: Counting ubike site number of each area#
現在,我們已經可以用簡單的for-loop逐一跑過所有的Youbike即時資料。但我發現在資料中,sarea欄位資料為行政區,所以我想要統計,每個行政區有幾個Youbike站點。這樣一來,我就可以知道哪個行政區的Youbike站點最多了。相同的,我也可以統計,每個行政區的Youbike站點的可借車輛數(available)和可停空位數(total)的總和,並計算兩者的比例。這樣一來,我就可以知道哪個行政區的Youbike使用率最高了。
Counting sites by area#
# Your code here
Q4: Traversing AQI data#
以下為存取環保署開放資料平台空品資料的API連結與方法,請仿照Trace Youbike data的方法依序回答以下兩個問題。
import requests
import json
url = "https://airtw.moenv.gov.tw/gis_ajax.aspx?Type=GetAQInfo&Layer=EPA&QueryTime=2025/09/12%2011:00"
aqdata = requests.get(url).json()
print(type(aqdata)) # <class 'list'>
---------------------------------------------------------------------------
JSONDecodeError Traceback (most recent call last)
File ~/opt/anaconda3/lib/python3.9/site-packages/requests/models.py:971, in Response.json(self, **kwargs)
970 try:
--> 971 return complexjson.loads(self.text, **kwargs)
972 except JSONDecodeError as e:
973 # Catch JSON-related errors and raise as requests.JSONDecodeError
974 # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
File ~/opt/anaconda3/lib/python3.9/json/__init__.py:346, in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
343 if (cls is None and object_hook is None and
344 parse_int is None and parse_float is None and
345 parse_constant is None and object_pairs_hook is None and not kw):
--> 346 return _default_decoder.decode(s)
347 if cls is None:
File ~/opt/anaconda3/lib/python3.9/json/decoder.py:337, in JSONDecoder.decode(self, s, _w)
333 """Return the Python representation of ``s`` (a ``str`` instance
334 containing a JSON document).
335
336 """
--> 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end())
338 end = _w(s, end).end()
File ~/opt/anaconda3/lib/python3.9/json/decoder.py:355, in JSONDecoder.raw_decode(self, s, idx)
354 except StopIteration as err:
--> 355 raise JSONDecodeError("Expecting value", s, err.value) from None
356 return obj, end
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
During handling of the above exception, another exception occurred:
JSONDecodeError Traceback (most recent call last)
Input In [8], in <cell line: 4>()
2 import json
3 url = "https://airtw.moenv.gov.tw/gis_ajax.aspx?Type=GetAQInfo&Layer=EPA&QueryTime=2025/09/12%2011:00"
----> 4 aqdata = requests.get(url).json()
6 print(type(aqdata))
File ~/opt/anaconda3/lib/python3.9/site-packages/requests/models.py:975, in Response.json(self, **kwargs)
971 return complexjson.loads(self.text, **kwargs)
972 except JSONDecodeError as e:
973 # Catch JSON-related errors and raise as requests.JSONDecodeError
974 # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
--> 975 raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
AQI.1 Accessing list#
請Access該筆資料列印出第一個站台的站台名稱為何?
# Your code here
AQI.2 Checking data type#
請Access空品資料的任一筆資料,觀察資料內容,用程式列印出AQI的Data type為何?
# Your code here
AQI.3: Converting data structure#
目前的資料是將所有測站資料存放在一個List中,這樣會使得我們無法查詢例如「三重」測站的資料。因此,我們需要將資料結構轉換成Dictionary,以便我們可以透過測站名稱來查詢資料。怎麼做?
for site in aqdata:
if site['SiteName'] == '三重':
print(site['AQI'])
54
aqdict = {}
# Your code here