floviq logo floviq
← 筆記 · 2026-07-04 · mechanics

TWSE MOPS 重大訊息端點 t187ap04_L 欄位解析 — 事實發生日 vs 發言日期的時序陷阱與符合條款解碼

同一筆重大訊息裡,事實發生日發言日期 早了將近一個交易日,兩個欄位卻常被當成同一件事處理——這是 TWSE MOPS 重大訊息端點 t187ap04_L 最容易踩的坑。多數串接邏輯只挑其中一個日期欄位當作「這則新聞的時間」,但端點實際回傳三個語義不同的日期欄位:出表日期發言日期事實發生日,三者最多可以相差數天。除了日期欄位,這個端點還有兩個典型的欄位陷阱:欄位名 主旨 含尾部空白(直接用 主旨 取值會 KeyError),以及 符合條款 只存條款編號不附文字說明,需要另外對照證交所公開的處理程序文件才能解讀意思。

本文整理三個日期欄位的定義差異、欄位命名陷阱、符合條款 的解碼方式,以及穩定取值的建議做法。


① t187ap04_L 端點與欄位結構

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

此端點為 TWSE MOPS 上市公司重大訊息公開資訊,回傳為 JSON 陣列,每筆代表一則公司發布的重大訊息。以下欄位清單以 2026-07-04 快照為準,公司代號以 #### 遮罩,僅作 schema 示意:

欄位名範例值(遮罩後)型別說明
出表日期"1150704"字串,7 碼民國年此批次資料的釋出日,全批次同一天
發言日期"1150703"字串,7 碼民國年公司對外發言(提交重大訊息)的日期
發言時間"54626"字串,時分秒發言的時間戳,配合 發言日期 定位精確發布時刻
公司代號"####"字串股票代號
公司名稱"####"字串公司名稱(僅供 schema 示意)
主旨 "公告本公司..."字串欄位名含尾部空白 — 訊息主旨
符合條款"第51款"字串對應處理程序條文款號,只存號碼不附說明
事實發生日"1150702"字串,7 碼民國年該重大事件實際發生的日期
說明"1.事實發生日:115/07/02\n2..."字串完整重大訊息內容,含編號段落

出表日期 為 7 碼民國年格式(如 "1150704"),與 TWSE 其他端點常見的民國年編碼一致,本文不重複拆解 parse 邏輯——完整的 ROC 日期格式對照見〈TWSE OpenAPI 日期格式陷阱〉。

出表日期 是整批次的統一釋出日,同一批次內每一筆紀錄的 出表日期 完全相同——這個「批次內同值」的行為與月營收端點 t187ap05_L出表日期 語義一致,細節見〈TWSE MOPS 月營收出表日期機制〉。本篇的日期核心不重複這件事,聚焦在 t187ap04_L 特有的第二層時序問題:發言日期事實發生日 的落差。


② 三個日期欄位語義完全不同

t187ap04_L 一筆紀錄裡同時存在三個日期欄位,各自代表不同的時間點,混用會造成數天、甚至數個交易日的時序錯誤。

出表日期 = 這批資料上 MOPS 開放資料的釋出日(批次層級,全批次同值,非個別訊息的時間戳)。

發言日期 = 公司實際對外提交這則重大訊息的日期。這是「公司說了什麼、什麼時候說的」。

事實發生日 = 觸發這則重大訊息的事件,實際發生的日期。這是「事情本身、什麼時候發生的」。

以 2026-07-04 快照第一筆為例:發言日期"1150703"(民國 115 年 7 月 3 日),但 事實發生日"1150702"(民國 115 年 7 月 2 日)——事件發生在 7 月 2 日,公司隔天 7 月 3 日才對外發言。同一份快照另一筆案例的落差更大:發言日期"1150703"事實發生日 卻是 "1150629"——事件早在 6 月 29 日已發生(一次公司名稱變更核准),但重大訊息在四個交易日後的 7 月 3 日才發布。

這個落差不是資料異常,而是制度設計的必然結果:依證交所《對有價證券上市公司重大訊息之查證暨公開處理程序》,重大訊息原則上須於事實發生日之次一營業日交易時間開始二小時前輸入公告,而公司評估、內部簽核、對外發言稿撰寫也都需要時間,事實發生日發言日期 之間本來就存在合理的處理窗口。但如果自動化流程把 發言日期 當作「事件發生的時間」寫進時間軸或推播訊息,時序上就會系統性偏晚——對於需要精確重建事件發生順序(例如比對股價異動與消息面的時間關係)的用途,這個偏差足以讓整條時間軸位移數天。

穩定取值原則:需要「事件本身何時發生」→ 取 事實發生日;需要「公司何時對外揭露」→ 取 發言日期 + 發言時間;需要「這批資料何時釋出到 API」→ 取 出表日期。三者不可互相替代。

// t187ap04_L 三個日期欄位各自對應不同語意,不可混用
function parseDisclosureTimeline(record) {
  return {
    releasedAt: record["出表日期"],     // 批次釋出日(全批次同值)
    spokenAt:   record["發言日期"],     // 公司對外發言日
    spokenTime: record["發言時間"],     // 發言時分秒
    eventAt:    record["事實發生日"],   // 事件實際發生日 — 時間軸重建用這個
  };
}

③ 欄位名 主旨 含尾部空白 — 直接取值會 KeyError

t187ap04_L 的主旨欄位,官方回傳的欄位名不是 主旨,而是 主旨 (末尾多一個半形空白字元 U+0020,以 2026-07-04 快照對原始 JSON key 逐字元檢查確認)。這個空白在瀏覽器或編輯器裡幾乎不可見,但對程式取值是致命的:

# 錯誤:直接用「主旨」取值
record = response_json[0]
subject = record["主旨"]
# KeyError: '主旨'
# 正確:欄位名末尾含空白
subject = record["主旨 "]

這類「欄位名含隱藏字元」的坑不會在測試少量欄位時被發現——只有實際呼叫 API 並列印 record.keys() 才會看到欄位名真正的字串內容。建議串接前一律先印出完整欄位名清單再寫死取值邏輯,不要憑記憶或憑文件描述手打欄位名。

# 穩定做法:用 list(record.keys()) 核對真實欄位名,不要手打猜測
print(list(response_json[0].keys()))
# ['出表日期', '發言日期', '發言時間', '公司代號', '公司名稱',
#  '主旨 ', '符合條款', '事實發生日', '說明']

符合條款 只存條款號碼,需另外解碼

符合條款 欄位回傳的值是純號碼字串,例如 "第51款""第10款",不附帶任何條款內容說明。這個號碼對應的是證交所《對有價證券上市公司重大訊息之查證暨公開處理程序》第 4 條第 1 項各款——該條文列出 50 款具體事由(例如股權變動、財務業務重大事件、訴訟、經營權異動等),第 51 款則是概括條款,適用於前 50 款未列舉但仍對股東權益或證券價格有重大影響的事項。

也就是說,符合條款"第51款" 只能告訴你「這則訊息屬於概括條款範疇」,實際內容仍要看 說明 欄位的自由文字,或另外查詢處理程序全文才能知道第 10 款、第 51 款等各款具體對應什麼類型的事件。單純用 符合條款 的號碼做分類,等於只做到「有 vs 沒有列舉在明確款項內」的粗分類,無法區分具體事件類型。

// 符合條款只有號碼,判斷事件類型仍需搭配 說明 欄位內容
// 不可只憑 符合條款 號碼推論事件性質
function needsFullTextForClassification(record) {
  // "第51款" 是概括條款,涵蓋範圍最廣,號碼本身資訊量最低
  return record["符合條款"] === "第51款";
}

需要完整對照表時,條款原文見證交所公告的《對有價證券上市公司重大訊息之查證暨公開處理程序》,該文件為法規性質,非本端點回傳內容,需另行取得後自行維護對照表。


⑤ 穩定取值的建議做法

做法 1:欄位名一律先印出 keys() 核對,不手打猜測(同 §③)。

做法 2:三個日期欄位依用途分流讀取,不互相替代

function buildDisclosureRecord(raw) {
  const subject = raw["主旨 "];
  if (subject === undefined) {
    throw new Error("t187ap04_L: 主旨 field missing (check trailing space) — possible schema change");
  }
  return {
    subject,
    clause: raw["符合條款"],
    eventDate: raw["事實發生日"],       // 事件時間軸用這個
    disclosedDate: raw["發言日期"],     // 公司揭露動作用這個
    disclosedTime: raw["發言時間"],
    batchReleaseDate: raw["出表日期"],  // 批次新鮮度判斷用這個
  };
}

做法 3:符合條款 只做粗分類,需要精細分類時搭配 說明 欄位全文解析或另建條款對照表,不單靠號碼判斷事件性質。

做法 4:欄位缺失時直接拋出,不靜默降級——尤其 主旨 這種含空白的欄位名,一旦官方未來調整命名(例如移除空白),靜默 fallback 會讓資料悄悄變成空值而不易察覺。


⑥ Edge case 已知

同一天可能有大量重大訊息同時發布:批次內單日筆數可能達數百筆,出表日期 全部相同,需靠 發言時間事實發生日 進一步排序,不能單靠 出表日期 做時間排序。

事實發生日 早於 發言日期 超過法定申報窗口的情況:多數案例落差在 0-2 個交易日內,但涉及公司名稱變更、股東會決議等需走完內部流程的事件,事實發生日發言日期 的落差可能達數個交易日,屬於正常範圍,不代表資料異常。

說明 欄位內含手動編號的自由文字:公司自行填寫,格式不統一(有些用「1.」有些用全形數字),不建議用正則從 說明 內解析出結構化欄位(例如再次抓取事實發生日),應直接信任 事實發生日 這個獨立欄位。

新掛牌或名稱變更類事件的 符合條款 常落在概括條款:本文 §④ 引用的公司名稱變更案例即為 "第51款",不是因為公司名稱變更特別重大,而是因為第 51 款概括條款覆蓋範圍廣,許多未特別列舉的行政性公告也會歸類於此。


⑦ 為什麼值得知道

對用 n8n 或自建流程監控重大訊息的開發者,發言日期事實發生日 的混淆是時間軸重建最常見的誤差來源——尤其在需要比對「消息面時間點」與「其他市場資料時間點」的自動化情境,選錯日期欄位會讓整條分析時間軸系統性偏晚數天,而且流程本身完全不會報錯。主旨 欄位名的隱藏空白則是另一種典型的「文件看起來對、程式跑起來炸」的坑,唯一根治做法是先印出真實欄位名再寫死程式邏輯。

想把 TWSE 的台股公開資料自動整理、即時送到自己的 Telegram 或 LINE?floviq 台股工具模板(portaly.cc/floviq)提供一系列 n8n 現成模板,處理的正是這類「官方欄位陷阱先踩平、設定一次自動運作」的資料串接工作。


⑧ See also


※ 本文描述 TWSE MOPS 重大訊息端點 t187ap04_L 的欄位機制、日期欄位語義差異與符合條款解碼方式 · 不構成投資建議,投資請自行評估風險。文中公司代號與名稱已遮罩,僅作為端點欄位結構的示意,不構成對任何公司之評估。

資料來源:TWSE OpenAPI t187ap04_L(資料快照 2026-07-04);證交所《對有價證券上市公司重大訊息之查證暨公開處理程序》