2024年5月1日 星期三

Python 學習筆記 : 網頁爬蟲實戰 (四) 台北市公開資料平台 API

在上一篇擷取證交所休市日期網頁的測試中, 我們先利用瀏覽器開發人員工具從 HTTP 的 XHR 訊息中找出透過 Ajax 傳遞 JSON 資料的 URL, 然後直接用 requests.get() 擷取此資料即可, 這種取得資料的方式雖然要費點手腳, 但也總比用 BeautifulSoup 從網頁中硬解資料方便, 可以說是一種不公開的 API. 

本篇測試要從另一種網站擷取資料, 它們提供公開的 API (JSON 網址), 因此使用者. 例如台北市公開資料平台就有非常多政府公開資料供民眾下載, 我們不需要像擷取證交所休市日期網頁那樣去分析 HTTP 的回應訊息來找到資料的 JSON 網址, 也毋需註冊取得 API Key 即可擷取或下載資料, 對爬蟲程式而言, 這是最方便的資料取得方式. 



台北市公開資料平台網址如下 :


首先在左上角的輸入框輸入 youbike 按搜尋鈕 : 




然後在結果列表中找尋 "Yoybike2.0臺北市公共自行車即時資訊" 這一列 :




按超連結進入 Youbike 資料頁 :




將網頁拉到最底下 :




在 "資料即描述" 這一列右邊就可看到 JSON 資料的網址, 先複製下來備用 : 


也可以按右下角的 "下載" 超連結將此 JSON 檔下載到本機, 用記事本開啟後會發現是壓得很緊實難以閱讀的純文字內容 :




可以全部複製後貼到線上 JSON 剖析器例如 jsonfomatter.org 去解析 :




按底下的 "Format JSON" 鈕就會在右邊顯示整齊的格式 :




當然最重要的是可以用 Python 擷取 JSON 資料進行分析 :


1. 擷取 JSON 資料轉成字典串列 :  
  
先用 requests 模組擷取 JSON 資料 (不需要申請 Key/Token) :

>>> import requests   
>>> url='https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json'   
>>> response=requests.get(url)    
>>> response    
<Response [200]>   
>>> type(response.text)   
<class 'str'>    

接下來用內建模組 json 讀取抓下來的 JSON 字串, 將其轉換成 Python 的字典串列 :

>>> import json   
>>> ubike=json.loads(response.text)    
>>> type(ubike)   
<class 'list'>   
>>> len(ubike)  
1413   

可見目前總共有 1413 個 Ubike 站點, 檢視其中第一個 : 

>>> ubike[0]   
{'sno': '500101001', 'sna': 'YouBike2.0_捷運科技大樓站', 'tot': 28, 'sbi': 12, 'sarea': '大安區', 'mday': '2024-04-30 23:32:20', 'lat': 25.02605, 'lng': 121.5436, 'ar': '復興南路二段235號前', 'sareaen': 'Daan Dist.', 'snaen': 'YouBike2.0_MRT Technology Bldg. Sta.', 'aren': 'No.235, Sec. 2, Fuxing S. Rd.', 'bemp': 16, 'act': '1', 'srcUpdateTime': '2024-04-30 23:32:23', 'updateTime': '2024-04-30 23:32:52', 'infoTime': '2024-04-30 23:32:20', 'infoDate': '2024-04-30'}

也可以用迴圈迭代串列元素擷取關心的欄位 :

>>> i=0 
>>> for site in ubike: 
    print(f'{site["sna"]}: {site["ar"]}')   
    i += 1  
    if i > 100:  
        break  

YouBike2.0_捷運科技大樓站: 復興南路二段235號前
YouBike2.0_復興南路二段273號前: 復興南路二段273號西側
YouBike2.0_國北教大實小東側門: 和平東路二段96巷7號
YouBike2.0_和平公園東側: 和平東路二段118巷33號
YouBike2.0_辛亥復興路口西北側: 復興南路二段368號
YouBike2.0_復興南路二段280號前: 復興南路二段280號
YouBike2.0_復興南路二段340巷口: 復興南路二段342號
YouBike2.0_新生南路三段52號前: 新生南路三段52號
YouBike2.0_新生南路三段66號前: 新生南路三段66號東側
YouBike2.0_新生南路三段82號前: 新生南路三段82號
YouBike2.0_辛亥路一段30號前: 辛亥路一段30號
YouBike2.0_和平復興路口西北側: 復興南路二段236號
YouBike2.0_羅斯福路三段311號前: 羅斯福路三段311號
YouBike2.0_大安運動中心停車場: 敦南街76巷28號
... (略) ,,,

關於 json 模組用法參考 :



2. 利用 MD5 雜湊值判斷 JSON 資料有無更新 :  

我們可以利用內建模組 hashlib 計算 JSON 資料的雜湊值並與儲存在檔案中的舊雜湊值比較, 若相同表示 JSON 內容不變; 否則就是有更新, 

import requests
import hashlib
import os

def if_ubike_updated():
    old_hash=''   # 舊雜湊值初始值
    hash_file='ubike_hash.txt'    # 儲存雜湊值的檔案
    if os.path.exists(hash_file):    # 判斷雜湊值檔案有否存在, 有才讀取 (否則會錯誤)
        with open(hash_file, 'r') as f:
            old_hash=f.read()     # 讀取舊雜湊值
    url='https://tcgbusfs.blob.core.windows.net/dotapp/youbike/' +\
        'v2/youbike_immediate.json'
    response=requests.get(url)    # 取得 JSON 資料
    m=hashlib.md5()   # 建立 HASH 物件
    m.update(response.text.encode('utf-8'))   # 用取得之 JSON 資料更新 HASH 物件
    new_hash=m.hexdigest()   # 計算新雜湊值
    with open(hash_file, 'w') as f:
        f.write(new_hash)   # 將新雜湊值寫入檔案保存
    if new_hash == old_hash:   # 新舊雜湊值相等傳回 False 否則傳回 True
        return False
    else:
        return True 

if __name__ == '__main__':
    if if_ubike_updated():
        print('Youbike 即時資訊已更新')
    else:
        print('Youbike 即時資訊未更新')

執行結果如下 :

>>> %Run check_youbike_status.py   
Youbike 即時資訊已更新
>>> %Run check_youbike_status.py     
Youbike 即時資訊未更新 

可見第一次執行因尚無雜湊值檔案, old_hash 為空字串, 故與 new_hash 不相同傳回 True, 但馬上再呼叫 is_ubike_updated() 時雜湊值檔案已存在, 且其值在短時間未更新, 故 old_hash 與 new_hash 值相等而傳回 False. 

關於 hashlib 用法參考 : 


沒有留言 :