floviq logo floviq
← 筆記 · 2026-06-02 · mechanics

TWSE MOPS 月營收端點的「出表日期」vs「資料年月」— 兩個不同欄位的用途差異與 silent failure 機制

串接台股月營收資料時,TWSE MOPS 月營收端點(t187ap05_L)的每筆回傳包含兩個看起來都跟「哪個月」有關的欄位:出表日期(申報揭露日)與資料年月(實際營收所屬月份)。兩者不是同一件事,用錯欄位會造成整月位移的 silent failure:流程照樣跑完、數字看起來合理、不拋出任何錯誤,只有人工比對月份時才會察覺。

本文整理這兩個欄位的定義差異、民國字串解析規則、千元單位陷阱,以及穩定取值的建議做法。


① t187ap05_L 端點與欄位結構

GET https://openapi.twse.com.tw/v1/opendata/t187ap05_L

此端點為 TWSE MOPS 上市公司每月營業收入彙總,每月彙整更新一次。批次內所有公司共用同一個 出表日期(即該批次資料的釋出日);該釋出日與「公司須於次月 10 日前完成申報」的法定截止日是兩回事 — 釋出日可能落在截止日之後(本批次 出表日期 = 1150517,即 2026-05-17,對應 4 月營收批次)。回傳為 JSON 陣列,每筆代表一間上市公司當月申報結果。

以下欄位清單以 2026-05-28 快照(民國 115 年 4 月營收批次)為準,台泥(1101)為 schema 示意:

欄位名範例值型別說明
出表日期"1150517"字串,7 碼民國年申報揭露日
資料年月"11504"字串,5 碼民國年月實際營收所屬月份
公司代號"1101"字串股票代號
公司名稱"台泥"字串公司名稱(僅供 schema 示意,非市場判斷依據)
產業別"水泥工業"字串TWSE 產業分類
營業收入-當月營收"12213195"字串,單位千元當月實際營收
營業收入-上月營收"12412837"字串,單位千元前一個月的營收
營業收入-去年當月營收"12681318"字串,單位千元去年同月營收
營業收入-上月比較增減(%)"-1.6083510965301486"字串,超長小數與上月比較增減幅度
營業收入-去年同月增減(%)"-3.6914380666110573"字串,超長小數與去年同月比較增減幅度
累計營業收入-當月累計營收"45472613"字串,單位千元當年度累積至當月
累計營業收入-去年累計營收"47653544"字串,單位千元去年同期累積
累計營業收入-前期比較增減(%)"-4.576639672381974"字串,超長小數累計與去年同期比較增減幅度
備註"-"字串公司自填說明

所有數值欄位均以字串型別回傳,計算前需明確轉型。百分比欄位為超長小數字串(例 -1.6083510965301486),顯示前建議 round 至 1 位;空字串或非數字輸入需另行處理。


② 出表日期 vs 資料年月:兩欄各是什麼

這是此端點最容易誤用的核心差異。

出表日期(7 碼民國年 YYYMMDD)= 這筆數字申報上 MOPS 的日期

以台泥(1101)2026 年 4 月快照為例:出表日期"1150517",解析為民國 115 年 5 月 17 日,即 2026 年 5 月 17 日。這是台泥向主管機關申報、揭露在 MOPS 的時間點。

資料年月(5 碼民國年月 YYYMM)= 這筆數字所屬的營收月份

同一筆資料的 資料年月"11504",解析為民國 115 年 04 月,即 2026 年 4 月。這是台泥 4 月份的實際營收。

兩個欄位的時間差在一個月左右:4 月的營收,在 5 月 17 日申報揭露。這個時差是制度設計的結果,不是資料異常。

格式差異一覽

欄位格式碼數含義
出表日期ROC YYYMMDD7 碼,含日申報日(5 月)
資料年月ROC YYYMM5 碼,無日所屬月份(4 月)

資料年月 為 5 碼,不含日。解析時不可套用 YYYMMDD 的 7 碼格式,也不可用 new Date() 直接傳入,需手動拆年份(前 3 碼)和月份(後 2 碼)再轉換:

// 資料年月: "11504" → 2026-04
const raw = "11504";
const rocYear = parseInt(raw.slice(0, 3), 10);  // 115
const month   = raw.slice(3, 5);            // "04"
const adYear  = rocYear + 1911;             // 2026
const period  = `${adYear}-${month}`;       // "2026-04"

③ 為什麼會錯 — silent failure 機制

出表日期 當營收月份是這個端點最典型的 silent failure。

典型的錯誤邏輯是:「拿到這筆資料 → 取 出表日期 當作這筆營收屬於 5 月 → 存入 pipeline 標記為 5 月數據。」但這筆資料的實際內容是 4 月的營收,只是在 5 月申報揭露。結果是每一筆資料都整月位移:4 月營收被標成 5 月,3 月營收被標成 4 月,依此類推。

這個錯誤的 silent failure 特徵:

  • 流程不報錯:出表日期 是合法的字串,parse 不會 throw
  • 數字看起來合理:位移後的月份數字本身沒有異常,在合理的量級內
  • 推播正常完成:自動化通知會如常送出
  • 發現困難:只有在人工把端點回傳的 資料年月 和自己記錄的「月份標記」交叉比對時,才能察覺整批數據月份都差了一個月

其餘常見的 silent failure 入口:

民國 5 碼無日,不可直接套 7 碼解析邏輯"11504" 若誤套 YYYMMDD 格式,會把後 2 碼 04 讀成日期,月份部分變成空缺或亂讀。不報錯,但解出的日期是錯的。

千元單位造成數字看起來天文數字"12213195" 的單位是千元,意即 12,213,195,000 元(約 122 億)。若未轉換直接顯示,數字量級誤導判讀。換算為億元的公式:

// 營業收入欄位單位: 千元 → 億元
// 12213195 千元 = 122.13 億
const rawKUnit  = parseInt("12213195", 10);    // 千元
const hundredMil = rawKUnit / 100000;       // ÷ 100000 = 億元(保留 2 位)
const display   = hundredMil.toFixed(2) + " 億";  // "122.13 億"

百分比欄位為空字串或非數字。部分公司某些比較期間的百分比欄位回傳空字串 ""(例如新掛牌公司無去年同期),或前期基期為 0 時出現極端值(例如 "161502.90456431537")。直接呼叫 parseFloat() 不做空值判斷,會帶入 NaN 進入下游計算。


④ 穩定取值的建議做法

做法 1:永遠以 資料年月 為營收月份識別欄位

出表日期 的正確用途是「這批資料是什麼時候上線的」,適合用作 pipeline 的新鮮度判斷或 log 記錄,不應作為「這筆數據屬於哪個月」的依據。

// 取 資料年月 作為月份識別
function parseRevenuePeriod(raw) {
  // raw: "11504" (ROC YYYMM, 5 碼, 無日)
  if (!raw || raw.length !== 5) {
    throw new Error(`Unexpected 資料年月 format: "${raw}"`);
  }
  const rocYear = parseInt(raw.slice(0, 3), 10);
  const month   = raw.slice(3, 5);
  const adYear  = rocYear + 1911;
  return `${adYear}-${month}`;  // "2026-04"
}

indexOf / 欄位 key 不存在時直接拋出錯誤,不要靜默退回預設值。

做法 2:千元 → 億元換算在資料入口統一處理

// 千元→億元,回傳 2 位小數字串
function kUnitToHundredMil(rawStr) {
  if (!rawStr || rawStr === "") return null;
  const val = parseInt(rawStr, 10);
  if (isNaN(val)) return null;
  return (val / 100000).toFixed(2);
}

做法 3:百分比欄位統一做空值與極端值過濾

// 百分比欄位: 超長小數字串 → round 1 位,空字串/NaN → null
function parseRevenuePct(rawStr) {
  if (!rawStr || rawStr === "") return null;
  const val = parseFloat(rawStr);
  if (isNaN(val)) return null;
  // 前期基期為 0 時可能出現極端值,視使用情境決定是否 cap
  return val.toFixed(1);  // e.g. "-1.6"
}

做法 4:資料年月 欄位不存在時直接拋出,不靜默降級

const record = apiResponse[0];
const period = record["資料年月"];
if (period === undefined || period === null) {
  throw new Error("t187ap05_L: 資料年月 field missing — possible schema change");
}

⑤ 為什麼值得知道

對用 n8n 或自建流程串接月營收端點的開發者,出表日期 vs 資料年月 的混淆是月份標記位移的最常見來源。這個端點的批次更新節奏是:公司次月 10 日前完成申報(法定截止),TWSE 彙整後統一釋出批次;釋出後每次呼叫拿到的 出表日期 都是批次釋出當月,而 資料年月 都是上個月 — 這個設計符合申報制度邏輯,但對自動化流程的欄位選取形成干擾。

對存股族追蹤自選標的月營收趨勢的使用情境,正確解析 資料年月(而非 出表日期)是月份比較準確的前提。月對月、年對年的比較欄位(上月比較增減(%)去年同月增減(%))已由 TWSE 計算在端點回傳內,但分子分母的正確定位建立在先確認 資料年月 是目標月份。

想把 TWSE 月營收端點的資料自動整理、每月送到自己的 Telegram 或 LINE?floviq 月營收追蹤模板(portaly.cc/floviq)處理了這篇整理的四個取值眉角,包含欄位對齊、民國年解析、千元換算與空值過濾,設定一次後每月自動跑。


⑥ Edge case 已知

端點一次回傳全量上市公司t187ap05_L 每次呼叫回傳當批次所有上市公司的月營收,不支援單一公司查詢參數。若只需要特定代號,需要在 client 端過濾 公司代號 欄位。

新上市公司與去年同期空缺:部分新掛牌公司無去年同期資料,去年同月增減(%) 欄位回傳空字串 ""去年當月營收 欄位可能為 "0"。不做空值檢查直接計算增減會得到 NaN 或除以 0 的極端值。

備註欄位有實質內容:多數公司的 備註 欄位為 "-",但部分公司會填入營收變化說明(例如「主因子公司營收上升」),自動化流程若需要呈現這欄,需過濾 "-" 這個哨兵值。

月份更新時間點與批次釋出:每月 10 日為公司申報截止日。TWSE 在截止日之後彙整為批次統一釋出;出表日期 是該批次的統一資料釋出日(同一批次每筆紀錄的值完全相同),不是各公司各自的申報時間戳。呼叫時機若在批次尚未釋出之前,端點可能仍回傳上一批次資料。

申報截止日落在假日時的順延:若次月 10 日為假日,申報截止日順延至下一個營業日,批次釋出時間預期也會落在順延後的截止日之後。但無論釋出日如何浮動,資料年月 都不受影響,仍代表該批次所屬月份的營收期間,是月份定位的唯一可靠欄位。


⑦ See also


※ 本文描述 TWSE MOPS t187ap05_L 月營收端點的欄位機制、申報日期與資料所屬月份的差異,以及相關的 silent failure 風險 · 不構成投資建議,投資請自行評估風險。文中台泥(1101)資料僅作為端點欄位結構的示意,不構成對該公司任何評估。

資料來源:TWSE OpenAPI t187ap05_L(資料快照 2026-05-28,回傳批次 資料年月 = "11504" 即民國 115 年 4 月)