space ocr
指南文章价格文档
documents

如何从图片中提取表格数据并转换为CSV

将表格、订单或送货单的照片转换成干净的CSV文件。了解 space-ocr 如何读取分项明细,以及行级校验如何把需要人工核对的值挑出来。

4 分钟阅读· 2026-08-31

将扫描件或图片里的表格数据录入到电子表格中,是一项费时费力的传统工作。你可能有一张清晰的送货单或采购单图片,上面列满了各种项目,但它们都只是像素点。接下来的步骤通常是繁琐的手工录入:逐字逐句地将品名、数量和价格复制到新的一行里。这个过程不仅慢,而且一个打字错误就可能让整份数据出错。

一份表格形式的订单/送货单文件
一个分项列表格——多行数据,输出格式统一。

一个更高效的方法是,把表格的结构定义为一份 schema(列结构)。你不再是简单地提取一大块文本,而是预先声明所需要的列。重复出现的分项区块定义为 array 类型字段,并在它的子字段(children)里排好各列;对于始终是数字的列,直接声明类型。

1
2
3
4
5
6
7
8
9
10
{
  "name": "items",
  "type": "array",
  "children": [
    { "name": "name",   "type": "string" },
    { "name": "qty",    "type": "integer" },
    { "name": "price",  "type": "number" },
    { "name": "amount", "type": "number" }
  ]
}

你不需要指定行数,返回多少行由页面本身决定。每一行都以带索引的路径返回(values.items[0]、values.items[1] ……),同一条路径也就是逐值坐标与校验映射的键 cells["items[0].price"]。声明类型不会改变提取到的值,因为类型不会传给模型;它增加的是与 values 并列的第二层确定性数据 data.normalized。

为分项列表定义一个数组字段,然后上传图片,即可将表格提取到一个结构化的网格中。

即使是包含重复值的密集型表格,这种方法也同样有效。系统首先使用大型语言模型来初步提取文本,但这只是第一步。对于每一个值,比如品名「刻みたくあん」或单价「580」,系统都会进行交叉验证。它会把语言模型读到的内容与文档的列结构比对,并与页面上原始识别出的 OCR 符号逐字符匹配。当某个值滑到相邻行时,该坐标处的字符通常就对不上了,这一字段不会被悄悄放行,而是记入待核对清单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
"data": {
  "values": {
    "items": [
      { "name": "刻みたくあん", "qty": "3",
        "price": "580", "amount": "1,740" }
    ]
  },
  "cells": {
    "items[0]": {
      "box": { "xmin": 263, "ymin": 460,
               "xmax": 738, "ymax": 523 },
      "quad": [ { "x": 263, "y": 460 }, { "x": 738, "y": 460 },
                { "x": 738, "y": 523 }, { "x": 263, "y": 523 } ],
      "verified": null, "review": null
    },
    "items[0].name": {
      "box": {…}, "quad": […],
      "verified": true, "review": null,
      "evidence": { "text_match": true, "match_ratio": 1.0 }
    },
    "items[0].qty": { "box": {…}, "quad": […],
                      "verified": true, "review": null },
    "items[0].price": {
      "box": { "xmin": 693, "ymin": 460,
               "xmax": 738, "ymax": 488 },
      "quad": […],
      "verified": false,
      "review": { "reasons": ["text_mismatch"] },
      "evidence": { "text_match": false, "match_ratio": 0.62 }
    }
  },
  "review": {
    "unit": "field",
    "flagged": [
      { "path": "items[0].price", "reasons": ["text_mismatch"] }
    ]
  },
  "normalized": {
    "items": [ { "qty": 3, "price": 580, "amount": 1740 } ]
  }
}

这份清单就是工作队列:data.review.flagged 会点名具体路径和理由,cells["items[0].price"] 则保留了当时比对的坐标。但它并非万无一失。相邻行若印着同样的数字,无论框落在哪一行,字符都能对上;两个引擎若犯了同一个误读,也就没有可供对照的一方。因此,字符比对看不见的行列错位,值得交给可声明的规则:编码列用 pattern,主数据里已有的值用 enum,合理区间用 min 与 max。任何违反都会出现在同一份 review.flagged 中。

最后一层是你自己的核算,它属于下游。由于 qty 声明为 integer、金额列声明为 number,data.normalized 里放的是解析后的数值("1,740" 即 1740),所以一行的核算只需一次乘法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const { values, normalized, review } = data;

const rows = values.items.map((item, i) => {
  const n = normalized.items[i];
  const flagged = review.flagged.some((f) =>
    f.path.startsWith(`items[${i}]`)
  );
  return {
    name: item.name,
    qty: n.qty,
    price: n.price,
    amount: n.amount,
    // 需要人工核对的行:被标记,或这一行算不平
    check:
      flagged || n.qty == null || n.qty * n.price !== n.amount,
  };
});

无法解析的叶子在 normalized 中返回 null,失败类型在 cells[path].normalized.error,并以 type_mismatch 出现在待核对清单里。计算时取 normalized,展示时用 values——带着坐标和校验的是 values 这一侧。

数据提取完成后,只需一键即可将整个工作表导出为干净的CSV文件。
✓ Verified

每个值都会与它所在的那一页对照。系统把模型读到的内容与该处实际识别出的 OCR 符号逐字符匹配,匹配了多少记录在 evidence.match_ratio 里;达到 0.85 或更高即为高置信匹配。结论本身是 verified,它是 review 的镜像:只要有任何标记就是 false,比对跑过且没有标记则是 true,没有可比对的对象(例如整行的并集框)则是 null。坐标由匹配上的符号得出,以 box 和 quad 表示,并相对 data.image 归一化到 0–1000 的范围。这是值来自何处的证据,而不是它就是你要的那个值的证明;没有通过核对的值,都列在 review.flagged 里。

费用按使用量计算,每处理一张图片收费$0.05。您的账户每月包含100次免费扫描。如果因任何原因提取失败,我们不会收取任何费用。

  1. 定义工作表列结构 (Schema)
    创建一个新的工作表(Sheet)并定义您需要的列。对于分项列表,请使用'array'类型,并为其添加名称、数量、价格等子列。
  2. 上传您的图片
    通过拖拽或使用API将表格图片上传到该工作表。
  3. 检查提取的数据
    图片将根据您定义的列结构进行处理。表格中的每个分项都会在工作表中显示为一行结构化数据。
  4. 按需修正
    点击任意单元格,即可在图片上看到对应的区域。您可以直接在网格中手动修正任何数值。
  5. 导出为CSV
    点击“导出”按钮并选择CSV格式。您的表格数据(包括所有分项)将被下载为一个干净、结构化的文件。
如果我的表格有合并单元格或复杂的布局怎么办?
本系统专为标准的行列式表格设计。对于非常复杂的布局,您可以定义多份 schema(列结构),或在初步提取后于工作表中手动调整数据。
CSV导出功能如何处理分项列表?
如果您的数组列名为'items',且其子列为'name'和'price',那么导出的CSV文件表头将是'items.name'和'items.price'。图片中的每一个分项都会成为CSV文件中的独立一行。
我可以处理带有表格的PDF文件吗?
可以,在网页应用中支持。您可以直接拖入一个PDF文件,系统会自动将其每一页渲染成图片进行处理。API本身接受的是如JPEG和PNG等光栅图像格式。
每个单元格的坐标是如何确定的?
对于每个提取出的值,系统会将其字符与页面上 OCR 识别出的符号进行匹配。匹配上的符号决定了该值的 `box` 与 `quad`,并相对实际读取的页面(`data.image`)归一化到 0–1000 的范围。这组坐标是该值来源的证据,您可以把它画回图片上自行核对。
表格的行数有限制吗?
没有可设置的行数上限,返回多少行取决于页面本身。真正有上限的是同步调用:处理时间上限为 180 秒,超时返回 504 `ocr_engine_timeout`,且不计费。原因通常不是像素数量而是信息密度,因此非常密集的表格建议一页一图,或改用异步的 `POST /upload`。

将您的图片表格转化为数据

每月获取100次免费扫描。无需信用卡即可开始使用。

相关文章