用 space ocr 这个 Claude 技能:把文档变成结构化、可查询的数据
一份面向开发者的指南:在 Claude Code 中安装 space ocr 技能,把单张文档识别成带定位的结构化字段,批量处理整个文件夹,并直接查询存好的结果。
这是什么
space ocr 是一个 Claude 技能,以 Claude Code 插件的形式分发。装好之后,它给助手补上了一种能力:把文档图片(发票、收据、名片、证件、各类表单)识别成结构化字段,而不是让你把图片贴进对话里、然后指望模型自己读对。识别在 space ocr 的服务端完成,通过它的 REST API(https://api.space-ocr.com)返回结果。
它和两种常见做法都不一样。一种是“贴图碰运气”——把截图丢给模型,让它从像素里猜文字,没有固定字段、没有可核对的来源、也无法稳定复现。另一种是“自己搭一套”——拉起一个 OCR 服务,再配一个数据库或向量库来存结果。space ocr 把这两步都省了:识别结果带着精确的页面定位返回,而 API 本身就是存储——文件夹、表格(sheet)、行(row)都在服务端,处理过的文档直接变成一个可查询的工作区,你不需要自己建库。
运行时零依赖:整个客户端就是一个只用标准库的 Python 脚本 scripts/space_ocr.py。没有 pip install、没有 MCP 服务器、没有 SDK,只要有 python3 就行。所有命令的输出都是 stdout 上的 JSON。
安装
在 Claude Code 里用两条斜杠命令完成安装:先把技能所在的 marketplace 加进来,再安装这个技能。这一步只做一次。装好后 Claude Code 在需要时就能调用它,你不必每次手动触发。
/plugin marketplace add oisidonut/claude-space-ocr-skill
/plugin install space-ocr@space-ocr配置
安装的是客户端,真正干活还需要一个 API key。到 https://space-ocr.com → Settings → API Keys 生成一个,新账户有 100 次免费扫描,无需绑卡。
把 key 交给脚本有两种方式:设环境变量 SPACE_OCR_API_KEY=spocr_...,或者在项目根目录放一个 .env 文件。脚本对 API 的鉴权头是 Authorization: Bearer spocr_...。接口基地址是 https://api.space-ocr.com,需要时可用 SPACE_OCR_API_BASE 覆盖。
配好之后用一行 balance 验证连通,顺便看看额度。返回里 free.remaining 是剩余免费扫描数,再加上 flatfee 和 balance 就是你当前能跑的总扫描数。
export SPACE_OCR_API_KEY=spocr_xxx
python3 scripts/space_ocr.py balance处理单张文档
如果只是一张图,用内置模板直接 ocr 最省事。--template invoice 会按发票的固定字段集去抽取,返回 vendor、total 这些字段,每个值都带着自己的页面定位。
内置模板涵盖常见证件和单据:invoice、receipt、business_card、purchase_order、delivery、quote、bankbook、passport、driver_license、resident_card、my_number_card、residence_card。用 templates 命令可以列出全部。
遇到模板覆盖不到的文档,有两条路。一是 --fields <schema.json>:自己写一份字段 schema(name / type / description,数组字段用 children 描述子列),把抽取规则完全定下来。二是 --auto:让引擎自己推断 4–8 个字段。--auto 带有拒识闸门——如果送进去的是横拍照片、空白页或非结构化的随手拍,它会直接返回错误,而不是凭空编字段,并且这次失败的扫描会自动退还。
python3 scripts/space_ocr.py ocr invoice.jpg --template invoice一个文件夹的文档 → 一张表格
超过一份文档,就别再逐张 ocr 然后把 JSON 贴回对话——应该建一张 sheet,把行写进去。先用 create sheet 定义一次列结构,再 upload 把图片批量丢进去。上传后 OCR 在服务端异步跑,每张图按 sheet 的列 schema 抽取,加上 --wait 可以阻塞到处理完成再返回。
这里有一个路径陷阱:文件夹用名字寻址(比如 /invoices),但 sheet 和 memo 要用 create 返回的 uniqueKey 路径寻址,而不是显示名。一个叫 “March” 的表格实际住在类似 /invoices/8G90wq... 的路径上,不是 /invoices/March。所以 create 返回的那个 path 一定要存下来,后面 upload / view / query / edit 全都复用它。要是路径忘了,可以 view 或 space 父文件夹,从对应条目里读回它的 path。
批量上传前,整批额度会先做一次预检:如果不够,会返回 insufficient_balance 并给出能处理的数量,且不扣费——不会跑到一半才断。
# 1) define the columns once, then create the sheet
python3 scripts/space_ocr.py create sheet /invoices "March" --columns columns.json
# -> returns a path like /invoices/8G90wq... (reuse this uniqueKey path)
# 2) drop every image in; OCR runs server-side (async), --wait blocks until done
python3 scripts/space_ocr.py upload /invoices/8G90wq... *.jpg --wait从存好的行里回答问题
文档处理完,对它们提问就不该再重新识别了。用 query <sheet> 把存好的行拉出来,在行上推理。query 支持服务端的 --where / --sort / --limit,把过滤、排序、截断都推到服务器做,而不是把每一行都拖进上下文。
--where 用 = != > >= < <= 和 ~(包含)等运算符,可重复、按 AND 组合,作用于 sheet 的列以及 name / ocrStatus;当两侧都能解析成数字时按数值比较(会去掉逗号和货币符号),否则按字符串比。--sort 写成 total:desc,--limit 上限 500。
关键一点:读操作不花扫描额度。query 和 view 都是读,成本为 0,只有一次 OCR 调用或一张上传的图片才计 1 次扫描。所以回答问题时尽管查,别为了答一个问题去重新 OCR。
python3 scripts/space_ocr.py query /invoices/8G90wq... \
--where 'total>=40000' --sort total:desc --limit 20让它保持精简、可信的四条规则
这四条规则是这个技能的核心,默认就该遵守:
- 存下来,别倾倒——处理超过一份文档后,把结果写进 sheet 的行里,不要把原始 OCR JSON 贴回对话。重数据放在 API 后面,不占上下文窗口。
- 扫描前先核额度——批处理前先跑
balance,确认够用;要复查某份文档时先view看它是不是已经在 sheet 里,能复用就别重扫。 - 从存好的行里回答——用
query <sheet>配服务端--where/--sort/--limit,读是免费的,别重新 OCR。 - 标注来源、暴露不确定——每个值都带
field_bboxes定位;优先逐字抽取:照页面原样复制,不要归一化日期/数字、也不要自己算合计,否则定位框会漂移。
每个值都带可核对的页面定位。 返回的每个字段都附一个 4 点边界框,它锚定到 Vision API 识别出的真实符号上(不是模型猜的位置),坐标在 0–1000 的归一化网格上,与分辨率无关。因此每个值都能被引用、被核对——web 控制台会把这些框直接画在文档上让你逐一对照。也正因如此,逐字抽取很重要:重新格式化或自行计算出来的值无法锚回页面,框就会漂。
关于幂等
/ocr/fields 和上传都接受 Idempotency-Key。用同一个 key 重试时,服务端会回放缓存的结果,不会二次扣费。批处理脚本里如果有重试逻辑,带上这个 key 就能避免网络抖动导致重复计费。失败的扫描本身也会自动退还,两者叠加起来,重试是安全的。