iT邦幫忙

2026 iThome 鐵人賽

0

Day 9 留下了偵測器,但沒說它一開始有多爛。這一篇要講的是:規則寫出來了,為什麼只有 47% 的文件能找到表格,以及那兩個把數字推到 96% 的轉折。

先把「命中率」定義清楚

不然這個數字沒有意義。

我們抽樣了一批確定有表格的財報文件(人工看過),然後問一個問題:這份文件裡,至少被檢出一張表格的比例是多少?

  • 47%:第一版的成績。超過一半的文件,表格完全沒被看見。
  • 96%:改完之後的成績。
  • 剩下的 4%:是掃描件,不在射程內(Day 13 講)。

轉折一:只認一種線段原語

Day 9 的 detect_hlines() 裡有兩個分支:re(細長矩形)和 l(線段)。第一版只有 re。

這個假設很直覺:大多數 PDF 產生工具畫框線用的就是矩形——一條視覺上的線,在檔案裡是一個高度趨近於 0 的細長矩形。它在大部分文件上成立,所以第一版能跑。

問題出在上交所那批年報:表格框線是用線段原語(l)畫的。同一個視覺結果,兩種底層指令。只看 re 分支,整張表的行線一條都撈不到——不是少撈,是零。

補上 l 分支之後,那批文件整批回來了。

這一課我記得很深:「多數樣本成立」的假設,會在少數樣本上整批失手,而且非常安靜。 47% 這個數字,是靠「抽一批人工確認有表格的文件」跑出來的。

轉折二:垂直線不是裝飾,是欄邊界

行線撈到了,行數對了,但欄位還是錯的。

第一版的欄邊界是用「把這個區域所有橫線段的端點 x 座標蒐集起來、去重」算出來的。這在欄位整齊的表上能用,但只要出現跨欄的合併列(「合計」「其中:」那類),那一列的橫線就只有一條長線段,端點跟上面的欄位對不上,欄數當場跑掉——數字會落到隔壁欄,看起來像一份正常的表,只是每個數字都站錯位置。

修法是:優先找真正的垂直線。

def detect_vlines(page):
    """回傳 [(x, y0, y1)]"""
    vlines = []
    for d in page.get_drawings():
        width = d.get("width") or 0
        for item in d["items"]:
            if item[0] == "l":
                p1, p2 = item[1], item[2]
                x0, x1 = min(p1.x, p2.x), max(p1.x, p2.x)
                y0, y1 = min(p1.y, p2.y), max(p1.y, p2.y)
                if x1 - x0 < 1.0 and y1 - y0 > 10 and width < 1.5:
                    vlines.append((x0, y0, y1))
            elif item[0] == "re":
                r = item[1]
                if r.width < 1.5 and r.height > 10:
                    vlines.append((r.x0, r.y0, r.y1))
    return sorted(vlines)

注意垂直線的長度門檻只有 10 pt,比橫線的 50 pt 寬鬆——欄線本來就短,用橫線標準會把欄線全濾掉。

兩個細節不能省:

  • 只取落在這張表上下界之內的垂直線。 不然頁面邊框、頁眉線都會被當成欄邊界。
  • 垂直線是第一順位,不是唯一手段。 三線表(只有三條橫線、沒有垂直線)在財報裡很常見,這時 detect_vlines() 什麼都找不到,必須退回端點聯集的老方法。邏輯是:表格區域內找到兩條以上垂直線才用它,否則退回。

這兩個修改合起來,把命中率從 47% 推到了 96%。

實際在跑的門檻值

參數 值 作用
橫線:高度 < 1.5 pt 分開「線」與「色塊」
橫線:長度 > 50 pt 濾掉負號、破折號、底線
橫線:線寬 < 1.5 pt 濾掉粗框線
垂直線:寬度 < 1.5 pt 同上
垂直線:長度 > 10 pt 欄線短,門檻要放寬
行/欄聚類容差 3.0 pt 同一條線的座標抖動
表格塊最小行線數 4 條 太少不當表格
表格塊行距上限 80 pt 超過就切成兩張表

最後兩個是「表格在哪裡」的判斷:行線間距超過 80 pt 就視為斷開;同一塊至少 4 條行線才當一張表(3 條線只能圍出兩列,很容易是裝飾線)。

如果線條法失手:備案是模型

96% 之後,我還是找了一條備援路線。微軟的 Table Transformer(TATR),其中一版是在財務表格資料集(FinTabNet)微調的(microsoft/table-transformer-structure-recognition-v1.1-fin),28.8M 參數、約 115MB,純 torch 就能跑,不需要額外的 OCR 引擎。

我拿一頁全頁的資產負債表試過:切出 5 欄 × 48 列,欄邊界跟線條法幾乎一致。但它還是備案,不是主力:毫秒級 vs 需要 GPU,是兩個世界——規則引擎能命中的,沒理由每頁都跑一次神經網路。

剩下的 4%

96% 是文字型文件的上限。剩下約 4% 是純掃描件——沒有文字層,任何線條偵測都是白做。數字不大卻最容易被忽略:掃描件抽不出內容時,長得跟「這份文件沒有正文」一模一樣。Day 13 會講怎麼分開。

明天換第二個坑:那些整篇變成亂碼的 PDF。


本文為 2026 iThome 鐵人賽連載第 10 篇。系列主題:把東亞財報 PDF 煉成 LLM 讀得懂的 Markdown。


上一篇
坑一:你的表格不是表格,是向量線條畫出來的
系列文
30 天打造東亞財報全文 API:PDF 解析、亂碼修復、章節切分到 MCP 上線實錄 共 10 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言