###### tags: `FM631A` # Lab 10 更新使用 TDX 運輸資料流通服務 手冊上採用的 [PTX 資料服務](https://ptx.transportdata.tw/PTX/)平台因為政府整合資訊的關係, 在 2022 年底就不再提供服務, 要改成使用 [TDX 運輸資料流通服務](https://tdx.transportdata.tw/), 本文將說明如何修改程式改用新的平台獲取相同的資訊。 ## 未註冊用戶的用法 TDX 和 PTX 一樣都可以不註冊使用, 不過會有同一 IP 每日透過程式查詢 50 次的限制, 以下先說明如何使用 TDX 查詢手冊範例中的鐵路班車資訊與特定車站電子看板資訊。 ### 修改瀏覽器字串 要免註冊存取 TDX, 程式一定要加上 TDX 認得的瀏覽器名稱, 以 Firefox 為例, 我們可以如下設定 user-agent: ```python >>> headers = {'user-agent':'Mozilla/5.0'} ``` 若是照手冊上設定成 curl 工具的名稱, 是會被 TDX 擋掉的。 你可以在慣用的瀏覽器開啟**網頁開發者工具**, 切換到**主控台**頁次, 執行 `navigator.userAgent` 指令, 即可得到瀏覽器的設定名稱: ![](https://i.imgur.com/oFimolu.png) 實際上不需要輸入完整的內容, 只要前面的**名稱/版本**即可。 ### 修改網址 TDX 的 API 網址和 PTX 幾乎一樣, 只要修改小地方即可: ``` https://ptx.transportdata.tw/MOTC/v2/Rail/TRA/GeneralTrainInfo/TrainNo/1202?$format=JSON --- ---- | | | +-+ | | V V --- --------- https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/GeneralTrainInfo/TrainNo/1202?$format=JSON ``` 就可以存取同樣的服務了。實際的 API 網址可在 [TDX 網站](https://tdx.transportdata.tw/api-service/swagger/basic/268fc230-2e04-471b-a728-a726167c1cfc#/TRA/TRAApi_GeneralTrainInfo_2147_1)上查詢。 ### 簡易測試--查詢指定班次的起訖站 這裡以 1202 班次為例, 統合以上所說, 查詢這班次火車的起訖站資訊: ```python >>> import urequests >>> headers = {'user-agent':'Mozilla/5.0'} >>> train_url = 'https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/GeneralTrainInfo/TrainNo/1202?$format=JSON' >>> train_res = urequests.get(train_url,headers=headers) >>> j = train_res.json() >>> print(j[0]['StartingStationName']['Zh_tw']) 北湖 >>> print(j[0]['EndingStationName']['Zh_tw']) 基隆 >>> ``` ### 修改範例程式 根據以上, 就可以把原始範例改成這樣: - lab10_tdx.py ```python # 無會員:當天次數 50 次 from machine import Pin,PWM import network import urequests import time import tm1637 import ntptime # 四位數顯示器 tm = tm1637.TM1637(clk=Pin(16), dio=Pin(17)) # 清空四位數顯示器 tm.write([0, 0, 0, 0]) # 連線至無線網路 sta=network.WLAN(network.STA_IF) sta.active(True) sta.connect('無線網路名稱','無線網路密碼') while not sta.isconnected() : pass print('Wi-Fi連線成功') ntptime.settime() # 想查詢的車號 s_number = 1202 # 想查詢的車站(1000是台北) sta_number = 1000 ifttt_url = "IFTTT請求網址" # 讓網站認為請求是使用瀏覽器發出。因為有些網頁會擋爬蟲程式 headers = {'user-agent':'Mozilla/5.0'} # 查詢指定火車起點與終點 train_url = ("https://tdx.transportdata.tw/api/basic" "/v2/Rail/TRA/GeneralTrainInfo/TrainNo/" + str(s_number) +"?$format=JSON") train_res = urequests.get(train_url,headers=headers) if(train_res.status_code == 200): pass else: print("傳送失敗") print("錯誤碼:",train_res.status_code) train_j = train_res.json() print("\n車號:",s_number) print(train_j[0]['StartingStationName']['Zh_tw'] \ +" → "+train_j[0]['EndingStationName']['Zh_tw']) train_res.close() # 車子第一次出現在時刻表時要提醒使用者 remind = False # 查詢火車時刻表 time_url = ("https://tdx.transportdata.tw/api/basic" "/v2/Rail/TRA/LiveBoard/Station/" + str(sta_number)+"?$top=20&$format=JSON") while True: time_res = urequests.get(time_url,headers=headers) if(time_res.status_code == 200): pass else: print("傳送失敗") print("錯誤碼:",time_res.status_code) time_j = time_res.json() time_res.close() # 將車號加入 number列表 中 number = [] for i in range(len(time_j)): number.append(time_j[i]['TrainNo']) print("\n即時班車號碼:",number) # 有查到對應車號, 顯示延遲時間 if(str(s_number) in number): if(remind == False): # 蜂鳴器 buzzer_pin = Pin(23,Pin.OUT) buzzer = PWM(buzzer_pin,freq=0, duty=50) buzzer.freq(349) time.sleep(1) buzzer.freq(294) time.sleep(1) buzzer.deinit() remind = True ind = number.index(str(s_number)) print("\n表定發車時間:", time_j[ind]['ScheduledArrivalTime']) print("延遲時間:", time_j[ind]['DelayTime'],"分鐘") tm.number(time_j[ind]['DelayTime']) res = urequests.get(ifttt_url + "?value1=" + str(time_j[ind]['DelayTime'])) if(res.status_code == 200): pass else: print("傳送失敗") print("錯誤碼:",res.status_code) res.close() # 沒有查到對應車號 else: print("\n目前無"+str(s_number)+"號火車") TW_sec = time.mktime(time.localtime())+28800 TW = time.localtime(TW_sec) hour = TW[3] minu = TW[4] tm.numbers(hour,minu) remind = False time.sleep(300) # 暫停 300 秒 ``` ## 註冊會員的程式寫法 如果不想受限於每天 50 次查詢, 請先至 [TDX 網站](https://tdx.transportdata.tw/)註冊會員, 註冊後需要一段時間審核, 審核通過會收到通知信件, 即可登入使用。 ### 找到金鑰資訊 要以會員身份使用 TDX API, 必須知道你的金鑰資訊, 請在登入後的頁面點選**建立更多金鑰**: ![](https://i.imgur.com/CvRuotJ.png) 往下捲動找到你的預設 API Key, 按一下**編輯**: ![](https://i.imgur.com/NPZsnaR.png) 就可以看到你的金鑰內含的兩項資訊: ![](https://i.imgur.com/qgEezxD.png) ### 取得 Access Token 實際上要以會員身份使用 TDX API, 必須分成以個階段: 1. 程式要利用剛剛取得的 Client Id 和 Client Secret 這兩項資訊向 TDX 索取 Access Token, 這個 Access Token 才是最後使用 API 時的認證, 有效期限是 1 天, 也就是說, 程式必須在 Access Token 失效後重新索取。 2. 利用剛剛取得的 Access Token 當成認證叫用 TDX API 上述過程聽起來就很麻煩, 詳細過程可以參考 [〈TDX運輸資料流通服務API介接範例程式碼說明〉](https://github.com/tdxmotc/SampleCode), 或是[〈「TDX 運輸資料流通服務平台」含 Python 範例程式,PTX 平台的升級版~〉](https://blog.jiatool.com/posts/tdx_python/), 我們已經將整個過程包裝成好用的 [tdx 模組](https://github.com/codemee/tdx), 可以大幅簡化程式。 tdx 模組也已經包含在範例檔中, 使用前請先上傳到控制版上。 ### tdx 模組的用法 tdx 模組使用上很簡單, 步驟如下: 1. 以 Client Id 及 Client Secret 建立 TDX 物件: ```python >>> from tdx import TDX >>> client_id = '你的 Client Id' >>> client_secret = '你的 Client Secret' >>> tdx = TDX(client_id, client_secret) ``` 2. 傳入 TDX API 網址呼叫 TDX.get_json() 取得查詢結果轉換後的 JSON 物件: ```python >>> train_url = 'https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/GeneralTrainInfo/TrainNo/1202?$format=JSON' >>> j = tdx.get_json(train_url) get new access token: 200 access token:eyJhbG... >>> print(j[0]['StartingStationName']['Zh_tw']) 北湖 >>> print(j[0]['EndingStationName']['Zh_tw']) 基隆 >>> ``` 是不是很簡單呢?這個物件會在叫用 API 之前確認 Access Token 的有效時間, 若已經過期, 就會自動重新索取。如果查詢出錯, 會傳回一個這樣格式的物件: ```python { 'error_code': HTTP 的錯誤碼, 'text': HTTP 回應內容 } ``` ### 修改範例程式 有了 tdx 模組, 我們就可以將原本的範例程式改寫, 不管如何使用 TDX API 都沒有問題 (好啦, 其實還是有限制, 每一 IP 每秒 50 次): - lab10_tdx_member.py ```python from machine import Pin,PWM import network import urequests import time import tm1637 import ntptime from tdx import TDX client_id = '你的 Client Id' client_secret = '你的 Client Secret' # 想查詢的車號 s_number = 4171 # 想查詢的車站(1000是台北) # 可以使用以下網址查詢 # https://tip.railway.gov.tw/tra-tip-web/tip/tip001/tip111/view sta_number = 1000 # 四位數顯示器 tm = tm1637.TM1637(clk=Pin(16), dio=Pin(17)) # 清空四位數顯示器 tm.write([0, 0, 0, 0]) # 連線至無線網路 sta=network.WLAN(network.STA_IF) sta.active(True) sta.connect('無線網路名稱','無線網路密碼') while not sta.isconnected() : pass print('Wi-Fi連線成功') # 建立 TDX API 存取物件 tdx = TDX(client_id, client_secret) ifttt_url = "IFTTT請求網址" # 查詢指定火車起點與終點 train_url = "https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/GeneralTrainInfo/TrainNo/" \ + str(s_number) +"?$format=JSON" train_j = tdx.get_json(train_url) if('error_code' in train_j): print("傳送失敗") print("錯誤碼:",train_j['error_code']) print("\n車號:",s_number) print(train_j[0]['StartingStationName']['Zh_tw'] \ +" → "+train_j[0]['EndingStationName']['Zh_tw']) # 車子第一次出現在時刻表時要提醒使用者 remind = False # 查詢火車時刻表 time_url = "https://tdx.transportdata.tw/api/basic/v2/Rail/TRA/LiveBoard/Station/" \ + str(sta_number)+"?$top=20&$format=JSON" while True: time_j = tdx.get_json(time_url) if('error_code' in time_j): print("傳送失敗") print("錯誤碼:",time_j['error_code']) # 將車號加入 number列表 中 number = [] for i in range(len(time_j)): number.append(time_j[i]['TrainNo']) print("\n即時班車號碼:",number) # 有查到對應車號, 顯示延遲時間 if(str(s_number) in number): if(remind == False): # 蜂鳴器 buzzer = PWM(Pin(23,Pin.OUT),freq=0, duty=512) buzzer.freq(349) time.sleep(1) buzzer.freq(294) time.sleep(1) buzzer.deinit() remind = True ind = number.index(str(s_number)) print("\n表定發車時間:",time_j[ind]['ScheduledArrivalTime']) print("延遲時間:",time_j[ind]['DelayTime'],"分鐘") tm.number(time_j[ind]['DelayTime']) res = urequests.get(ifttt_url + "?value1=" + str(time_j[ind]['DelayTime'])) if(res.status_code == 200): pass else: print("傳送失敗") print("錯誤碼:",res.status_code) res.close() # 沒有查到對應車號 else: print("\n目前無"+str(s_number)+"號火車") TW_sec = time.mktime(time.localtime())+28800 TW = time.localtime(TW_sec) hour = TW[3] minu = TW[4] tm.numbers(hour,minu) remind = False time.sleep(300) # 暫停 300 秒 ```