TWSE T86「外陸資買賣超股數」欄位命名實況 — silent failure 風險為何容易發生
TWSE 開放資料 API 的 T86 endpoint 提供當日三大法人 (外資 / 投信 / 自營商) 買賣超數據。財經新聞口語普遍以「外資買賣超」稱呼此數字,但 API 回傳的欄位名稱跟口語並不一致,用關鍵字部分比對 (substring keyword match) 抓欄位會有 silent failure 風險。本文整理欄位命名實況、silent failure 機制,以及穩定取值的建議做法。
① Endpoint 與欄位實況
GET https://www.twse.com.tw/rwd/zh/fund/T86?date={YYYYMMDD}&selectType=ALL&response=json
回傳當日各檔股票的外資 / 投信 / 自營商買賣超,每個交易日 17:30 後更新。
回傳結構 (fields 陣列順序,0-based index,共 19 欄):
[0] 證券代號[1] 證券名稱[2] 外陸資買進股數(不含外資自營商)[3] 外陸資賣出股數(不含外資自營商)[4] 外陸資買賣超股數(不含外資自營商)← 對應財經新聞口語「外資買賣超」(目標欄位)[5] 外資自營商買進股數[6] 外資自營商賣出股數[7] 外資自營商買賣超股數[8] 投信買進股數[9] 投信賣出股數[10] 投信買賣超股數[11] 自營商買賣超股數[12] 自營商買進股數(自行買賣)[13] 自營商賣出股數(自行買賣)·[14] 自營商買賣超股數(自行買賣)[15] 自營商買進股數(避險)·[16] 自營商賣出股數(避險)·[17] 自營商買賣超股數(避險)[18] 三大法人買賣超股數
目標的「外陸資買賣超股數(不含外資自營商)」在 0-based index 4:index 2 是「外陸資買進股數」(買進總量,非買賣超淨額),index 3 是「外陸資賣出股數」。另外欄位中沒有單獨的「自營商買進股數」,自營商買進只存在 (自行買賣) (index 12) 與 (避險) (index 15) 兩個帶後綴的版本。
② 命名實況 · 字串細節
完整欄位名稱:
外陸資買賣超股數(不含外資自營商)
- 字數:17 個字 (含括號)
- 括號類型:半形
()· 不是全形() - 「外陸資」= 外資 + 陸資合稱
- 「不含外資自營商」= 排除外資旗下自營業務 · 才是外部法人對台股的真實淨方向
口語的「外資買賣超」/「法人外資買超」/「外資淨買超」都不是 API 的實際欄位名。
③ 為什麼欄位名稱跟口語不同
TWSE T86 endpoint 同時追蹤多個主體:
- 外資 (含外資自營商)
- 外資 (不含外資自營商) ← 財經新聞講的「外資」對應此欄位
- 外資自營商
- 投信
- 自營商
為避免歧義,每個欄位名都明確標註所涵蓋的範圍。完整名稱是「外陸資買賣超股數(不含外資自營商)」,不是「外資買賣超」,也不是「外資淨買超」。
④ Silent failure 風險為何容易發生
開發者抓 T86 資料時,若用關鍵字部分比對 (例如搜「外資」拿第一個命中) 抓欄位,可能會抓到:
外資自營商買進股數(index 5,外資旗下自營業務,跟外部法人的進出方向不同)外陸資買進股數(不含外資自營商)(index 2,用「外資」比對時,因括號內「不含外資自營商」也含「外資」字串,第一個命中往往落在這裡,抓到的是買進總量而非買賣超淨額)- 其他細分欄位 (投信 / 自營商各組的買進、賣出、買賣超)
T86 欄位中並不存在標記「合計」或「(含外資自營商)」的彙總欄位。若需要「含外資自營商」口徑,要自行把 index 4 (外陸資買賣超,不含) 與 index 7 (外資自營商買賣超) 相加;官方唯一的彙總欄位是 index 18「三大法人買賣超股數」。
抓錯欄位的後果:
- T86 的數值是帶千分位逗號的字串 (例如
'186,758,969',型別為字串),不是數字。直接做算術會得到NaN或字串串接,取值後必須先.replace(/,/g, '')去逗號再Number()轉換 - 即使索引抓錯 (例如抓到買進而非買賣超),取出的仍是一個合法字串,不會拋出錯誤
- 流程照樣完整跑完,推播照樣送出
- 數字本身看起來合理,沒有 null / undefined
- 只有人工比對官網數字才會發現抓錯欄位
這是典型的 silent failure:自動化流程持續穩定執行,但執行的是錯的事。錯誤堆疊看不到、靜態檢查看不到、單元測試若用了錯的測試資料也救不了。
另一個 silent failure 來源:全形括號 () 與半形 () 不同,字串比對會直接落空;若把備援邏輯寫成「找不到就用預設 index 0」,反而會無聲地抓到 證券代號。
⑤ 穩定取值的建議做法
做法 1:對齊完整欄位名稱
// 用完整欄位名
const idx = fields.indexOf('外陸資買賣超股數(不含外資自營商)');
if (idx === -1) {
throw new Error('Expected field not found in T86 fields');
}
const value = dataRow[idx];
indexOf 找不到時直接拋出錯誤,不要無聲地退回預設值。
做法 2:固定 index (需鎖定欄位結構)
T86 此欄位在 fields 陣列的 index 4 (0-based)。若確認 endpoint 結構穩定,可直接用 index:
// 對齊 T86 fields[4]「外陸資買賣超股數(不含外資自營商)」
const raw = dataRow[4];
const value = Number(raw.replace(/,/g, '')); // 數值為帶逗號字串,先去逗號再轉數字
但這需要先確認 endpoint 結構沒變,或加上結構驗證:
const expected = '外陸資買賣超股數(不含外資自營商)';
if (fields[4] !== expected) {
throw new Error(`T86 schema drift · expected fields[4] = "${expected}" got "${fields[4]}"`);
}
⑥ Edge case 已知
- 假日 / 國定假日:回傳
{ "stat": "很抱歉,沒有符合條件的資料!", "total": 0 },注意句中是全形逗號「,」(U+FF0C) 而非半形,且沒有fields/data欄位;最穩的判斷方式是stat !== 'OK',而不是比對整句字面值 - 回應慢:尖峰時段 (9-10 點) 約 5-8 秒,逾時設定 ≥ 15000ms,並加上 3 次重試
- 日期格式:此 endpoint 只接受西元 8 碼
YYYYMMDD。傳民國 7 碼 (例如1150508) 會被當成西元年 1150,回{"stat":"查詢日期小於101年05月02日,請重新查詢!","total":0} - 編碼:UTF-8,內含較多全形空白與中文,取值時用
fields陣列 index 比對較穩
⑦ 為什麼這個設計值得知道
對於用 n8n 或自建流程抓 T86 資料的開發者,欄位命名不對齊口語是一個常見的 silent failure 入口。對齊完整欄位名稱並加上結構驗證,才能避免推播錯誤數字;「沒有拋出錯誤」不等於「跑對了」,自動化的可靠性建立在每個欄位都人工對齊過一次真實來源,而不是靠關鍵字猜。
修正紀錄
先前版本有以下更正:
- §①:目標欄位
index 2→ index 4(index 2=買進、3=賣出、4=買賣超),並補上完整 19 欄對照。 - §①:無單獨的「自營商買進股數」欄位,只有(自行買賣) index 12 與(避險) index 15。
- §④:T86 無「含外資自營商」彙總欄位,該口徑需 index 4 + index 7 自行相加,官方唯一彙總為 index 18。
- §④:抓錯欄位取出的是帶逗號字串,非數字,需去逗號再轉數字。
- §⑤:做法 2 索引
dataRow[2]→ dataRow[4]。 - §⑥:假日的 stat 字串為全形逗號,實務以
stat !== 'OK'偵測較穩。 - §⑥:date 僅接受西元 8 碼
YYYYMMDD,不接受民國 7 碼。
※ 本文描述 TWSE T86 endpoint API 欄位結構與 silent failure 風險 · 不構成投資建議 · 投資請自行評估風險。
資料來源:TWSE OpenAPI T86
如果這篇對你有用: