Guide · 使用教程
Xberg Python 教程:批量文档抽取、OCR 与结果校验
固定 Xberg 1.3.9,以完整 Python 程序逐个处理本地文件,记录失败与来源,说明六份成功、两份失败及十一项验证边界。
先建立能解释失败的抽取流程
这篇教程用 Xberg 处理一个本地目录,把每份文件的成功、失败、内容摘要和抽取结果保存成 JSON。目标是先确认输入与结果能对应、错误不会丢失,再考虑字段抽取和知识库索引。程序逐个调用 SDK,便于检查失败;它不是并行吞吐量基准。
实测日期为 2026 年 10 月 23 日,环境为 Python 3.11、xberg 1.3.9。官方文档当时已经展示更新的版本,以下代码以实际安装并运行的 wheel 为准。旧 Kreuzberg 文档与 API 不能直接套用。本文未调用付费 LLM API,也没有执行多工具质量排名。
第一步:建立独立环境
在有 Python 3.11 的终端执行:
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install 'xberg==1.3.9'
python -c 'from importlib.metadata import version; print(version("xberg"))'
mkdir input
将获准处理的样本放进 input。示例允许 TXT、HTML、CSV、DOCX、PDF 和 PNG,最多二十份、每份不超过 2 MiB。这是演示代码的限制,不能理解为 Xberg 支持格式或文件大小的全局上限。其他平台是否有匹配 wheel 和资源应独立核对。
这里仅为图片开启英文 Tesseract OCR,其他文件关闭 OCR。因此数字 PDF 可用来验证文本抽取,扫描 PDF 不在本示例的 OCR 验收范围。若需要中文扫描或混合 PDF,请先按当前版本文档配置相应语言和依赖,用独立样本验证,不能只把参数改掉就宣布可用。
第二步:保存完整程序
把下面代码保存为 extract_batch.py。当前 API 使用异步 extract、ExtractInput,以及列表类型的 language 参数。代码检查 SDK 错误、结果数量和空文本,并为每个输入保留处理状态。
"""Fixed Xberg 1.3.9 local-byte demo; no remote URLs or optional LLMs."""
import argparse
import asyncio
import hashlib
import json
from importlib.metadata import version
from pathlib import Path
import time
from xberg import ExtractInput,ExtractionConfig,OcrConfig,extract
MIMES={'.txt':'text/plain','.html':'text/html','.csv':'text/csv',
'.docx':'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.pdf':'application/pdf','.png':'image/png'}
async def process(path,root):
record={'source':path.name,'status':'failed','xberg_version':version('xberg')}
try:
if path.is_symlink() or not path.resolve().is_relative_to(root.resolve()):
raise ValueError('outside input directory or symlink')
mime=MIMES.get(path.suffix.lower())
if not mime:raise ValueError('unsupported extension in this demo')
if path.stat().st_size>2*1024*1024:raise ValueError('file exceeds demo 2 MiB limit')
data=path.read_bytes()
if not data:raise ValueError('empty file')
record['sha256']=hashlib.sha256(data).hexdigest()
record['source_bytes']=len(data)
is_image=mime.startswith('image/')
config=ExtractionConfig(use_cache=False,disable_ocr=not is_image,
ocr=OcrConfig(backend='tesseract',language=['eng']) if is_image else None,
extraction_timeout_secs=15,output_format='plain')
started=time.perf_counter()
output=await extract(ExtractInput(kind='bytes',bytes=data,mime_type=mime,filename=path.name),config)
record['extract_ms']=round((time.perf_counter()-started)*1000,2)
if output.errors:raise RuntimeError('Xberg returned extraction errors: '+str(output.errors)[:400])
if len(output.results)!=1:raise RuntimeError('expected exactly one extracted document')
doc=output.results[0]
if not doc.content.strip():raise RuntimeError('empty extracted content')
record.update(status='ok',content=doc.content,mime_type=doc.mime_type,
table_count=len(doc.tables),ocr_requested=is_image)
except Exception as exc:
record.update(error_type=type(exc).__name__,error=str(exc)[:600])
return record
async def main():
parser=argparse.ArgumentParser()
parser.add_argument('input_directory',type=Path)
args=parser.parse_args()
root=args.input_directory.resolve()
if not root.is_dir():raise SystemExit('input must be an existing directory')
files=sorted(p for p in root.iterdir() if p.is_file())
if not files or len(files)>20:raise SystemExit('demo expects 1 to 20 files')
# Sequential execution makes errors and resource use easier to inspect.
records=[await process(p,root) for p in files]
print(json.dumps({'records':records,'summary':{'attempted':len(records),
'ok':sum(r['status']=='ok' for r in records),
'failed':sum(r['status']!='ok' for r in records)}},ensure_ascii=False,indent=2))
if __name__=='__main__':asyncio.run(main())
第三步:运行并检查每个文件
python extract_batch.py input > extraction-output.json
结果包含 records 和 summary。records 保留源文件名、版本、字节数、SHA-256、抽取文本和表格数量;失败时保留错误类型与说明。summary 统计 attempted、ok 和 failed。脚本为继续处理下一份文件而捕获单文件异常,因此进程正常退出不代表所有文件通过。
在后续索引或导出前,可以增加一个独立检查:
import json
from pathlib import Path
report = json.loads(Path('extraction-output.json').read_text())
records = report['records']
assert records, 'no extraction records'
assert report['summary']['attempted'] == len(records)
bad = [row['source'] for row in records if row['status'] != 'ok']
if bad:
raise SystemExit('review failed files: ' + ', '.join(bad))
print('All files passed the basic extraction gate.')
这个检查只把失败文件挡在下一步之前,不能证明正文与字段都正确。对于允许部分成功的工作流,可以分别提交成功记录与失败队列,但必须显式定义规则,不能默默漏掉失败单据。
第四步:核对本次实际结果
本次准备了八份合成文件,成功六份、失败两份。PDF 返回发票号与金额;中文 DOCX、TXT 返回预期文本;HTML 和 CSV 返回预期标记且各有一个表格对象;英文 PNG 返回发票号与金额标记,空格与版式不完全保留。损坏 DOCX 返回错误,演示允许列表之外的文件被主动拒绝。
额外验证空文件、超过演示大小限制的文件以及指向文件的符号链接会被拒绝,共完成十一项检查。成功文件的来源摘要也与输入一致。这里的“表格对象存在”仅验证基础返回结构,没有逐单元格验收,更不能作为复杂表格准确率数字。
系统 tesseract 命令在该环境因动态库问题失败,但本次 SDK 路径实际成功完成英文图片 OCR。这是两个分别观察到的结果,不能仅以系统命令失败判定 SDK OCR 不可用,也不能推断所有机器都无需额外依赖。出现安装与 OCR 错误时,应分别检查命令行路径、SDK 返回和所选版本文档。
中文文本抽取不等于中文扫描 OCR。本文没有测试中文扫描件、手写、旋转照片或复杂跨页版式,也没有测试全部可选插件。新增文档类型前,先建立参考答案和失败对照,再记录实际版本、语言和配置。
第五步:给业务结果增加校验
文本非空只是最基础的门槛。做知识库时,需要检查段落顺序、引用能否回到原件和表格内容是否丢失。处理单据时,需要把文本或表格映射成明确字段,再验证日期、币种、金额、必填项和重复记录。不要把抽取到的金额直接作为付款指令。
可以先选择一组人工确认的样本,为每份文件保存期望关键文本、字段和表格答案;每次升级版本都重新运行。把错误分为文件损坏、环境依赖、抽取失败、空文本、字段不一致和待人工判断,便于判断重试是否有意义。对损坏文件无限重试只会增加等待与资源消耗。
示例配置 extraction_timeout_secs=15,是传给 SDK 的抽取超时设置,不能当作覆盖读取、所有原生操作和进程资源的完整沙箱。生产环境仍需独立任务隔离、资源限制、队列停止和重试规则。扩展到并发时,还要重新验证内存与故障恢复,不要直接以单文件时长估算整批吞吐量。
数据和版本记录要跟着输出走
输出 JSON 包含原文,真实文件应放在受控目录,限制日志、备份和共享权限。文件摘要有助于追踪输入变化,但不是访问控制。程序仅展示本地字节输入,没有启用可选 LLM;不应由此推断所有模型下载或原生组件配置都绝对离线,严格环境需要按真实部署检查网络行为。
升级时先查看变更,再在独立环境安装新版本,复跑同一组样本并比较输出。遇到文档版本比已发布包更新,以实际可用版本的运行结果为准,不要混合两套 API。只有基础抽取、业务校验和失败处理都能解释,才适合扩大文件量。
继续阅读:Xberg 工具介绍、本地文档抽取 SDK 推荐、Xberg vs LlamaParse、Xberg vs Parseur。
官方资料:Python API、PyPI 版本、Xberg 源码与许可、历史 Kreuzberg 文档迁移说明。本教程以固定版本实测为边界,实际安装条件以对应平台与版本说明为准。
常见问题
- 为什么固定 1.3.9?
- 这是本次实际安装与验证的 PyPI wheel;在线文档可能更新更快,不应混用 API。
- 脚本会递归处理子目录吗?
- 不会。它只处理指定目录直接包含的文件,并设置演示数量、大小及扩展名限制。
- 批处理正常退出就全部成功吗?
- 不一定。程序保留单文件错误后继续运行,后续必须检查 records 和 summary。
- 测试证明中文扫描识别可用吗?
- 没有。本次中文样本是 DOCX 与 TXT;实际 OCR 仅测试了一张英文合成 PNG。
- 十一项检查是什么?
- 八份合成文件的成功和失败检查,加空文件、超限文件及符号链接拒绝检查。
- 这个示例是生产沙箱吗?
- 不是。演示限制和 SDK 超时不替代进程隔离、资源管理、文件权限和业务复核。