space ocr
指南文章價格文件
receipts

自動從發票擷取明細品項

自動從發票與收據擷取明細品項並整理成結構化的列。定義一個陣列欄位,每個品項就成為一列可驗證的資料——每列都附帶定界框——並可匯出成 CSV。

7 分鐘閱讀· 2026-06-25

發票和收據是大家最想數位化的文件,可是最難搞定的從來都不是表頭。廠商名稱、日期、發票號碼這些都是單一的值,OCR 模型一次就能抓到。真正麻煩的是中間那張表:明細品項數量不固定,每一項都有品名、數量和金額,得整理成乾淨的資料列,才能加總、對帳,再匯入帳本。

這份指南會示範怎麼用 space-ocr 自動從發票擷取明細品項——不是攤平成一團文字,而是抽成結構化的陣列,每一行都是獨立的一列,而且每一格都還能對應回它在頁面上被讀取的精確位置。如果你要擷取的是整份文件,而不只是表格,請先從更完整的發票與收據 OCR 教學開始。

訣竅:把明細品項宣告成 array 欄位

大多數 OCR API 都只能讓你把表格當成一整串字串擷取出來,再自己去解析。space-ocr 則讓你直接在 schema 裡就把明細表描述清楚。一個帶有 children 清單的 type: "array" FieldSpec,等於在告訴引擎:這個區域會重複出現,而且每次重複都包含這些子欄位。

以下是一張收據的 schema 範例。商品(「items」)欄位是一個陣列,它的 children 是 商品名(品名)、数量(數量)和 単価(單價):

fields[] — 把明細品項當成陣列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "fields": [
    { "name": "店舗名", "type": "string", "description": "store name" },
    { "name": "日付",   "type": "string", "description": "date" },
    { "name": "合計",   "type": "string", "description": "total" },
    {
      "name": "商品",
      "type": "array",
      "description": "one row per line item",
      "children": [
        { "name": "商品名", "type": "string", "description": "item name" },
        { "name": "数量",   "type": "string", "description": "quantity" },
        { "name": "単価",   "type": "string", "description": "unit price" }
      ]
    }
  ]
}

把這份內容連同圖片 POST 到 POST /ocr/fields,陣列欄位就會以清單的形式回傳。這張收據會解析出 10 個明細品項ポッカレモン100359シール割引-34(折扣行,正負號會原樣保留)、エキストラBオリー698,依此類推。你完全不用寫資料列解析器、欄位拆分器,也不用碰正規表達式,只要把結構宣告一次就好。

從一張發票擷取明細品項
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
curl -s https://api.space-ocr.com/ocr/fields \
  -H "Authorization: Bearer $SPACE_OCR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://example.com/receipt.jpg",
    "imageType": "url",
    "fields": [
      { "name": "total", "type": "string" },
      { "name": "items", "type": "array",
        "children": [
          { "name": "description", "type": "string" },
          { "name": "qty",         "type": "string" },
          { "name": "unit_price",  "type": "string" }
        ] }
    ]
  }'

每一個明細品項都能獨立驗證

明細擷取最常出錯的地方就在這裡:模型回傳一張看起來工整的表,但其實微妙地對不齊——某個金額往上錯了一列,某個品名跟下一行黏在一起。而用 space-ocr 時,每一個陣列項目都帶著自己的 bboxverticesmatch_ratio,還有一份對應其 children 的 field_bboxes map。收據裡單獨一行長這樣:

商品 陣列裡的其中一項(節錄)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
  "商品名": "ポッカレモン100",
  "単価": "359",
  "match_ratio": 1.0,
  "bbox_source": "vision_symbol_match",
  "field_bboxes": {
    "単価": {
      "bbox": { "xmin": 450, "ymin": 356, "xmax": 484, "ymax": 378 },
      "vertices": [
        { "x": 450, "y": 360 }, { "x": 483, "y": 356 },
        { "x": 485, "y": 374 }, { "x": 452, "y": 378 }
      ],
      "match_ratio": 1.0
    }
  }
}

所以一個金額不只是 359——它是落在 0–1000 normalized 座標格(xmin/ymin/xmax/ymax,原點在左上角)上某個特定矩形裡的 359,附帶四個會跟著文件傾斜角度走的 vertices,以及一個 match_ratio,告訴你這段文字實際上有多少在頁面上被找到。match_ratio1.0 代表每一個字元都定位到了;引擎把 ≥ 0.85 視為可信的比對。你可以依比對比率把擷取出來的資料列排序,只用肉眼檢查最弱的那幾筆。完整的運作機制請參考用邊界框驗證 OCR

✓ Verified

那些座標不是模型編出來的。語言模型回傳的是每個明細品項的文字,外加它用了哪些 word token 的提示,但從不回傳框。接著由引擎拿這段文字,去和視覺 OCR 在頁面上實際偵測到的符號做逐字元比對,並針對每個值回報一個 match_ratio,表示它找到了多少。模型給的 token 提示在重複的資料列之間往往不太穩,所以系統不會盲目信任,而是用欄一致性與列一致性檢查去驗證——這一點在 30 列、每行看起來都很像的表格上尤其重要。這就是讓表格可被檢查、而不只是看起來合理的關鍵:每一行都帶著一個分數,說明它和頁面比對得有多好。

點一下某一行,直接落在像素上

因為每個明細品項都知道自己在哪裡,抽查一張表就變成點一下的事。在 app 裡點任何一格——品名、數量、單價——原始圖片就會把這個值的來源區域精確地高亮起來,還附上放大裁切。就算是一張三十行的發票,你的視線也能直接落在看起來不對勁的那一行,不必整頁一行行掃過去。

點任何一個明細品項格 → 對應區域就在原始發票上亮起來。

從明細品項到會計工具讀得懂的 CSV

明細品項一旦存進工作表,匯出時陣列結構的好處又派上用場了。space-ocr 會在匯出時把陣列欄位展開:表頭變成 # 加上各個純量欄位,再為每個陣列 child 各加一欄,欄名為 colName.childName(也就是 商品.商品名商品.数量商品.単価)。每個明細品項都各自成為一列子資料列——一張有 10 項的收據會產生 10 列,每列都重複帶著相同的店名和日期。這正是試算表和帳本匯入工具想要的那種又長又扁的格式。

匯出工作表——陣列明細品項會展開成每項一列,欄名為 colName.childName

把這張收據匯出後,裁剪一下大致是這樣:

#店舗名日付商品.商品名商品.単価
1KINSHO2019年08月17日ポッカレモン100359
2KINSHO2019年08月17日エキストラBオリー698
3KINSHO2019年08月17日シール割引-34

檔案是帶 BOM 的 UTF-8,所以日文、韓文和中文的品名在 Excel 裡都能正常打開。你手動改過的任何值,匯出時都會覆蓋 OCR 的值,而原始值仍會留底備查。完整的「圖片資料夾到試算表」流程,請參考掃描文件轉 CSV

幾個步驟就搞定

  1. 為明細品項定義一個陣列欄位
    在你的 fields[] schema 裡加入一個 type 為 "array" 且帶有 children 清單的欄位,例如 description、qty、unit_price。這會告訴引擎:明細品項區域會帶著這些子欄位重複出現。
  2. 把發票送到 /ocr/fields
    把圖片(以 URL 或 base64 的形式)連同 imageType 和你的 fields[] 一起 POST 到 https://api.space-ocr.com/ocr/fields。陣列欄位會以清單回傳,每個明細品項一個物件。
  3. 驗證每一行
    每個陣列項目都帶著自己的 bbox、vertices 和 match_ratio。可以依 match_ratio 排序,或在 app 裡點某一格,跳到圖片上的精確區域確認該值對不對。
  4. 匯出成 CSV
    匯出工作表時,陣列 children 會展開成 colName.childName 欄位,每個明細品項各自成為一列,並重複帶上文件層級的欄位,直接就能餵給你的會計工具。
我要怎麼自動從發票擷取明細品項?
把明細表宣告成一個 type 為 "array" 的欄位,並附上 children 清單(例如 description、qty、unit_price),然後把圖片 POST 到 /ocr/fields。引擎會把陣列以資料列清單的形式回傳,每個明細品項一個物件,你完全不用寫任何解析表格的程式碼。每一項還會帶著自己的邊界框、vertices 和比對比率。
OCR 能處理每張發票明細品項數量不固定的情況嗎?
可以。陣列欄位並不假設資料列數固定。示範裡的收據解析出 10 項,另一張發票可能會有 30 項。引擎會從偵測到的文字版面把重複的資料列分組,所以頁面上有幾行,你就會得到幾個明細品項物件,而且每一個都在圖片上獨立定位。
明細品項在 CSV 匯出裡會怎麼呈現?
陣列欄位在匯出時會展開。表頭是 '#' 加上各純量欄位,再為每個陣列 child 各加一欄,欄名為 colName.childName(例如 items.description、items.qty、items.unit_price)。每個明細品項都各自成為一列子資料列,並重複帶上文件層級的欄位(如廠商和日期),這正是帳本和試算表匯入工具想要的扁平格式。檔案是帶 BOM 的 UTF-8,CJK 文字在 Excel 裡能乾淨顯示。
我怎麼知道某個明細品項有沒有讀對?
每個陣列項目都會附上一個 match_ratio(它的字元中有多少比例在頁面上被定位到)和一個邊界框。match_ratio 為 1.0 代表每一個字元都找到了;引擎把 0.85 以上視為可信的比對。你可以依比對比率把資料列排序,只檢視最弱的那幾筆,或在 app 裡點某一格,把它的來源區域精確高亮出來。
它能處理非英文的發票嗎?
可以。語言偵測是自動的,日文、韓文、中文和英文都跑同一個引擎,連全形字元和直書的 CJK 文字也一併支援。示範擷取的就是一張日文收據的 商品(items)陣列,children 是 商品名、数量 和 単価。不需要設定任何語言旗標。
相關文章