Day 9 留下了偵測器,但沒說它一開始有多爛。這一篇要講的是:規則寫出來了,為什麼只有 47% 的文件能找到表格,以及那兩個把數字推到 96% 的轉折。
不然這個數字沒有意義。
我們抽樣了一批確定有表格的財報文件(人工看過),然後問一個問題:這份文件裡,至少被檢出一張表格的比例是多少?
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,是兩個世界——規則引擎能命中的,沒理由每頁都跑一次神經網路。
96% 是文字型文件的上限。剩下約 4% 是純掃描件——沒有文字層,任何線條偵測都是白做。數字不大卻最容易被忽略:掃描件抽不出內容時,長得跟「這份文件沒有正文」一模一樣。Day 13 會講怎麼分開。
明天換第二個坑:那些整篇變成亂碼的 PDF。
本文為 2026 iThome 鐵人賽連載第 10 篇。系列主題:把東亞財報 PDF 煉成 LLM 讀得懂的 Markdown。