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資料的步驟

  1. 利用 requests 庫中的 .get() 方法來向指定的URL發送HTTP GET請求,以獲得該網址的回應。

  2. 獲得的HTTP回應會存儲在 response 物件中,這個物件的 .text 屬性包含了回應的文字內容,其資料型態為 str(字串)。

  3. 因為我們知道返回的文字內容是用JSON格式儲存的,我們可以使用兩種方式將其轉換成Python的資料結構:

    • 使用 response.json() 直接轉換。

    • 或是使用 json.loads(response.text),這會將JSON格式的字串轉換成Python的字典和列表結構。

  4. 一旦資料被轉換成Python的資料結構,我們就可以按照需求來存取其各個部分,通常這會是一個由多層的列表和字典組成的巢狀結構。

Getting Data#

這一節將教授如何從網路上獲取資料並將其轉換成Python的字典(dict)和列表(list)結構。注意,不要太在意抓取資料的細節暫,未來會有更詳細的教學。這裡主要是使用一個實際例子來展示如何存取字典(dict)。

  1. requests 是一個專門用來發送網路請求的Python函式庫。在這裡,我們使用 .get() 方法來向指定的URL發送HTTP GET請求,並獲得回應。你可以使用 type() 函數來查看 response 的資料型態,它應該會返回 <class 'requests.models.Response'>

  2. 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_bikestotal 值的總和,以及這兩個總和的比例:

  1. Initialize variables
    available_sumtotal_sum,都設為0。這兩個變數將用於累加特定的數值。

  2. Traversing data
    資料使用 for 迴圈遍歷 data 字典的所有鍵(key)。

    • available_sum 累加每個 key 對應的 available 值(需先轉換為整數)。

    • total_sum 累加每個 key 對應的 total 值(需先轉換為整數)。

  3. Sum-up data
    最後,返回三個值:available_sumavailable 的總和)、total_sumtotal 的總和)以及 available_sum/total_sumavailable 總和與 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