# 上传文件 Source: https://docs-docflow.textin.com/api-reference/上传文件 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/file/upload # 修改文件类别 Source: https://docs-docflow.textin.com/api-reference/修改文件类别 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/file/amend_category - 对于普通任务,修改普通任务文件类别 - 对于文档拆分任务,修改拆分后的文件类型和页码 - 对于多图切分任务,修改切分后的文件类型 # 删除任务 Source: https://docs-docflow.textin.com/api-reference/删除任务 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/file/delete 按条件删除文件,满足任一条件即可删除。 # 同步上传文件 Source: https://docs-docflow.textin.com/api-reference/同步上传文件 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/file/upload/sync 同步上传文件并等待处理完成,返回处理结果 # 抽取特定字段 Source: https://docs-docflow.textin.com/api-reference/抽取特定字段 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/file/extract_fields 针对已经完成抽取的任务,为该任务抽取额外的字段,或重新抽取个别已有的字段。 返回所有字段的完整抽取结果。返回结构同`/api/app-api/sip/platform/v2/file/fetch`。 # 创建文件类别 Source: https://docs-docflow.textin.com/api-reference/文件类别/创建文件类别 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/create 在指定空间中创建一个新的文件类别,需要上传至少一个样本文件并配置至少一个字段。 支持一站式创建:可同时传入 `tables` 参数,创建表格及表格内嵌字段。 # 删除文件类别 Source: https://docs-docflow.textin.com/api-reference/文件类别/删除文件类别 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/delete 删除指定的文件类别 # 删除文件类别字段 Source: https://docs-docflow.textin.com/api-reference/文件类别/删除文件类别字段 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/fields/delete 删除指定的文件类别字段,支持批量删除普通字段和表格字段 # 删除文件类别表格 Source: https://docs-docflow.textin.com/api-reference/文件类别/删除文件类别表格 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/tables/delete 删除指定的文件类别表格,支持批量删除 # 新增文件类别字段 Source: https://docs-docflow.textin.com/api-reference/文件类别/新增文件类别字段 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/fields/batch_add 在指定文件类别下新增多个字段。全有全无语义:任一字段校验不通过则全部失败。 # 新增文件类别表格 Source: https://docs-docflow.textin.com/api-reference/文件类别/新增文件类别表格 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/tables/batch_add 在指定文件类别下新增多个表格(支持内嵌字段)。全有全无语义。 # 更新文件类别 Source: https://docs-docflow.textin.com/api-reference/文件类别/更新文件类别 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/update 更新指定文件类别的信息。 注意:设置 extract_model 时将级联覆盖该分类下所有字段和表格的抽取模型。 # 更新文件类别字段 Source: https://docs-docflow.textin.com/api-reference/文件类别/更新文件类别字段 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/fields/batch_update 更新指定文件类别下的多个字段。全有全无语义:任一字段校验不通过则全部失败。 # 更新文件类别表格 Source: https://docs-docflow.textin.com/api-reference/文件类别/更新文件类别表格 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/tables/batch_update 更新指定文件类别下的多个表格。全有全无语义。 # 获取文件类别下的表格列表 Source: https://docs-docflow.textin.com/api-reference/文件类别/获取文件类别下的表格列表 /docflow/cn/rest-api/openapi.bundle.yaml get /api/app-api/sip/platform/v2/category/tables/list 获取指定文件类别下配置的所有表格 # 获取文件类别字段列表 Source: https://docs-docflow.textin.com/api-reference/文件类别/获取文件类别字段列表 /docflow/cn/rest-api/openapi.bundle.yaml get /api/app-api/sip/platform/v2/category/fields/list 获取文件类别字段列表,包含普通字段和表格(含表格字段) # 获取空间文件类别列表 Source: https://docs-docflow.textin.com/api-reference/文件类别/获取空间文件类别列表 /docflow/cn/rest-api/openapi.bundle.yaml get /api/app-api/sip/platform/v2/category/list 获取空间文件类别列表 # 上传类别样本 Source: https://docs-docflow.textin.com/api-reference/文件类别样本/上传类别样本 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/sample/batch_upload 为指定文件类别上传多个样本文件,最多20个 # 下载类别样本(ZIP) Source: https://docs-docflow.textin.com/api-reference/文件类别样本/下载类别样本(zip) /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/sample/batch_download 下载指定类别的多个样本文件,打包为 ZIP 格式返回。不传 sample_ids 时下载全部样本。 # 删除类别样本 Source: https://docs-docflow.textin.com/api-reference/文件类别样本/删除类别样本 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/category/sample/delete 删除指定的类别样本 # 获取类别样本列表 Source: https://docs-docflow.textin.com/api-reference/文件类别样本/获取类别样本列表 /docflow/cn/rest-api/openapi.bundle.yaml get /api/app-api/sip/platform/v2/category/sample/list 获取指定文件类别的样本列表 # 更新文件处理结果 Source: https://docs-docflow.textin.com/api-reference/更新文件处理结果 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/file/update 更新文件处理结果。通常用于人工审核校对后,上传修改后的key-value,覆盖自动识别的结果。 # 生成短时间有效的token Source: https://docs-docflow.textin.com/api-reference/生成短时间有效的token /docflow/cn/rest-api/openapi.bundle.yaml get /api/app-api/sip/platform/v2/auth/token 生成短时间有效的token(用于iframe场景) # 创建空间 Source: https://docs-docflow.textin.com/api-reference/空间管理/创建空间 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/workspace/create 创建一个新的工作空间 # 删除空间 Source: https://docs-docflow.textin.com/api-reference/空间管理/删除空间 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/workspace/delete 删除指定的工作空间 # 更新空间 Source: https://docs-docflow.textin.com/api-reference/空间管理/更新空间 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/workspace/update 更新指定空间的信息 # 获取空间列表 Source: https://docs-docflow.textin.com/api-reference/空间管理/获取空间列表 /docflow/cn/rest-api/openapi.bundle.yaml get /api/app-api/sip/platform/v2/workspace/list 获取当前用户的所有工作空间列表 # 获取空间详情 Source: https://docs-docflow.textin.com/api-reference/空间管理/获取空间详情 /docflow/cn/rest-api/openapi.bundle.yaml get /api/app-api/sip/platform/v2/workspace/get 根据空间ID获取空间详细信息 # 获取文件处理结果列表 Source: https://docs-docflow.textin.com/api-reference/获取文件处理结果列表 /docflow/cn/rest-api/openapi.bundle.yaml get /api/app-api/sip/platform/v2/file/fetch # 重新处理文件 Source: https://docs-docflow.textin.com/api-reference/重新处理文件 /docflow/cn/rest-api/openapi.bundle.yaml post /api/app-api/sip/platform/v2/file/retry 重新处理文件。 # AP 审单场景(无工作空间) Source: https://docs-docflow.textin.com/docflow/cn/00-overview/ap_review 在财务部门 AP(应付账款)审单业务场景中,调用 DocFlow API 完成发票、采购订单、收货单等单据的自动化交叉审核。 本文以 **AP 审单**(应付账款审核)为业务场景,演示如何通过 API 完成从创建空间、配置类别、上传文件、获取抽取结果到智能审核的完整流程。 如果您刚刚接触 DocFlow,建议先在 [Web 页面](https://docflow.textin.com/) 体验一下产品的基本功能,再阅读本文。 ## 01 场景说明 在 AP(应付账款)审单业务中,财务人员每次付款前需要对一组采购单据进行交叉核验。典型的单据组合包括: * 发票(含数电票、纸质增值税专用发票/普通发票等):记录销售方/购买方信息、开票金额和税率 * 采购订单(PDF/Word):约定供应商信息、合同金额、税率和产品明细 * 收货单(PDF):记录实际收货数量、物料信息及关联的采购订单号 通过 DocFlow,您只需配置一次分类和字段,后续上传的单据即可自动完成**分类识别**和**结构化信息抽取**;配置好审核规则库后,还可对多份单据进行**跨文档智能审核**,自动发现供应商不一致、金额超标、数量异常等问题。 ## 02 业务流程 AP 审单业务流程 **工作空间、文件类别和审核规则库只需配置一次**,后续可持续复用——直接上传新的待处理文件并创建新的审核任务即可。本示例为演示完整流程,将配置步骤和处理步骤放在同一段代码中运行。 ## 03 先决条件 1. 登录 [TextIn 控制台](https://www.textin.com/console/dashboard/setting),获取 `x-ti-app-id` 和 `x-ti-secret-code` 2. 下载[示例样本文件](https://github.com/ichaozai/docflow-docs/tree/master/examples/sample_files/ap_review),或使用自己的单据文件 ## 04 类别与字段配置 本示例配置四个文件类别,均通过 `category/create` 接口创建并配置字段。 增值税电子发票(数电票),包含发票号码、开票日期、购买方/销售方名称及税号、价税合计等基本信息字段,以及商品明细表格字段: 示例样本文件,可点击查看或下载:[sample\_invoice.pdf](https://dllf.intsig.net/download/2026/CustomService/sample_invoice.pdf) ``` *** ## 附加说明 ### postMessage 通信协议 iframe 内的 DocFlow 页面会向父窗口发送以下类型的消息,父页面可通过 `window.addEventListener('message', ...)` 接收并处理。 | 消息类型 | 说明 | 触发时机 | | -------------- | --------- | ------------ | | `tokenExpired` | Token 已失效 | API 返回 401 时 | | `login` | 用户登录成功 | Token 认证成功时 | | `logout` | 用户退出登录 | 用户主动退出时 | | `noPermission` | 权限不足 | API 返回权限错误时 | | `error` | 发生错误 | 通用错误通知 | **消息格式**: ```ts theme={null} interface IframeMessage { type: 'tokenExpired' | 'login' | 'logout' | 'noPermission' | 'error'; data?: unknown; } ``` ### Token 隔离机制 当同一浏览器中存在多个不同父页面嵌入的 DocFlow iframe 时,系统会按「来源」隔离各自的用户状态,避免 Token 冲突。该机制依赖 **Referrer** 信息(即 `document.referrer` 或父页面的 origin)。接入方必须满足以下**二选一**:\ 1)Referrer 策略允许传递 origin;或\ 2)使用同源策略(父子同源)。 **推荐配置方式**: **方式一:iframe 属性(推荐)** ```html theme={null} ``` **方式二:HTML meta 标签** 在父页面 `` 中添加: ```html theme={null} ``` **方式三:HTTP 响应头** 在父页面所在域名的 HTTP 响应中设置: ``` Referrer-Policy: origin ``` # 中文类别参数传参问题 Source: https://docs-docflow.textin.com/docflow/cn/100-faq/chinese_category_param 解决中文或其他非英语类别名称在API传参时的问题 在使用 DocFlow API 时,如果文件类别名称是中文或其他非英语字符,直接传递原始类别名称可能会导致以下问题: 1. **类别匹配失败**:API 无法正确识别中文类别名称 2. **处理错误**:返回类别不存在的错误信息 3. **编码问题**:URL 参数包含非 ASCII 字符导致请求失败 ## 解决方案 ### 1. URL 编码处理 中文类别名称必须进行 UTF-8 URL 编码后才能作为 API 参数传递。 #### Python 示例 ```python theme={null} import urllib.parse # 原始中文类别名称 category = "发票" # URL 编码 encoded_category = urllib.parse.quote(category) print(f"编码后: {encoded_category}") # 输出: %E5%8F%91%E7%A5%A8 ``` #### JavaScript 示例 ```javascript theme={null} // 原始中文类别名称 const category = "发票"; // URL 编码 const encodedCategory = encodeURIComponent(category); console.log(`编码后: ${encodedCategory}`); // 输出: %E5%8F%91%E7%A5%A8 ``` # 如何获取企业ID Source: https://docs-docflow.textin.com/docflow/cn/100-faq/get_enterprise_id 1. 在空间列表,点击左上角DocFlow logo,在弹出的下拉菜单中,点击相应的企业组织上的**企业管理**按钮 enterprise setting 2. 在弹出的滑窗上,**查看**或**复制**企业ID enterprise setting # 如何获取工作空间ID Source: https://docs-docflow.textin.com/docflow/cn/100-faq/get_workspace_id 1. 在空间列表,选择工作空间-点击**更多操作-设置** workspace setting 2. 在弹出的滑窗上,**查看**或**复制**工作空间ID workspace setting # OpenAPI 文件下载 Source: https://docs-docflow.textin.com/docflow/cn/100-faq/openapi_download ## OpenAPI 规范文件 如果您需要使用 OpenAPI 规范文件进行 API 集成开发或工具导入,可以通过以下地址下载完整的 OpenAPI 定义文件: **下载地址:** ``` https://raw.githubusercontent.com/ichaozai/docflow-docs/refs/heads/master/docflow/cn/rest-api/openapi.bundle.yaml ``` ## 使用说明 下载的 OpenAPI 文件(YAML 格式)包含了 DocFlow API 的完整接口定义,您可以: 1. **导入到 API 开发工具**:如 Postman、Insomnia、Apifox 等工具,快速生成 API 请求集合 2. **生成客户端代码**:使用 OpenAPI Generator 等工具,自动生成各种编程语言的 SDK 代码 3. **查看完整 API 文档**:在支持 OpenAPI 规范的文档工具中查看详细的接口说明 ## 文件内容 该文件包含: * 所有 API 接口的详细定义 * 请求参数和响应格式说明 * 数据模型和结构定义 * 认证方式说明 * 接口示例代码 如有任何问题,欢迎联系我们的技术支持团队。 # 计费说明 Source: https://docs-docflow.textin.com/docflow/cn/100-faq/product_charge ## 01 产品规格与充值 您可以通过TextIn 官网,访问 “[DocFlow 文档自动化-产品规格](https://www.textin.com/product/textin_docflow)” 查看详细的产品定价信息,并进行 T 币充值操作,以满足您的使用需求。 ## 02 资费查询 在使用过程中,您可以在工作台的“[充值与消费明细](https://www.textin.com/console/dashboard/userCenter/finance)”板块,清晰地查看到 T 币的订单记录以及消费情况,方便您随时掌握账户的资金动态。 ## 03 计费规则 DocFlow 文档自动化平台采用简洁明了的“按页计费”模式,计费单位为“T 币/页”,您可以根据自身需求直接充值 T 币,灵活使用平台的各项功能。平台提供三种使用方式,分别是 SaaS 平台使用、API 使用和私有化部署使用,以下是针对这三种使用方式的具体计费说明: ### 3.1 SaaS平台 您可以通过点击“[Docflow 文档自动化平台](https://docflow.textin.com/)”访问 SaaS 平台产品,其计费规则如下: #### **1、文件处理计费** 文件上传后,系统将依据首次成功分类的页数计费。若部分文件或页面在首次上传时未成功分类(被归为 undefined 分类),则不会进行计费。但如果后续您通过重新识别等操作使这些文件分类成功,那么将按照首次分类成功的页数进行计费。不同文件类型计费标准如下: (1)国内通用票据:0.1 元/页,目前涵盖以下23种票据类型 Bill (2)标准卡证:0.1 元/页,目前涵盖以下5种卡证类型 Card (3)其他文件类型:0.3 元/页 #### 2、发票验真计费 发票验真服务的计费标准为 0.2 元/次,按照发起次数进行计费。若需使用此服务,请提前联系商务开通 #### 3、其他操作计费 (1)使用文件拆分、多图切分功能时,不会对同一内容进行重复计费,避免您因功能使用而产生不必要的费用支出。 (2)对于已经计费的页面,后续进行的诸如重新识别、修改分类、字段修正、智能审核等操作,均不会再产生额外的计费,确保您在使用过程中的费用清晰可控。 文档自动化处理成功后,您可以通过空间文件列表查询历史处理结果。 ### 3.2 API 接口 通过 API 接口调用平台功能时,其计费逻辑与 SaaS 平台保持一致,即按照上述 SaaS 平台的计费规则执行。若 API 请求失败,则不会产生任何计费,确保您在接口调用过程中的费用安全。 ### 3.3 私有化部署 如果您有私有化部署的需求,我们提供了三种灵活的付费方案: 1. 一次性买断 2. 按年付费 3. 按用量付费 您可以根据自身企业的实际需求和预算情况选择合适的方案。具体的付费细节和方案内容,可通过联系我们的商务团队进行详细咨询和接洽,我们将为您提供一对一的专业服务,确保您能够找到最适合您的私有化部署付费方式。 ## 04 新用户福利 我们为每位新注册的用户赠送 50 页 DocFlow 文档自动化平台免费使用额度,助力您快速体验产品功能。 此外,添加 TextIn 福利官,还可额外获得 1000 页(次)TextIn 产品使用额度及其他诸多福利,让您能够更全面地探索 DocFlow 在实际业务场景中的应用效果。 当免费额度使用完毕后,您需要通过[充值T币](https://www.textin.com/console/dashboard/userCenter/charge) 来继续使用平台服务。 # 如何配置文件类别 Source: https://docs-docflow.textin.com/docflow/cn/100-faq/setup_category ## 类别配置页入口 1. 在空间列表,选择工作空间-点击**更多操作-设置** workspace setting 2. 在弹出的滑窗上,点击类别**配置**,进入文件类别配置页 workspace setting ## 添加类别示例样本 1. 点击**添加类别** add category 2. 点击**添加自定义类别**, 上传文件 add custom file ## 配置分类名称 设置分类名称 set category name ## 配置样本字段 点击**自动添加字段**,将由AI根据文档自动添加字段 add fields automatically 也可以手动添加、调整和删除字段 add fields manually ## 保存配置 点击**保存** save # 生成示例代码 Source: https://docs-docflow.textin.com/docflow/cn/20-coding-guide/example-generate-code 让 Agent 根据文档自动生成可用的 Docflow API 调用代码 ## 01 场景说明 安装 Docflow 文档 Skill 后,Agent 可以根据 API 文档自动生成规范的调用代码,包含参数处理、错误处理等完整逻辑。 ## 02 示例 ### 2.1 生成文档抽取调用代码 ```text theme={null} 帮我生成一个调用 Docflow 文档抽取 API 的 Python 代码 ``` 生成示例代码 Agent 生成的代码会自动引用最新的 API 文档,确保参数名称和接口地址是准确的。 # 检索文档和内容 Source: https://docs-docflow.textin.com/docflow/cn/20-coding-guide/example-search-docs 使用 Coding Agent 快速检索 TextIn Docflow 相关文档 ## 01 场景说明 当你需要查找 Docflow 的 API 文档、参数说明或使用指引时,可以直接向 Agent 提问,它会通过 MCP 服务检索相关内容。 ## 02 示例 ### 2.1 查询 API 接口 ```text theme={null} 帮我查询 Docflow 文档抽取 API 的接口说明 ``` 查询 API 接口 ### 2.2 查询参数说明 ```text theme={null} Docflow 支持的文件有哪些限制 ``` 查询参数说明 ### 2.3 查询最佳实践 ```text theme={null} 如何接入 Docflow 的抽取功能 ``` 查询最佳实践 你可以用自然语言提问,Agent 会自动判断需要检索哪些文档内容来回答你。 # 安装 Coding Agent 工具 Source: https://docs-docflow.textin.com/docflow/cn/20-coding-guide/install-coding-agent 选择一款支持 MCP 的 AI Coding 工具 ## 01 推荐工具 请先安装一款支持 MCP 协议的 AI Coding 工具,以下是我们推荐的几款: Anthropic 官方 CLI 工具,原生支持 MCP 协议和 Skill 系统。 OpenAI 官方命令行编程助手,支持 MCP 协议和 Skill 系统。 AI 代码编辑器,支持 MCP 协议。 字节跳动推出的 AI IDE,支持 MCP 协议。 # 安装 Docflow 文档 MCP Source: https://docs-docflow.textin.com/docflow/cn/20-coding-guide/install-docflow-mcp 连接 TextIn Docflow 文档 MCP 服务,让 Agent 获得文档检索能力 ## 01 什么是 Docflow 文档 MCP TextIn Docflow 文档 MCP 是一个基于 [Model Context Protocol](https://modelcontextprotocol.io/) 的服务,为 AI Coding Agent 提供 TextIn Docflow 的文档检索能力。 接入后,你的 Agent 可以: * 查询 Docflow API 文档 * 获取接口参数说明 * 检索使用示例和最佳实践 ## 02 安装步骤 ### 添加 MCP 服务 在终端中执行以下命令: ```bash theme={null} claude mcp add textin-docflow-docs --transport sse http://docflow.textin.com/textin-docs/mcp ``` ### 确认安装成功 运行以下命令查看已添加的 MCP 服务列表: ```bash theme={null} claude mcp list ``` 输出中应包含 `textin-docflow-docs`。 ### 添加 MCP 服务 在终端中执行以下命令: ```bash theme={null} codex mcp add textin-docflow-docs --url http://docflow.textin.com/textin-docs/mcp ``` ### 确认安装成功 运行以下命令查看已添加的 MCP 服务列表: ```bash theme={null} codex mcp list ``` 输出中应包含 `textin-docflow-docs`。 ### 添加 MCP 服务 1. 打开 **Cursor** → **Preferences** → **Cursor Settings** 2. 在左侧导航选择 **Tools & MCPs** 3. 点击 **Add Custom MCP** 4. 填入以下 JSON 配置: ```json theme={null} { "mcpServers": { "textin-docflow-docs": { "url": "http://docflow.textin.com/textin-docs/mcp" } } } ``` 5. 保存后确认 `textin-docflow-docs` 状态为已连接 ### 添加 MCP 服务 通过命令面板(`Ctrl+Shift+P` / `Cmd+Shift+P`)搜索 **MCP: Add Server**,依次: 1. 选择 **HTTP (http or sse)** 2. 输入服务 URL:`http://docflow.textin.com/textin-docs/mcp` 3. 输入服务 ID:`textin-docflow-docs` ### 确认安装成功 通过命令面板(`Ctrl+Shift+P` / `Cmd+Shift+P`)运行 **MCP: List Servers**,确认 `textin-docflow-docs` 状态为已连接。 ### 添加 MCP 服务 1. 打开 Trae **设置** → **MCP** 2. 点击下拉 **添加**,选择 **手动配置** 3. 填入以下 JSON 配置: ```json theme={null} { "mcpServers": { "textin-docflow-docs": { "url": "http://docflow.textin.com/textin-docs/mcp" } } } ``` 4. 保存后确认 `textin-docflow-docs` 状态为已连接 请确保你的网络环境可以访问 `docflow.textin.com`,如有网络限制请联系管理员开通白名单。 # 安装 Docflow 文档 Skill Source: https://docs-docflow.textin.com/docflow/cn/20-coding-guide/install-docflow-skill 为 Coding Agent 安装 Docflow 文档 Skill,增强文档理解与代码生成能力 ## 01 什么是 Docflow 文档 Skill Skill 是 Coding Agent 的技能扩展,安装后 Agent 会自动在相关场景中调用该技能。Docflow 文档 Skill 让 Agent 能够理解 TextIn Docflow 的 API 体系,并生成符合规范的代码。 ## 02 安装方式 ### 方式一:让 Agent 自行安装 在对话框中直接输入: ``` 安装 Docflow 文档 Skill ``` Agent 会自动完成 Skill 的下载和配置。 此方式需要 Agent 已正确连接 Docflow 文档 MCP 服务。 ### 方式二:使用命令手动安装 ```bash macOS / Linux theme={null} mkdir -p "$HOME/.claude/skills/textin-docflow-document" && \ curl -sS -L "http://docflow.textin.com/textin-docs/skills/docflow/document" \ -o "$HOME/.claude/skills/textin-docflow-document/SKILL.md" ``` ```powershell Windows theme={null} New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.claude\skills\textin-docflow-document" | Out-Null Invoke-WebRequest -Uri "http://docflow.textin.com/textin-docs/skills/docflow/document" ` -OutFile "$env:USERPROFILE\.claude\skills\textin-docflow-document\SKILL.md" ``` ```bash macOS / Linux theme={null} mkdir -p "$HOME/.codex/skills/textin-docflow-document" && \ curl -sS -L "http://docflow.textin.com/textin-docs/skills/docflow/document" \ -o "$HOME/.codex/skills/textin-docflow-document/SKILL.md" ``` ```powershell Windows theme={null} New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.codex\skills\textin-docflow-document" | Out-Null Invoke-WebRequest -Uri "http://docflow.textin.com/textin-docs/skills/docflow/document" ` -OutFile "$env:USERPROFILE\.codex\skills\textin-docflow-document\SKILL.md" ``` ```bash macOS / Linux theme={null} mkdir -p "$HOME/.cursor/skills/textin-docflow-document" && \ curl -sS -L "http://docflow.textin.com/textin-docs/skills/docflow/document" \ -o "$HOME/.cursor/skills/textin-docflow-document/SKILL.md" ``` ```powershell Windows theme={null} New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.cursor\skills\textin-docflow-document" | Out-Null Invoke-WebRequest -Uri "http://docflow.textin.com/textin-docs/skills/docflow/document" ` -OutFile "$env:USERPROFILE\.cursor\skills\textin-docflow-document\SKILL.md" ``` ```bash macOS / Linux theme={null} mkdir -p "$HOME/.copilot/skills/textin-docflow-document" && \ curl -sS -L "http://docflow.textin.com/textin-docs/skills/docflow/document" \ -o "$HOME/.copilot/skills/textin-docflow-document/SKILL.md" ``` ```powershell Windows theme={null} New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.copilot\skills\textin-docflow-document" | Out-Null Invoke-WebRequest -Uri "http://docflow.textin.com/textin-docs/skills/docflow/document" ` -OutFile "$env:USERPROFILE\.copilot\skills\textin-docflow-document\SKILL.md" ``` ```bash macOS / Linux theme={null} mkdir -p "$HOME/.trae-cn/skills/textin-docflow-document" && \ curl -sS -L "http://docflow.textin.com/textin-docs/skills/docflow/document" \ -o "$HOME/.trae-cn/skills/textin-docflow-document/SKILL.md" ``` ```powershell Windows theme={null} New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.trae-cn\skills\textin-docflow-document" | Out-Null Invoke-WebRequest -Uri "http://docflow.textin.com/textin-docs/skills/docflow/document" ` -OutFile "$env:USERPROFILE\.trae-cn\skills\textin-docflow-document\SKILL.md" ``` ## 03 验证安装 安装完成后,重启工具以重新加载 Skill。然后在技能(Skill)页面找到或搜索 `textin-docflow-document`,确认 Skill 已成功加载。 ## 04 安装后效果 安装成功后,Agent 会具备以下增强能力: | 能力 | 说明 | | ------ | ------------------------ | | 文档感知 | 自动识别与 Docflow 相关的开发需求 | | API 理解 | 准确理解 Docflow API 的参数和返回值 | | 代码生成 | 生成符合最佳实践的调用代码 | | 错误处理 | 包含规范的异常处理逻辑 | # Coding Agent 接入概览 Source: https://docs-docflow.textin.com/docflow/cn/20-coding-guide/intro 帮助开发者快速将 TextIn Docflow 文档 Skill 接入 AI Coding Agent,实现智能文档检索与代码生成 ## 01 什么是 Docflow 文档 Skill TextIn Docflow 文档 Skill 是一个为 AI Coding Agent 提供的技能插件,让你的编程助手能够: * **检索文档和内容** — 快速查找 TextIn Docflow 相关的 API 文档和技术资料 * **生成示例代码** — 根据文档自动生成可用的代码片段 * **辅助开发** — 在开发过程中实时获取技术指引和最佳实践 ## 02 快速开始 选择并安装一款 AI Coding 工具 连接 TextIn Docflow 文档服务 为 Agent 安装 Docflow 技能 查看实际使用场景和示例 # 快速开始 Source: https://docs-docflow.textin.com/docflow/cn/20-coding-guide/quickstart 三步完成 TextIn Docflow 文档 Skill 接入 本文通过示例说明如何快速将 Docflow 文档 Skill 接入 AI Coding Agent。\ 如果您还未使用过 Docflow,建议先在 [Web 页面](https://docflow.textin.com/) 体验一下 Docflow 的工作方式。 ## 01 三步完成接入 ### 1.1 安装 Coding Agent 安装一款支持 MCP 和 Skill 的 AI Coding 工具,推荐使用 [Claude Code](https://claude.com/product/claude-code)。 ```bash theme={null} npm install -g @anthropic-ai/claude-code ``` ### 1.2 安装 TextIn Docflow 文档 MCP 将 TextIn Docflow 文档 MCP 服务添加到你的 Coding Agent 中。 ```bash theme={null} claude mcp add textin-docflow-docs --transport sse http://docflow.textin.com/textin-docs/mcp ``` ### 1.3 安装 TextIn Docflow 文档 Skill 使用命令安装 Docflow 文档 Skill: ```bash theme={null} mkdir -p "$HOME/.claude/skills/textin-docflow-document" && \ curl -sS -L "http://docflow.textin.com/textin-docs/skills/docflow/document" \ -o "$HOME/.claude/skills/textin-docflow-document/SKILL.md" ``` ## 02 验证安装 安装完成后,在 Claude Code 中输入以下内容验证是否成功: ``` 帮我查询 Docflow 的文档解析 API 有哪些参数 ``` 如果 Agent 能够正确返回相关文档信息,说明接入成功。 # 智能文档抽取 Source: https://docs-docflow.textin.com/xparse/entity-extraction 调用 TextIn 智能文档抽取 API,从任意格式文档中一键提取结构化信息 TextIn 智能文档抽取 API 整合了专业文档解析底座与大模型语义理解能力,支持 **Prompt 自由抽取** 和 **自定义字段精确抽取** 两种模式,覆盖 20 余种文件格式,0 样本开箱即用。 ## 01 接口说明 | 项目 | 说明 | | ------------ | ------------------------------------------------------------------------------------------------- | | 请求地址 | `https://api.textin.com/ai/service/v2/entity_extraction` | | 请求方式 | HTTP POST | | Content-Type | `application/json` | | 支持格式 | png, jpg, jpeg, pdf, bmp, tiff, webp, doc, docx, html, mhtml, xls, xlsx, csv, ppt, pptx, txt, ofd | **两种抽取模式对比:** | 模式 | 触发方式 | 最大处理页数 | 适用场景 | | --------- | -------------------------------- | ------ | ------------- | | Prompt 模式 | 请求体中传入 `prompt` 字段 | 20 页 | 字段不固定、需要灵活抽取 | | 字段模式 | 请求体中传入 `fields` / `table_fields` | 100 页 | 字段固定、需要精确可控抽取 | 当同时传入 `prompt` 和 `fields` 时,以 **Prompt 模式** 优先。 ## 02 先决条件 登录 [TextIn 控制台](https://www.textin.com/console/dashboard/setting),在「账号与开发者信息」中获取: * `x-ti-app-id` * `x-ti-secret-code` ## 03 完整示例代码下载 完整可运行代码(含 Python、Java 两个版本)已内置在文档仓库的 `examples/` 目录下: ``` examples/ ├── python/ │ ├── entity_extraction.py # Python 完整示例 │ └── requirements.txt └── java/ └── src/main/java/com/docflow/ └── EntityExtraction.java # Java 完整示例 ``` 查看 Python 完整示例代码 查看 Java 完整示例代码 ## 04 运行示例 **环境要求**:Python 3.8+ **1. 安装依赖** ```bash theme={null} cd examples/python pip install requests ``` **2. 填写配置** 打开 `entity_extraction.py`,填写文件顶部的配置项: ```python theme={null} APP_ID = "your-app-id" # x-ti-app-id SECRET_CODE = "your-secret-code" # x-ti-secret-code ``` **3. 运行** ```bash theme={null} python entity_extraction.py invoice.pdf ``` **环境要求**:JDK 11+,Maven 3.6+ **1. 填写配置** 打开 `src/main/java/com/docflow/EntityExtraction.java`,填写文件顶部的配置项: ```java theme={null} private static final String APP_ID = "your-app-id"; private static final String SECRET_CODE = "your-secret-code"; ``` **2. 编译并运行** ```bash theme={null} cd examples/java mvn clean package -q java -cp target/docflow-examples-1.0.0.jar com.docflow.EntityExtraction invoice.pdf ``` ## 05 代码说明 所有请求都需要在 HTTP 头中携带 `x-ti-app-id` 和 `x-ti-secret-code`;待抽取文件以 **base64** 字符串形式放入请求体。 ```python theme={null} import base64 import requests APP_ID = "your-app-id" SECRET_CODE = "your-secret-code" API_URL = "https://api.textin.com/ai/service/v2/entity_extraction" def _headers() -> dict: return { "x-ti-app-id": APP_ID, "x-ti-secret-code": SECRET_CODE, "Content-Type": "application/json", } def _encode_file(file_path: str) -> str: with open(file_path, "rb") as f: return base64.b64encode(f.read()).decode("utf-8") def extract(file_path, request_body, url_params=None): body = {**request_body, "file": _encode_file(file_path)} resp = requests.post(API_URL, params=url_params or {}, headers=_headers(), json=body, timeout=120) resp.raise_for_status() result = resp.json() if result.get("code") != 200: raise RuntimeError(f"API 错误 {result['code']}:{result['message']}") return result ``` ```java theme={null} private static final String APP_ID = "your-app-id"; private static final String SECRET_CODE = "your-secret-code"; private static final String API_URL = "https://api.textin.com/ai/service/v2/entity_extraction"; private static Headers authHeaders() { return new Headers.Builder() .add("x-ti-app-id", APP_ID) .add("x-ti-secret-code", SECRET_CODE) .build(); } private static String encodeFile(String filePath) throws IOException { byte[] bytes = Files.readAllBytes(new File(filePath).toPath()); return Base64.getEncoder().encodeToString(bytes); } private static JsonObject callApi(String filePath, JsonObject body, Map urlParams) throws IOException { body.addProperty("file", encodeFile(filePath)); HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(API_URL)).newBuilder(); if (urlParams != null) urlParams.forEach(urlBuilder::addQueryParameter); Request req = new Request.Builder().url(urlBuilder.build()).headers(authHeaders()) .post(RequestBody.create(GSON.toJson(body), JSON_TYPE)).build(); try (Response resp = HTTP.newCall(req).execute()) { JsonObject result = JsonParser.parseString(resp.body().string()).getAsJsonObject(); if (result.get("code").getAsInt() != 200) throw new RuntimeException("API 错误:" + result.get("message").getAsString()); return result; } } ``` 传入一段自然语言 prompt,系统将根据 prompt 的描述从文档中提取信息,返回键值对(`llm_json`)和带坐标的结果(`raw_json`)。 ```python theme={null} result = extract( "invoice.pdf", request_body={ "prompt": "请抽取发票号码、开票日期、购买方名称、销售方名称、合计金额", }, url_params={"parse_mode": "scan"}, ) # llm_json:大模型直接返回的键值对,方便直接使用 llm_json = result["result"]["llm_json"] print(llm_json) # 示例输出:{"发票号码": "044031900211", "开票日期": "2024-03-15", "合计金额": "1500.00"} # raw_json:带坐标的抽取结果,用于前端高亮回显 raw_json = result["result"]["raw_json"] # usage:token 消耗统计 usage = result["result"]["usage"] print(f"Token 消耗:{usage['total_tokens']}") ``` ```java theme={null} JsonObject body = new JsonObject(); body.addProperty("prompt", "请抽取发票号码、开票日期、购买方名称、销售方名称、合计金额"); Map params = Map.of("parse_mode", "scan"); JsonObject result = callApi("invoice.pdf", body, params); JsonObject res = result.getAsJsonObject("result"); // llm_json:大模型直接返回的键值对,方便直接使用 JsonElement llmJson = res.get("llm_json"); System.out.println(GSON.toJson(llmJson)); // 示例输出:{"发票号码": "044031900211", "开票日期": "2024-03-15", "合计金额": "1500.00"} // usage:token 消耗统计 JsonObject usage = res.getAsJsonObject("usage"); System.out.println("Token 消耗:" + usage.get("total_tokens").getAsInt()); ``` 提供 `fields`(单值字段)和 `table_fields`(表格字段)列表,系统按字段名精确抽取,结果在 `details` 中按字段名组织。 ```python theme={null} result = extract( "invoice.pdf", request_body={ # 单值字段 "fields": [ {"name": "发票号码"}, {"name": "开票日期"}, {"name": "购买方名称"}, {"name": "销售方名称"}, {"name": "合计金额"}, ], # 表格字段 "table_fields": [ { "title": "货物明细", "description": "发票中的货物或服务明细表格", "fields": [ {"name": "项目名称"}, {"name": "数量"}, {"name": "单价"}, {"name": "金额"}, ], } ], }, url_params={"parse_mode": "scan"}, ) details = result["result"]["details"] # 读取单值字段 print(details["发票号码"]["value"]) # → "044031900211" print(details["合计金额"]["value"]) # → "1500.00" # 读取表格行 for row in details["row"]: print(row) ``` ```java theme={null} JsonObject body = new JsonObject(); // 单值字段 JsonArray fields = new JsonArray(); for (String name : new String[]{"发票号码", "开票日期", "购买方名称", "销售方名称", "合计金额"}) { JsonObject f = new JsonObject(); f.addProperty("name", name); fields.add(f); } body.add("fields", fields); // 表格字段 JsonArray tableFields = new JsonArray(); JsonObject table = new JsonObject(); table.addProperty("title", "货物明细"); table.addProperty("description", "发票中的货物或服务明细表格"); JsonArray cols = new JsonArray(); for (String col : new String[]{"项目名称", "数量", "单价", "金额"}) { JsonObject c = new JsonObject(); c.addProperty("name", col); cols.add(c); } table.add("fields", cols); tableFields.add(table); body.add("table_fields", tableFields); JsonObject result = callApi("invoice.pdf", body, Map.of("parse_mode", "scan")); JsonObject details = result.getAsJsonObject("result").getAsJsonObject("details"); // 读取单值字段 System.out.println(details.getAsJsonObject("发票号码").get("value").getAsString()); // 读取表格行 JsonArray rows = details.getAsJsonArray("row"); for (JsonElement row : rows) System.out.println(row); ``` ## 06 入参说明 ### 请求头 | 参数名 | 说明 | | ---------------- | ---------------------- | | x-ti-app-id | 登录后在「工作台-账号设置-开发者信息」查看 | | x-ti-secret-code | 登录后在「工作台-账号设置-开发者信息」查看 | ### URL 参数 URL 参数以 `?参数名=参数值` 形式拼接在请求 URL 后,例如 `?parse_mode=scan&page_count=10`。 | 参数名 | 类型 | 必填 | 默认值 | 说明 | | ----------------- | ------- | -- | --------- | --------------------------------------------------------------------------- | | page\_start | integer | 否 | 1 | PDF 从第几页开始抽取(从 1 计数) | | page\_count | integer | 否 | 见说明 | 抽取的 PDF 页数。Prompt 模式最多 20 页,字段模式最多 100 页 | | parse\_mode | string | 否 | `scan` | PDF 解析模式:`auto`(综合文字识别与解析)、`scan`(仅按文字识别);图片文件无需设置 | | get\_image | string | 否 | `objects` | 仅 Prompt 模式生效,返回图像:`none`(不返回)、`page`(整页图像)、`objects`(页内子图像)、`both`(整页+子图像) | | crop\_image | integer | 否 | `0` | 是否进行切边矫正:`0` 不处理、`1` 处理 | | remove\_watermark | integer | 否 | `0` | 是否去水印:`0` 不处理、`1` 处理 | | formula\_level | integer | 否 | `0` | 公式识别等级:`0` 全识别、`1` 仅识别行间公式、`2` 不识别 | | file\_name | string | 否 | — | 待抽取文件的文件名(含后缀),用于辅助格式判断 | ### 请求体字段 | 字段名 | 类型 | 必填 | 说明 | | -------------------------------------- | ------ | -- | -------------------------------------------------------- | | file | string | 是 | 待处理文档的 base64 字符串 | | prompt | string | 否 | Prompt 模式:自然语言抽取指令;传入此字段时 `fields` 和 `table_fields` 将被忽略 | | fields | array | 否 | 字段模式:要抽取的单值字段列表,总字段数(含 table\_fields 的列)不得超过 100 | | fields\[].name | string | 是 | 字段名 | | fields\[].description | string | 否 | 字段描述,辅助模型理解 | | table\_fields | array | 否 | 字段模式:要抽取的表格列表 | | table\_fields\[].title | string | 是 | 表格名称,例如"货物明细" | | table\_fields\[].description | string | 否 | 表格描述,辅助模型理解 | | table\_fields\[].fields | array | 否 | 表格列定义 | | table\_fields\[].fields\[].name | string | 是 | 列名 | | table\_fields\[].fields\[].description | string | 否 | 列描述,辅助模型理解 | ## 07 出参说明 ### 通用字段 | 字段名 | 类型 | 说明 | | -------------- | ------- | ------------------- | | code | integer | 错误码,200 表示成功 | | message | string | 错误信息,成功时为 "success" | | version | string | API 版本号 | | duration | integer | 推理时间(毫秒) | | x\_request\_id | string | 请求唯一标识 | ### Prompt 模式返回字段(result 对象) | 字段名 | 类型 | 说明 | | ---------------------------------------------- | -------------- | -------------------------------------------------- | | llm\_json | object / array | 大模型抽取的键值对结果,字段名由 prompt 决定;当文档包含多条记录时返回数组 | | raw\_json | object / array | 与 llm\_json 结构一致,但每个字段值扩展为含坐标的对象(见下表) | | raw\_json\[key].value | string | 字段的抽取值 | | raw\_json\[key].pages | array | 字段所在页码列表,例如 `[1]` | | raw\_json\[key].bounding\_regions | array | 字段的边界框信息列表 | | raw\_json\[key].bounding\_regions\[].page\_id | integer | 所在页码 | | raw\_json\[key].bounding\_regions\[].value | string | 边界框内的文本内容 | | raw\_json\[key].bounding\_regions\[].position | array | 坐标,8 元素数组 `[x1,y1,x2,y2,x3,y3,x4,y4]`(左上→右上→右下→左下) | | raw\_json\[key].bounding\_regions\[].char\_pos | array | 每个字符的坐标数组 | | pages | array | 每页的处理信息(状态、尺寸、图片 ID 等) | | usage.prompt\_tokens | integer | 输入消耗 token 数 | | usage.completion\_tokens | integer | 输出消耗 token 数 | | usage.total\_tokens | integer | 总消耗 token 数 | | finish\_reason | string | 推理结束原因:`stop`(正常结束)、`length`(超出 token 限制) | ### 字段模式返回字段(result 对象) | 字段名 | 类型 | 说明 | | ------------------------- | ------- | -------------------------------------------------- | | details | object | 字段抽取结果,以字段名为 key | | details\[key].value | string | 字段识别结果 | | details\[key].position | array | 坐标,8 元素数组(左上→右上→右下→左下) | | details\[key].description | string | 字段中文描述 | | details\[key].lines | array | 抽取结果的文本行信息 | | details.row | array | 表格抽取结果,每个元素为一行数据 | | category | object | details 各字段的数据类型:`one_to_one`(单值)或 `item_list`(表格) | | detail\_structure | array | 文档结构化识别信息,包含文档类型、表格、印章等 | | page\_count | integer | 实际处理的文档页数 | | rotated\_image\_width | integer | 正方向时文档宽度(仅图片有效) | | rotated\_image\_height | integer | 正方向时文档高度(仅图片有效) | | image\_angle | integer | 文档需逆时针旋转的角度(0/90/180/270,仅图片有效) | | finish\_reason | string | 推理结束原因:`stop` 或 `length` | `detail_structure` 中还包含印章识别结果(`stamps`),可获取印章颜色、形状、类型和文字内容。 ### 返回示例 ```json Prompt 模式 theme={null} { "code": 200, "message": "success", "duration": 2120, "result": { "llm_json": { "发票号码": "044031900211", "开票日期": "2024-03-15", "购买方名称": "北京某科技有限公司", "销售方名称": "上海某贸易有限公司", "合计金额": "1500.00" }, "raw_json": { "发票号码": { "value": "044031900211", "pages": [1], "bounding_regions": [ { "page_id": 1, "value": "044031900211", "position": [201, 199, 308, 199, 308, 230, 201, 230], "char_pos": [] } ] } }, "pages": [ {"page_id": 1, "status": "Success", "width": 1192, "height": 1024, "angle": 0} ], "usage": {"prompt_tokens": 800, "completion_tokens": 60, "total_tokens": 860}, "finish_reason": "stop" } } ``` ```json 字段模式 theme={null} { "code": 200, "message": "success", "duration": 1850, "result": { "details": { "发票号码": {"value": "044031900211", "position": [], "description": ""}, "合计金额": {"value": "1500.00", "position": [], "description": ""}, "row": [ {"项目名称": {"value": "办公耗材"}, "数量": {"value": "10"}, "单价": {"value": "50.00"}, "金额": {"value": "500.00"}}, {"项目名称": {"value": "打印纸"}, "数量": {"value": "20"}, "单价": {"value": "50.00"}, "金额": {"value": "1000.00"}} ] }, "category": { "发票号码": "one_to_one", "合计金额": "one_to_one", "row": "item_list" }, "page_count": 1, "finish_reason": "stop" } } ``` ## 08 错误码说明 | 错误码 | 描述 | | :---- | :------------------------------------- | | 40101 | x-ti-app-id 或 x-ti-secret-code 为空 | | 40102 | x-ti-app-id 或 x-ti-secret-code 无效,验证失败 | | 40103 | 客户端 IP 不在白名单 | | 40003 | 余额不足,请充值后再使用 | | 40004 | 参数错误,请查看文档检查传参 | | 40007 | 机器人不存在或未发布 | | 40008 | 机器人未开通,请至市场开通后重试 | | 40301 | 图片类型不支持 | | 40302 | 文件超过 50M 大小限制 | | 40303 | 文件类型不支持(响应中会返回实际检测到的文件类型) | | 40304 | 图片尺寸不符,宽高须在 20~10000 像素之间 | | 40305 | 识别文件未上传 | | 40306 | QPS 超过限制 | | 40400 | 无效的请求链接,请检查 URL 是否正确 | | 30203 | 基础服务故障,请稍后重试 | | 500 | 服务器内部错误 | # 获取目录树 Source: https://docs-docflow.textin.com/xparse/parse-getcatalog 在处理长篇技术文档、学术论文或企业规范文档时,RAG系统面临的最大挑战之一是如何理解内容的逻辑层次和上下文关系。简单的文本分块往往会破坏文档的原有结构,导致检索到的信息缺乏必要的背景context。例如:当用户询问"数据安全相关的实施要求"时,如果系统无法区分这些要求是来自"总体概述"、"技术规范"还是"合规检查"章节,就可能提供不准确或不完整的信息。 实践中通常有一种技巧,即利用文档的标题层级分chunk,然后在检索和重排序的时候也利用标题层级过滤无关的chunk,从而提升Top5召回的相关度,以便让大模型在最终回答时效果更好。 在TextIn xParse文档解析API中,我们提供了获取文档标题层级的功能,最多可支持6级标题的输出,您可以基于API的返回结果来构建完整的文档目录树。 ## 如何获取目录树 当您想要获取文档目录树(即大纲结构)时,您可以参考以下教程和示例代码。 这里为您提供了一份Textin官方pdf示例文件,您可点击下载或使用该链接:[文档解析pdf示例.pdf](https://web-api.textin.com/open/image/download?filename=c9cf7431eb314c7ba3f43ee716c799a3) * 参考[快速启动](/xparse/parse-quickstart),在 options 中设置URL参数 catalog\_details=1,API会在返回结果中包含目录相关信息。 * 在main函数中添加以下示例代码,获取API输出的目录信息,并保存为 json 文件。 ```python theme={null} # 解析JSON响应 json_response = json.loads(response) if "result" in json_response and "catalog" in json_response["result"]: catalog = json_response["result"]["catalog"] # 保存为json文件 with open("catalog.json", "w", encoding="utf-8") as f: json.dump(catalog, f, ensure_ascii=False, indent=2) print("目录已保存为 catalog.json") else: print("未检测到目录字段,可能文档没有目录或参数设置有误。") return ``` * 请注意:为了更加灵活的支持下游业务场景,文档解析API的目录返回结果中通过**hierarchy**字段表示目录的标题层级,但并没有目录之间直接的父子层级关系,您可以参考以下示例代码获取标题目录间的父子层级关系,以构建目录树状结构。 ```python theme={null} toc_list = catalog['toc'] result = [] parent_stack = [] # 用于跟踪当前路径上的父节点 for item in toc_list: # 复制当前项目,避免修改原始数据 current_item = item.copy() current_item['children'] = [] current_level = item.get('hierarchy', 1) # 根据层级调整父节点栈 # 移除层级大于等于当前层级的节点 while parent_stack and parent_stack[-1]['hierarchy'] >= current_level: parent_stack.pop() # 如果有父节点,将当前项目添加到父节点的children中 if parent_stack: parent_stack[-1]['children'].append(current_item) else: # 如果没有父节点,说明是根节点 result.append(current_item) # 将当前项目添加到父节点栈中 parent_stack.append(current_item) print(result) # 保存处理后的目录结构为json文件 with open("processed_catalog.json", "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) print("处理后的目录结构已保存为 processed_catalog.json") return result ``` * 处理后的目录结构如下,标题目录间有了父子层级关系,您可以据此构建标题目录的树状结构。 ```json expandable theme={null} [ { "title": "Textin", "hierarchy": 1, "page_id": 1, "paragraph_id": 0, "pos": [ 276, 228, 467, 228, 467, 272, 276, 272 ], "pos_list": [ [ 276, 228, 467, 228, 467, 272, 276, 272 ] ], "sub_type": "text_title", "children": [] }, { "pos": [ 371, 382, 906, 382, 906, 450, 371, 450 ], "pos_list": [ [ 371, 382, 906, 382, 906, 450, 371, 450 ] ], "sub_type": "text_title", "title": "标准参考样例", "hierarchy": 1, "page_id": 1, "paragraph_id": 2, "children": [] }, { "paragraph_id": 3, "pos": [ 214, 564, 974, 564, 974, 657, 214, 657 ], "pos_list": [ [ 214, 564, 974, 564, 974, 657, 214, 657 ] ], "sub_type": "text_title", "title": "本科毕业论文模板", "hierarchy": 1, "page_id": 1, "children": [] }, { "sub_type": "text_title", "title": "目录", "hierarchy": 1, "page_id": 2, "paragraph_id": 0, "pos": [ 571, 198, 685, 198, 685, 228, 571, 228 ], "pos_list": [ [ 571, 198, 685, 198, 685, 228, 571, 228 ] ], "children": [] }, { "title": "第一章 背景介绍", "hierarchy": 1, "page_id": 3, "paragraph_id": 0, "pos": [ 456, 212, 733, 212, 733, 241, 456, 241 ], "pos_list": [ [ 456, 212, 733, 212, 733, 241, 456, 241 ] ], "sub_type": "text_title", "children": [ { "page_id": 3, "paragraph_id": 2, "pos": [ 342, 413, 657, 413, 657, 440, 342, 440 ], "pos_list": [ [ 342, 413, 657, 413, 657, 440, 342, 440 ] ], "sub_type": "text_title", "title": "第1节 模板使用说明", "hierarchy": 2, "children": [ { "title": "1.1.如何使用样式?", "hierarchy": 3, "page_id": 3, "paragraph_id": 5, "pos": [ 179, 798, 411, 798, 411, 820, 179, 820 ], "pos_list": [ [ 179, 798, 411, 798, 411, 820, 179, 820 ] ], "sub_type": "text_title", "children": [] } ] }, { "title": "第2节 如何刷新目录", "hierarchy": 2, "page_id": 3, "paragraph_id": 7, "pos": [ 342, 1019, 657, 1019, 657, 1046, 342, 1046 ], "pos_list": [ [ 342, 1019, 657, 1019, 657, 1046, 342, 1046 ] ], "sub_type": "text_title", "children": [ { "hierarchy": 3, "page_id": 3, "paragraph_id": 9, "pos": [ 177, 1286, 747, 1286, 747, 1308, 177, 1308 ], "pos_list": [ [ 177, 1286, 747, 1286, 747, 1308, 177, 1308 ] ], "sub_type": "text_title", "title": "2.1.为什么我写了新的章节后没有新的目录项出现?", "children": [] }, { "title": "2.2. 如何排版文章章节", "hierarchy": 3, "page_id": 4, "paragraph_id": 1, "pos": [ 176, 221, 445, 221, 445, 243, 176, 243 ], "pos_list": [ [ 176, 221, 445, 221, 445, 243, 176, 243 ] ], "sub_type": "text_title", "children": [] }, { "paragraph_id": 4, "pos": [ 176, 522, 423, 522, 423, 543, 176, 543 ], "pos_list": [ [ 176, 522, 423, 522, 423, 543, 176, 543 ] ], "sub_type": "text_title", "title": "2.3. 其他的一些样式", "hierarchy": 3, "page_id": 4, "children": [] }, { "sub_type": "text_title", "title": "2.4.如何使用其他的高级功能?", "hierarchy": 3, "page_id": 4, "paragraph_id": 6, "pos": [ 176, 684, 531, 684, 531, 704, 176, 704 ], "pos_list": [ [ 176, 684, 531, 684, 531, 704, 176, 704 ] ], "children": [] } ] } ] }, { "title": "第二章 正文要求说明", "hierarchy": 1, "page_id": 5, "paragraph_id": 0, "pos": [ 422, 210, 766, 210, 766, 241, 422, 241 ], "pos_list": [ [ 422, 210, 766, 210, 766, 241, 422, 241 ] ], "sub_type": "text_title", "children": [ { "hierarchy": 2, "page_id": 5, "paragraph_id": 2, "pos": [ 384, 412, 642, 412, 642, 439, 384, 439 ], "pos_list": [ [ 384, 412, 642, 412, 642, 439, 384, 439 ] ], "sub_type": "text_title", "title": "第1节 字体和大小", "children": [ { "hierarchy": 3, "page_id": 5, "paragraph_id": 3, "pos": [ 176, 496, 349, 496, 349, 518, 176, 518 ], "pos_list": [ [ 176, 496, 349, 496, 349, 518, 176, 518 ] ], "sub_type": "text_title", "title": "1.1.文章标题", "children": [] }, { "pos": [ 176, 636, 324, 636, 324, 657, 176, 657 ], "pos_list": [ [ 176, 636, 324, 636, 324, 657, 176, 657 ] ], "sub_type": "text_title", "title": "1.2. 章标题", "hierarchy": 3, "page_id": 5, "paragraph_id": 5, "children": [] }, { "hierarchy": 3, "page_id": 5, "paragraph_id": 7, "pos": [ 176, 775, 324, 775, 324, 795, 176, 795 ], "pos_list": [ [ 176, 775, 324, 775, 324, 795, 176, 795 ] ], "sub_type": "text_title", "title": "1.3. 节标题", "children": [] }, { "title": "1.4.子节标题", "hierarchy": 3, "page_id": 5, "paragraph_id": 9, "pos": [ 176, 913, 349, 913, 349, 934, 176, 934 ], "pos_list": [ [ 176, 913, 349, 913, 349, 934, 176, 934 ] ], "sub_type": "text_title", "children": [] }, { "title": "1.5.正文", "hierarchy": 3, "page_id": 5, "paragraph_id": 11, "pos": [ 176, 1054, 300, 1054, 300, 1076, 176, 1076 ], "pos_list": [ [ 176, 1054, 300, 1054, 300, 1076, 176, 1076 ] ], "sub_type": "text_title", "children": [] } ] } ] }, { "title": "第三章 公式排版", "hierarchy": 1, "page_id": 6, "paragraph_id": 0, "pos": [ 457, 211, 733, 211, 733, 241, 457, 241 ], "pos_list": [ [ 457, 211, 733, 211, 733, 241, 457, 241 ] ], "sub_type": "text_title", "children": [ { "hierarchy": 2, "page_id": 6, "paragraph_id": 2, "pos": [ 297, 412, 731, 412, 731, 441, 297, 441 ], "pos_list": [ [ 297, 412, 731, 412, 731, 441, 297, 441 ] ], "sub_type": "text_title", "title": "第1节 Microsoft Equation Editor", "children": [] }, { "page_id": 6, "paragraph_id": 4, "pos": [ 366, 641, 637, 641, 637, 669, 366, 669 ], "pos_list": [ [ 366, 641, 637, 641, 637, 669, 366, 669 ] ], "sub_type": "text_title", "title": "第2节 MathType", "hierarchy": 2, "children": [] } ] }, { "pos_list": [ [ 359, 163, 644, 163, 644, 189, 359, 189 ] ], "sub_type": "text_title", "title": "第3节 TeX/LaTeX", "hierarchy": 1, "page_id": 7, "paragraph_id": 0, "pos": [ 359, 163, 644, 163, 644, 189, 359, 189 ], "children": [] }, { "pos": [ 442, 210, 750, 210, 750, 241, 442, 241 ], "pos_list": [ [ 442, 210, 750, 210, 750, 241, 442, 241 ] ], "sub_type": "text_title", "title": "第四章 图形和表格", "hierarchy": 1, "page_id": 8, "paragraph_id": 0, "children": [ { "pos": [ 428, 350, 602, 350, 602, 376, 428, 376 ], "pos_list": [ [ 428, 350, 602, 350, 602, 376, 428, 376 ] ], "sub_type": "text_title", "title": "第1节 图形", "hierarchy": 2, "page_id": 8, "paragraph_id": 1, "children": [ { "sub_type": "image_title", "title": "图表1.1这是一幅牛的图片", "hierarchy": 3, "page_id": 8, "paragraph_id": 4, "pos": [ 471, 847, 719, 847, 719, 865, 471, 865 ], "pos_list": [ [ 471, 847, 719, 847, 719, 865, 471, 865 ] ], "children": [] } ] }, { "pos": [ 401, 1087, 602, 1087, 602, 1114, 401, 1114 ], "pos_list": [ [ 401, 1087, 602, 1087, 602, 1114, 401, 1114 ] ], "sub_type": "text_title", "title": "第2节 表格", "hierarchy": 2, "page_id": 8, "paragraph_id": 6, "children": [ { "sub_type": "table_title", "title": "插入表格与图片类似。当然,可以使用Excel预先作一个表格,然后导入进来,但是word本身也可以胜任一部分简单表格的绘制,如:", "hierarchy": 3, "page_id": 8, "paragraph_id": 8, "pos": [ 179, 1167, 1011, 1167, 1011, 1231, 179, 1231 ], "pos_list": [ [ 179, 1167, 1011, 1167, 1011, 1231, 179, 1231 ] ], "children": [] }, { "hierarchy": 3, "page_id": 8, "paragraph_id": 10, "pos": [ 479, 1372, 713, 1372, 713, 1391, 479, 1391 ], "pos_list": [ [ 479, 1372, 713, 1372, 713, 1391, 479, 1391 ] ], "sub_type": "table_title", "title": "表格2.1一个简单的表格", "children": [] } ] } ] }, { "sub_type": "text_title", "title": "第五章 定理环境", "hierarchy": 1, "page_id": 9, "paragraph_id": 0, "pos": [ 456, 208, 734, 208, 734, 242, 456, 242 ], "pos_list": [ [ 456, 208, 734, 208, 734, 242, 456, 242 ] ], "children": [ { "pos_list": [ [ 357, 452, 673, 452, 673, 478, 357, 478 ] ], "sub_type": "text_title", "title": "第1节 自定义定理环境", "hierarchy": 2, "page_id": 9, "paragraph_id": 2, "pos": [ 357, 452, 673, 452, 673, 478, 357, 478 ], "children": [ { "page_id": 9, "paragraph_id": 4, "pos": [ 176, 581, 411, 581, 411, 603, 176, 603 ], "pos_list": [ [ 176, 581, 411, 581, 411, 603, 176, 603 ] ], "sub_type": "text_title", "title": "定理1.1.对顶角相等。", "hierarchy": 3, "children": [] }, { "hierarchy": 3, "page_id": 9, "paragraph_id": 6, "pos": [ 176, 683, 581, 683, 581, 706, 176, 706 ], "pos_list": [ [ 176, 683, 581, 683, 581, 706, 176, 706 ] ], "sub_type": "text_title", "title": "定理1.2.三边对应相等的三角形全等。", "children": [] } ] }, { "page_id": 9, "paragraph_id": 9, "pos": [ 371, 925, 631, 925, 631, 951, 371, 951 ], "pos_list": [ [ 371, 925, 631, 925, 631, 951, 371, 951 ] ], "sub_type": "text_title", "title": "第2节 已有环境", "hierarchy": 2, "children": [] }, { "pos_list": [ [ 357, 1255, 645, 1255, 645, 1282, 357, 1282 ] ], "sub_type": "text_title", "title": "第3节 自定义环境", "hierarchy": 2, "page_id": 9, "paragraph_id": 14, "pos": [ 357, 1255, 645, 1255, 645, 1282, 357, 1282 ], "children": [] } ] } ] ``` # 获取图片并持久化 Source: https://docs-docflow.textin.com/xparse/parse-getimage TextIn xParse为了保护您的数据隐私安全,从文档解析API返回的图片链接有效期为30天,30天后平台会自动删除图片资源。如果您想要获取图片保存到本地并使markdown中的图片链接持久化,以便您在下游诸如知识库问答等AI应用中长期稳定地为用户展示图片,有以下两种方法供您选择: 1. 设置URL参数[image-output-type](https://docs.textin.com/api-reference/endpoint/parse#parameter-image-output-type)为base64str,此时图片直接以base64格式在API结果中返回。(这种方式返回结果体积会很大,长文档不推荐) 2. 设置URL参数[image-output-type](https://docs.textin.com/api-reference/endpoint/parse#parameter-image-output-type)为default(不传时默认为该值),此时图片直接以TextIn平台的链接方式返回,您可以通过链接下载图片到本地,或上传到您的云存储。 ## 如何将markdown中的图片链接替换为本地图片链接 **您可以参考如下教程:使用上述方法2让API返回图片链接,并完成markdown中的图片链接替换。** 这里为您提供了一份Textin官方pdf示例文件,您可点击下载或使用该链接:[文档解析pdf示例.pdf](https://web-api.textin.com/open/image/download?filename=c9cf7431eb314c7ba3f43ee716c799a3) * 参考[快速启动](/xparse/parse-quickstart),在 options 中设置参数 get\_image 为 objects 或 both,让API返回页面内的图片对象;设置参数 image\_output\_type 为 default,API会返回图片URL。如下图: Parse Image Ori Pn * 参考如下示例代码:提取返回结果markdown中的图片URL将图片下载保存至本地,并将markdown中的图片链接替换为本地图片链接。 ```python theme={null} import os import re import requests import hashlib from urllib.parse import urlparse from pathlib import Path import time from typing import List, Tuple, Optional class ImageDownloader: def __init__(self, md_file: str, images_dir: str = "images"): """ 初始化图片下载器 Args: md_file: markdown文件路径 images_dir: 图片存储目录 """ self.md_file = md_file self.images_dir = images_dir self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' }) # 创建图片目录 Path(self.images_dir).mkdir(exist_ok=True) def extract_image_urls(self, content: str) -> List[Tuple[str, str]]: """ 提取markdown内容中的图片链接 Args: content: markdown文件内容 Returns: (完整的markdown语法, 图片URL) 的元组列表 """ # 匹配 ![...](https://...) 格式的markdown图片语法 pattern = r'!\[([^\]]*)\]\((https://[^\s\)]+\.(?:jpg|jpeg|png|gif|bmp|webp|svg))\)' matches = re.findall(pattern, content, re.IGNORECASE) # 返回完整的markdown语法和URL result = [] for alt_text, url in matches: full_markdown = f"![{alt_text}]({url})" result.append((full_markdown, url)) return result def generate_filename(self, url: str) -> str: """ 根据URL生成本地文件名 Args: url: 图片URL Returns: 本地文件名 """ # 解析URL获取文件名 parsed_url = urlparse(url) original_filename = os.path.basename(parsed_url.path) # 如果没有扩展名,从URL中提取 if not original_filename or '.' not in original_filename: # 使用URL的MD5哈希作为文件名 url_hash = hashlib.md5(url.encode()).hexdigest()[:12] original_filename = f"{url_hash}.jpg" # 默认为jpg return original_filename def download_image(self, url: str, max_retries: int = 3) -> Optional[str]: """ 下载单个图片 Args: url: 图片URL max_retries: 最大重试次数 Returns: 成功时返回本地文件路径,失败时返回None """ filename = self.generate_filename(url) local_path = os.path.join(self.images_dir, filename) # 如果文件已存在,跳过下载 if os.path.exists(local_path): print(f"📁 文件已存在: {local_path}") return local_path for attempt in range(max_retries): try: print(f"⬇️ 正在下载 ({attempt + 1}/{max_retries}): {url}") response = self.session.get(url, timeout=30) response.raise_for_status() # 检查是否是图片文件 content_type = response.headers.get('content-type', '') if not content_type.startswith('image/'): print(f"⚠️ 警告: {url} 不是图片文件 (Content-Type: {content_type})") # 保存文件 with open(local_path, 'wb') as f: f.write(response.content) file_size = len(response.content) print(f"✅ 下载成功: {filename} ({file_size} bytes)") return local_path except requests.exceptions.RequestException as e: print(f"❌ 下载失败 (尝试 {attempt + 1}/{max_retries}): {e}") if attempt < max_retries - 1: time.sleep(2) # 重试前等待2秒 else: print(f"💀 下载彻底失败: {url}") return None return None def process_markdown(self) -> bool: """ 处理markdown文件,下载图片并替换链接 Returns: 处理是否成功 """ try: # 读取markdown文件 with open(self.md_file, 'r', encoding='utf-8') as f: content = f.read() # 备份原文件 backup_file = f"{self.md_file}.backup" with open(backup_file, 'w', encoding='utf-8') as f: f.write(content) print(f"🗂️ 已创建备份文件: {backup_file}") # 提取图片链接 image_data = self.extract_image_urls(content) if not image_data: print("🔍 未找到图片链接") return True print(f"🔍 找到 {len(image_data)} 个图片链接") # 下载图片并替换链接 replacements = [] for i, (markdown_syntax, url) in enumerate(image_data, 1): print(f"\n📋 处理第 {i}/{len(image_data)} 个链接:") print(f"🔍 原始语法: {markdown_syntax}") local_path = self.download_image(url) if local_path: # 使用相对路径 relative_path = os.path.relpath(local_path, os.path.dirname(self.md_file)) # 保留原始的alt text,只替换URL alt_text = re.search(r'!\[([^\]]*)\]', markdown_syntax).group(1) new_markdown = f"![{alt_text}]({relative_path})" replacements.append((markdown_syntax, new_markdown)) print(f"🔗 将替换为: {new_markdown}") else: print(f"⚠️ 保留原始链接: {markdown_syntax}") # 应用替换 modified_content = content for old_link, new_link in replacements: modified_content = modified_content.replace(old_link, new_link) # 保存修改后的文件 with open(self.md_file, 'w', encoding='utf-8') as f: f.write(modified_content) print(f"\n✅ 处理完成!") print(f"📊 成功替换 {len(replacements)} 个链接") print(f"📁 图片保存在: {self.images_dir}/") print(f"📄 原文件备份: {backup_file}") return True except Exception as e: print(f"❌ 处理失败: {e}") return False def cleanup(self): """清理资源""" self.session.close() def main(): """主函数""" print("🚀 图片下载器启动") print("=" * 50) # 配置 md_file = "test.md" # 这里替换为你的markdown文件路径 images_dir = "images" # 这里替换为你的图片存储目录 # 检查文件是否存在 if not os.path.exists(md_file): print(f"❌ 文件不存在: {md_file}") return # 创建下载器并处理 downloader = ImageDownloader(md_file, images_dir) try: success = downloader.process_markdown() if success: print("\n🎉 所有操作完成!") else: print("\n💥 操作失败!") finally: downloader.cleanup() if __name__ == "__main__": main() ``` * 如下图:可以看到图片已经保存到本地指定目录下,打开markdown文件可以看到图片链接已经替换为本地图片链接。 Parse Image Re Pn # 返回JSON结构说明 Source: https://docs-docflow.textin.com/xparse/parse-getjson 当您使用文档解析API解析文档时,解析后的数据将按照以下结构的JSON格式返回。 **重要说明:根据 `parse_mode` 参数的不同,返回结构会有所不同**: * 当 `parse_mode` 为 `auto`、`scan`、`parse` 时,返回 `markdown`、`detail`、`pages` 等字段 * 当 `parse_mode` 为 `lite` 或 `vlm` 时,返回新的 `elements` 结构(包含 `success_count`、`elements` 数组等字段) 如需将 `elements` 格式转换为统一的`detail`/`pages`格式,请参考[转换脚本](/xparse/parse-quickstart#elements格式转换脚本)。 ```json expandable theme={null} { "code": 200, "message": "success", "result": { "markdown": "# hello markdown", "detail": [ { "page_id": 1, "paragraph_id": 123, "outline_level": -1, "text": "hello markdown", "position": [217,390,1336,390,1336,460,217,460], "origin_position": [217,390,1336,390,1336,460,217,460], "content": 0, "type": "paragraph", "sub_type": "catalog", "image_url": "", "tags": [ "formula", "handwritten" ], "caption_id": { "page_id": 123, "paragraph_id": 123 }, "cells": [ { "row": 123, "col": 123, "row_span": 123, "col_span": 123, "position": [10,10,100,10,100,50,10,50], "origin_position": [ 123 ], "text": "", "type": "" } ], "split_section_page_ids": [1,2,3], "split_section_positions": [ [0,0,100,100,100,200,0,200], [0,0,100,100,100,200,0,200], [0,0,100,100,100,200,0,200] ], "stamp": { "value": "", "stamp_shape": "", "type": "", "color": "" } } ], "pages": [ { "status": "success", "page_id": 0, "durations": 612.5, "image_id": "90u12adcad08r2", "origin_image_id": "90u12adcad08r2", "base64": "", "origin_base64": "", "width": 123, "height": 123, "angle": 123, "content": [ { "id": 123, "type": "line", "text": "", "angle": 0, "pos": [ 123 ], "origin_position": [ 123 ], "sub_type": "handwriting", "direction": 123, "score": 0.5, "char_pos": [ [ 123 ] ] } ], "raw_ocr": [ { "text": "这是一个例子。", "score": 0.99, "type": "text", "position": [10,10,100,10,100,50,10,50], "angle": 123, "direction": 1, "handwritten": 1, "char_scores": [0.99,0.98,0.95,0.95,0.99,0.93,0.87], "char_centers": [ [20,10], [30,10], [40,10], [50,10], [60,10], [70,10], [80,10] ], "char_positions": [ [ [18,8,22,8,22,12,18,12] ], [ [28,88,32,8,32,12,28,12] ], [ [38,88,42,8,42,12,38,12] ], [ [48,88,52,8,52,12,48,12] ], [ [58,88,62,8,62,12,58,12] ], [ [68,88,72,8,72,12,68,12] ], [ [78,88,82,8,82,12,78,12] ] ], "char_candidates": [ ["这"], ["是"], ["一","-"], ["个"], ["例"], ["子"], ["。","O"] ], "char_candidates_score": [ [0.99], [0.99], [0.95,0.05], [0.99], [0.99], [0.99], [0.89,0.11] ] } ], "structured": [ { "type": "textblock", "pos": [ 123 ], "origin_position": [ 123 ], "content": [0,1,2], "sub_type": "text", "continue": true, "next_page_id": 2, "next_para_id": 1, "text": "", "outline_level": 123 } ] } ], "catalog": { "toc": [ [ { "hierarchy": 2, "title": "1.公司简介和主要财务指标", "page_id": 3, "pos": [10,10,100,10,100,50,10,50] }, { "hierarchy": 3, "title": "1.1 公司简介", "page_id": 4, "pos": [10,10,100,10,100,50,10,50] } ] ] }, "total_page_number": 10, "valid_page_number": 3, "excel_base64": "", "success_count": 1, "elements": [ { "element_id": "", "type": "NarrativeText", "text": "xParse 是一个端到端文档处理 AI 基础设施", "metadata": { "page_image_url": "https://web-api.textin.com/ocr_image/external/01a91572ca81092c.jpg", "original_image_url": "", "angle": 0, "page_number": 1, "page_width": 600, "page_height": 800, "coordinates": [0.182212, 0.231622, 0.671733, 0.231634, 0.671754, 0.273244, 0.182266, 0.273255], "is_continue": false, "category_depth": -1, "parent_id": "", "sub_type": "stamp", "image_url": "https://web-api.textin.com/ocr_image/external/e47f8aed69ccabce.jpg", "image_base64": "" } } ] }, "version": "2.1.0", "duration": 999, "metrics": [ { "page_image_width": 1024, "page_image_height": 768, "dpi": 72, "durations": 123, "status": "", "page_id": 123, "angle": 90, "image_id": "" } ] } ``` ## 常规字段说明 * x\_request\_id:该请求的唯一标识。 * code:错误码,200表示成功。详情见[快速启动-错误码说明](/xparse/parse-quickstart)。 * message:错误信息,成功时为"success"。 * version:引擎版本号,例如"3.18.9"。 * duration:引擎耗时(毫秒),例如"999"。 ## 主要结果说明:result对象 文档解析API会在返回结果的result对象中包含以下关键信息。 ### markdown:正文字符串 * **markdown**:解析结果 markdown 的正文字符串。 ### detail:markdown 各类型元素详细信息 detail包含markdown中不同类型元素的详细信息。受URL参数**markdown\_details**影响,默认返回detail字段,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。 * **detail**:markdown各类型元素详细信息 * **page\_id**:当前元素所在页码,例如"1"。 * **paragraph\_id**:当前元素id。 * **outline\_level**:标题级别(最多支持5级标题) -1表示正文,0表示一级标题,1表示二级标题 ... * **text**:文本,例如"hello markdown"。 * **position**:以长度为8的整型数组表示四边形,8个数两两一组为一个点的横纵坐标,分别是左上,右上,右下,左下。 当输入是PDF时, 此坐标是基于72dpi的;当输入是图片时,此坐标是原图里的坐标。 单位:像素。例如\[217, 390, 1336, 390, 1336, 460, 217, 460] * **origin\_position**:受URL参数**切边矫正**和**去水印**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。仅当打开切边或去水印时返回,表示该段落在原图中的坐标。格式同**position**。 * **content**:表示元素是否为正文。0 正文(段落、图片、表格);1 非正文(页眉、页脚、侧边栏) * **type**:元素的类型。 * paragraph(段落类型,包括正文、标题、公式等文字信息) * image(图片类型) * table(表格类型) * **sub\_type**:元素子类型,受**type**影响。 * 当**type**为**paragraph**时,取值范围为catalog(目录),header(页眉),footer(页脚),sidebar(侧边栏),text(正文普通文本),text\_title(文本标题),image\_title(图片标题),table\_title(表格标题); * 当**type**是**image**时,取值范围为stamp(印章),chart(图表),qrcode(二维码),barcode(条形码); * 当**type**为**table**时,取值范围为bordered(有线表), borderless(无线表) * **image\_url**:图片链接,仅在**type**为**image**时返回。受URL参数**get\_image**和**image\_output\_type**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。 * 当get\_image = objects, image\_output\_type = default时,返回图片的url,图片默认保存30天;如需长久保存,请在有效期内下载图片并自行保存,可参考[获取图片并持久化](/xparse/parse-getimage); * 或者使用image\_output\_type = base64str,图片以base64的方式返回。 * **tags**:表示段落内是否存在特殊文本,类型包括公式formula和手写体handwritten,仅在**type**为**paragraph**时返回。 * **caption\_id**:表格或图片的标题id,仅在**type**为**image**或**table**时返回。 * **page\_id**:标题所在页码。 * **paragraph\_id**:标题所在段落id。 * **cells**:单元格数组,仅在**type**为**table**时返回。 * **row**:单元格行号。 * **col**:单元格列号。 * **row\_span**:单元格行跨度,默认为1。 * **col\_span**:单元格列跨度,默认为1。 * **position**:单元格的四个角点坐标,依次为左上,右上,右下,左下。例如\[10, 10, 100, 10, 100, 50, 10, 50] * **origin\_position**:受URL参数**切边矫正**或**去水印**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。仅当打开切边或去水印时返回,表示该单元格在原图中的坐标。格式同position。 * **text**:单元格文本内容。 * **type**:类型,固定为cell,表示单元格。 * **split\_section\_page\_ids**:当表格/段落有合并时,记录合并前各个子表格/段落所在的页的id * **split\_section\_positions**:当表格/段落有合并时,记录合并前各个子表格/段落所在页的位置,位置所属的页码与split\_section\_page\_ids按索引一一对应,如split\_section\_positions\[2]所属的页码为split\_section\_page\_ids\[2] * **stamp**:当sub\_type为stamp时,返回印章识别结果 * **value**:印章文本内容 * **stamp\_shape**:印章形状 * **type**:印章类型 * **color**:印章颜色 ### pages:每一页的详细信息 文档按页为单位展开时, 存储每一页的详情和状态(适用于PDF),部分信息与**metrics**字段重复。受URL参数**page\_details**影响,默认返回pages,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。 * **pages**:每一页的详细信息 * **page\_id**:当前页码 (若为流式文件, 页码置为0),例如"0"。 * **status**:表示当前页的引擎输出状态,或者error\_message,例如"success"。 * **durations**:当前页总耗时(毫秒),例如"612.5"。 * **width**:文档页宽度 * **height**:文档页高度 * **angle**:图像的角度(可选值0, 90, 180, 270) * **image\_id**:当前页图片id 。受URL参数**get\_image**和**image\_output\_type**影响,当URL参数image\_output\_type=default且get\_image=page/both时返回,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。 * 下载方式:[https://api.textin.com/ocr\_image/download?image\_id=xxx](https://api.textin.com/ocr_image/download?image_id=xxx) ,需要在headers里添加appid和key * **origin\_image\_id**:切边或去水印前的原始页图片。受URL参数**切边矫正**或**去水印**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。仅当开启切边或去水印,image\_output\_type=default且get\_image=page/both时返回。 * 下载方式同image\_id * **base64**:当前页图片的base64字符串,受URL参数**image\_output\_type**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。当输入参数image\_output\_type=base64str且get\_image=page/both时返回。 * **origin\_base64**:切边或去水印前的原始页图片base64字符串。受URL参数**切边矫正**或**去水印**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。仅当开启切边或去水印,image\_output\_type=base64str且get\_image=page/both时返回。 * **raw\_ocr**:全部文字识别结果,只包含文字结果。受URL参数**raw\_ocr**影响,默认不返回,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。 * **text**:识别内容字符串,例如"这是一个例子。" * **score**:识别置信度(0 \<= x \<= 1),例如"0.99"。 * **type**:文本类型,用于表示文字的形态。 当前版本下,文本类型包括: * text(文本) * formula(公式) * **position**:文本行的四个角点坐标,依次为左上,右上,右下,左下。例如\[10, 10, 100, 10, 100, 50, 10, 50] * **angle**:文本行的角度(可选值0, 90, 180, 270) * **direction**:文字阅读方向。 * -1: 其他 * 0: 单字 * 1: 横向阅读 * 2: 纵向阅读 * **handwritten**:文字是否手写所得。 * -1: 未知 * 0: 非手写文字, 一般为印刷文字 * 1: 文字手写, 一般具备明显的书写特征 * **char\_scores**:字符置信度,值域范围0-1。 受URL参数**char\_details**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。设置char\_details=1时输出。 * **char\_centers**:字符中心点坐标。受URL参数**char\_details**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。设置char\_details=1时输出。 * **char\_positions**:字符四边形点坐标,以顺时针构成闭合区域。 受URL参数**char\_details**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。设置char\_details=1时输出。 * **char\_candidates**:候选字数组,表示每一个字符的候选,与候选置信度配套使用。受URL参数**char\_details**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。 设置char\_details=1时输出。 * **char\_candidates\_score**:候选字置信度数组,表示每一个候选字符的置信度,与候选字符配套使用。 受URL参数**char\_details**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。 设置char\_details=1时输出。 * **content**:基础数据,为文字行、图像中的其中一种。 * **textline:文字行** * **id**:数据id(页内唯一) * **type**:数据类型,line * **text**:文本行文字内容 * **angle**:文本行文字方向, 默认为0(angle为0时, json中可能不包含angle属性)。 * **pos**:文本行四个角点坐标。 * **origin\_position**:表示文本行在原图中的坐标。受URL参数**切边矫正**或**去水印**影响,详情见[**快速启动-URL参数说明**](https://docs.textin.com/xparse/parse-quickstart)。仅当打开切边或去水印时返回,格式同pos。 * **sub\_type**:子类型。有handwriting, formula。 * **direction**:文字方向, 默认为0。 * 0:横向文本; * 1:竖向文本; * 2:横向右往左文本(如阿拉伯语) * **score**:文本行内每个字符的置信度(仅当输入图像做ocr时) * **char\_pos**:文本行内每个字符的坐标,每个item是一个由八个整数组成的数组,分别表示,左上,右上,右下,左下四个点的(x,y)坐标。受URL参数**char\_details**影响,详情见[**快速启动-URL参数说明**](https://docs.textin.com/xparse/parse-quickstart)。设置char\_details=1时输出。 * **image:图像** * **id**:数据id * **type**:数据类型, image * **pos**:图像四个角点坐标 * **sub\_type**:子类型, 包括stamp, chart, qrcode, barcode * **size**:图像大小\[width, height] * **data**:图像内容 * **base64**:图像文件(jpg, png)的base64字符串 * **region**:图像在页图像中的区域(四边形4个点坐标) * **path**:图像文件路径(如在zip包中的路径) * **stamp**:当sub\_type为stamp时,返回印章识别结果 * **value**:印章文本内容 * **stamp\_shape**:印章形状 * **type**:印章类型 * **color**:印章颜色 * **structured**:结构化数据,为段落块、图像块、表格块、页脚块、页眉块中的一种。 * **textblock:段落块** * **type**:段落块类型, 固定为 textblock * **pos**:文本行四个角点坐标 * **origin\_position**:表示该段落在原图中的坐标。受URL参数**切边矫正**或**去水印**影响,详情见[**快速启动-URL参数说明**](https://docs.textin.com/xparse/parse-quickstart)。仅当打开切边或去水印时返回,格式同pos。 * **content**:段落块内文本行id数据组 * **sub\_type**:段落块子类型,包括catalog(目录),text(正文普通文本),text\_title(文本标题),image\_title(图片标题),table\_title(表格标题) * **continue**:段落块连续属性,用于判断完整的段落块是否被页面或栏分割,为true表示该段落块和下一个段落块连续(即两个段落块可合成一个逻辑段落块)。 * **next\_page\_id**:当且仅当continue为true时有值。表示下一个段落块的page\_id。 * **next\_para\_id**:当且仅当continue为true时有值。表示下一个段落块的paragraph\_id。 * **text**:段落块文本内容 * **outline\_level**:标题级别: (最多支持5级标题) * -1:正文 * 0:一级标题 * 1:二级标题 * … * **imageblock:图像块** * **type**:图像块类型, 值为 image * **pos**:文本行四个角点坐标 * **origin\_position**:表示该子图在原图中的坐标。受URL参数**切边矫正**或**去水印**影响,详情见[**快速启动-URL参数说明**](https://docs.textin.com/xparse/parse-quickstart)。仅当打开切边或去水印时返回,格式同pos。 * **lines**:图像包含的文本行id * **content**:图像资源数据id数组 * **caption\_id**:图片的标题id * **page\_id**:标题所在页码 * **paragraph\_id**:标题所在段落id * **text**:子图片识别得到的文本内容。受URL参数**apply\_image\_analysis**影响,详情见[**快速启动-URL参数说明**](https://docs.textin.com/xparse/parse-quickstart)。开启图像分析后,该字段内容会替换为大模型对子图片的分析结果。 * **table:表格块** * **type**:表格块类型, 固定为table * **sub\_type**:表格子属性,取值为bordered(有线表)或borderless(无线表),默认为bordered(即json中无该字段时,默认值为bordered) * **pos**:文本行四个角点坐标 * **origin\_position**:表示该表格在原图中的坐标。受URL参数**切边矫正**或**去水印**影响,详情见[**快速启动-URL参数说明**](https://docs.textin.com/xparse/parse-quickstart)。仅当打开切边或去水印时返回,格式同pos。 * **rows**:表格行数 * **cols**:表格列数 * **columns\_width**:表格列宽度列表 * **rows\_height**:表格行高度列表 * **text**:表格文本内容,以html或md格式展示 * **continue**:当前表格与后一表格连续,用来判断一个表格是否被页面分割(如果 continue为true 且该表格位于本页结尾,该表格可与下一页开头表格组合为一个表格) * **caption\_id**:表格的标题id * **page\_id**:标题所在页码 * **paragraph\_id**:标题所在段落id * **cells**:单元格数组 * **row**:单元格行号 * **col**:单元格列号 * **row\_span**:单元格行跨度,默认为1 * **col\_span**:单元格列跨度,默认为1 * **pos**:单元格的四个角点坐标,依次为左上,右上,右下,左下。 * **content**:单元格内容 * **footer:页脚块** * **type**:页脚块类型,固定为 footer * **pos**:文本行四个角点坐标 * **blocks**:footer段落内容,为textblock, imageblock, table中其中的一种 * **header:页眉块** * **type**:页眉块类型,固定为 header * **pos**:文本行四个角点坐标 * **blocks**:header段落内容,为textblock, imageblock, table中的其中一种 ### catalog:描述目录树的结构 * **catalog**:目录树结构。受URL参数**catalog\_details**和**apply\_document\_tree**影响,详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。 * **toc**:返回的table of contents * **sub\_type**:标题类型 text\_title、 image\_title、 table\_title * **hierarchy**:标题层级, 1 是 一级标题, 2 是 二级标题,依次类推 * **title**:标题内容 * **page\_id**:标题所在页码(最小页码为 1) * **paragraph\_id**:标题所在段落id * **pos**:该目录区域的四个角点坐标,依次为左上,右上,右下,左下。 * **pos\_list**:发生标题合并时,为合并前多个标题的坐标;未发生标题合并时,即为该标题的坐标。 ### elements:元素数组(parse\_mode 为 lite 或 vlm 时返回) 当 `parse_mode` 为 `lite` 或 `vlm` 时,返回 `elements` 数组结构,替代原有的 `detail` 和 `pages` 结构。如需将 `elements` 格式转换为原有格式,请参考[转换脚本](/xparse/parse-quickstart#elements格式转换脚本)。 * **success\_count**:解析成功页数 * **elements**:element 数组,包含文档中的所有元素 * **element\_id**:唯一标识 * **type**:类型,具体见下文 Element type 类型说明 * **text**:文本内容 * **metadata**:元数据对象 * **page\_image\_url**:页图url * **original\_image\_url**:原始页图url,仅当开启去水印或切边时返回 * **angle**:页面角度 * **page\_number**:页码 * **page\_width**:页宽 * **page\_height**:页高 * **coordinates**:element的归一化坐标(float数组,8个值,六位小数) * **is\_continue**:是否和下一个element合并 * **category\_depth**:目录层级,0表示一级标题,1表示2级标题,依次增加。-1表示正文段落 * **parent\_id**:父节点的element\_id。表格和图片等父节点为对应的表格标题和图片标题。文本段落的父节点为对应的文本标题 * **sub\_type**:仅当type为Image时返回,包括stamp, card, qrcode\_barcode, chart * **image\_url**:仅当type为Image 且请求参数 image\_output\_type=default 时返回,值为子图的url * **image\_base64**:仅当type为Image 且请求参数 image\_output\_type=base64str时返回,值为子图的base64字符串 #### Element type 类型说明 当 `parse_mode` 为 `lite` 或 `vlm` 时,返回的 `elements` 数组中每个 element 的 `type` 字段可能的值如下: | Element type | 说明 | | ----------------- | -------------------------- | | NarrativeText | 除了标题、页眉页脚、图片说明文字列表外的普通段落文字 | | Title | 章节标题 | | Table | 表格 | | TableCaption | 表格标题 | | Image | 图片 | | FigureCaption | 图片标题 | | Formula | 公式 | | Header | 页眉 | | Footer | 页脚 | | CodeSnippet | 代码片段 | | PageNumber | 页码 | | UncategorizedText | 其他文本 | ### 其他result字段 * **total\_count**:解析总页数 * **success\_count**:解析成功的页数(parse\_mode 为 lite 或 vlm 时返回) * **total\_page\_number**:输入PDF时, 返回文档的总页数。 * **valid\_page\_number**:记录本次解析成功的总页数。 * **excel\_base64**:excel的base64结果,受URL参数**get\_excel**影响,仅当get\_excel=1时返回。详情见[快速启动-URL参数说明](/xparse/parse-quickstart)。 ## metrics:每一页的信息 部分信息跟**pages**字段重复,当URL参数**page\_details**设置为不返回pages字段时,可以在**metrics**字段获取每一页的信息。 * **metrics**:每一页信息 * **page\_image\_width**:当前段落所在页的图片宽或者pdf转成的图片宽,例如"1024"。 * **page\_image\_height**:当前段落所在页的图片高或者pdf转成的图片高,例如"768"。 * **dpi**:当前pdf页转成图片所用的dpi,例如"72"。 * **durations**:当前页总耗时(毫秒) * **status**:当前页状态 * **page\_id**:当前页码 * **angle**:图像角度, 定义0度为人类阅读文字的图像方向,称为正置图像, 本字段表示输入图像是正置图像进行顺时针若干角度的旋转所得。 * 0: ▲ * 90: ▶ * 180: ▼ * 270: ◀ * **image\_id**:当前页图片id。 * 下载方式:[https://api.textin.com/ocr\_image/download?image\_id=xxx](https://api.textin.com/ocr_image/download?image_id=xxx), 需要在headers里添加appid和key, 有效期30天 **另外您也可以在**[**API**](https://docs.textin.com/api-reference/endpoint/parse)**中查看Response说明以及调试查看结果。** ## 将结果保存为JSON或markdown文件 参考[快速启动](/xparse/parse-quickstart)中的使用示例,您可以将API返回的结果保存为JSON文件,也可以解析JSON响应以提取并保存markdown文件。以下示例代码在快速启动中已提供,您可以直接使用。 ```python theme={null} # 保存完整的JSON响应到result.json文件 with open("result.json", "w", encoding="utf-8") as f: f.write(response) # 解析JSON响应以提取markdown内容 json_response = json.loads(response) if "result" in json_response and "markdown" in json_response["result"]: markdown_content = json_response["result"]["markdown"] with open("result.md", "w", encoding="utf-8") as f: f.write(markdown_content) ``` # 前端可视化:获取精确坐标 Source: https://docs-docflow.textin.com/xparse/parse-getpos 一个功能丰富的RAG应用,通常会支持用户查看大模型找到的片段在原文档中的具体位置,从而让用户检查大模型是否在胡编乱造。而这就需要能把文本段落等各元素在原文档中高亮标记出来的能力。 TextIn xParse文档解析API支持返回块级坐标`position`以及字符级坐标`char_pos`(请求时设置URL参数`char_details=true`),代表解析结果片段在原文档中的精确位置。 将解析结果和坐标可视化,有助于: * 与原文档对照,细粒度验证解析的效果 * 审核校正解析结果 例如,下面是一个原文档(带坐标回显,左)和解析结果(Markdown,右)的对比图: 接下来详细介绍如何利用python实现坐标可视化。 本教程基于Textin官方pdf示例文件,您可点击下载或使用该链接:[文档解析pdf示例.pdf](https://web-api.textin.com/open/image/download?filename=a2bd40607faa4be7ba975d41c96b9a47) ## 上传pdf文件,获取解析结果 参考[快速启动](/xparse/parse-quickstart),上传pdf文件,获取解析结果。为获得详细的页面信息和坐标数据,解析时需要设置URL参数page\_details=1和markdown\_details=1。 本次示例文件解析结果如下(为方便展示,此处只解析一页,仅列出坐标相关数据): ```python theme={null} { "code": 200, "message": "success", "duration": 1751, "result": { "pages": [ { "status": "Success", "angle": 0, "page_id": 1, "width": 1191, "height": 1684, "structured": [ { "blocks": [ { "id": 0, "pos": [71,146,549,144,548,185,70,187], "text": "某服装企业(600398.SH)", "type": "textblock" # ... } ], "type": "header" }, { "type": "header", "blocks": [ { "id": 2, "pos": [69,203,781,203,781,238,69,238], "text": "第三季度收入下滑11%,费用率提升盈利承压", "type": "textblock", } ], } ] # ... } ], "detail": [ { "page_id": 1, "text": "**某服装企业(600398.SH)**" "position": [71,146,549,144,548,185,70,187] # ... }, { "page_id": 1, "text": "**第三季度收入下滑11%,费用率提升盈利承压**" "position": [69,203,781,203,781,238,69,238] # ... } # ... ] } } ``` `pages`字段包含每一页的信息,其中`page_id`表示页码(从1开始),`width, height`表示识别时文档转成图像的宽高,`angle`表示将图像转正的角度(如需要),`structured`表示解析后对应页的结构化数据,包含元素块内容以及对应的坐标`pos`。 `detail`中包含所有markdown块级元素(文字、段落、表格等)的详细信息,每一块通过`page_id`与页码关联,`position`字段表示该块的坐标信息。与`pages`不同的是,detail中是将markdown内容规整后元素块,比如跨页段落、跨页表格在detail中已经合并,采用了更好的语义上的分割,可以直接用于下游需要分块的应用,而`pages`中最大限度地保留了每一页的原始信息。 **坐标系统说明** 接口返回的坐标格式为:`[x1, y1, x2, y2, x3, y3, x4, y4]` 这表示一个四边形的四个顶点坐标,按顺时针排列: ``` 坐标数组: [x1, y1, x2, y2, x3, y3, x4, y4] ↑左上 ↑右上 ↑右下 ↑左下 ``` 该坐标表示在识别时以页面左上角为原点,宽高为`page.width`、`page.height`画布下的绝对坐标,单位为像素(px)。 比如上述接口返回: ```json theme={null} "pages": [{ "width": 1191, "height": 1684}] "position": [69,203,781,203,781,238,69,238] ``` 在图像上示意如下: ``` 图像坐标系 (原点在左上角) ┌───────────────1191────────────────── x │(0,0) │ │ │ (69,203) ───────────── (781,203) │ │ │ 1684 文本区域 │ │ │ │ (69,238) ───────────── (781,238) │ │ │ │ y ``` 下面演示如何从pages中和detail中获取页面和元素块坐标信息,并在原文档上绘制标注。 原文档页面图片可以通过设置参数`get_image="page"或"both"`返回, 您将获得每一页的image\_id或者base64(详见[JSON结构说明](/xparse/parse-getjson))用于预览,也可以手动将您的原文档转成图片, 只要保证每一页的图片跟上述`page.width`、`page.height`同比例渲染,`position`中的坐标值也需要跟随页面同比例缩放,,即可准确绘制。 ## 从API返回结果中获取坐标数据 从json结果提取出每一页的元素坐标信息,输出到二维数组: ```python theme={null} def extract_coordinates_from_parse_result(parse_result): """ 从API返回的解析结果中提取每页的宽高、角度和所有detail块的坐标信息 返回: [{width, height, angle, details: [detail, ...]}, ...] """ result = parse_result.get("result", {}) pages = result.get("pages", []) details = result.get("detail", []) # 按页组织details page_map = {} for page in pages: page_id = page.get("page_id", 1) page_map[page_id] = { "width": page.get("width", 0), "height": page.get("height", 0), "angle": page.get("angle", 0), "details": [] } for d in details: page_id = d.get("page_id", 1) if page_id in page_map: page_map[page_id]["details"].append(d) # 保证顺序 return [page_map[pid] for pid in sorted(page_map.keys())] ``` ## 绘制坐标框到原图 ```python theme={null} import fitz # PyMuPDF from PIL import Image, ImageDraw import os # pdf转图片,获取页面图片 def pdf_to_images(pdf_path, output_dir="./temp_images"): os.makedirs(output_dir, exist_ok=True) doc = fitz.open(pdf_path) zoom = 144 / 72 # dpi=144,图片更清晰 mat = fitz.Matrix(zoom, zoom) image_paths = [] for i, page in enumerate(doc): pix = page.get_pixmap(matrix=mat) img_path = os.path.join(output_dir, f"page_{i+1}.png") pix.save(img_path) image_paths.append(img_path) doc.close() return image_paths # 绘制一页坐标 def draw_boxes_on_image(image_path, details, page_width, page_height, color=(26,102,255), line_width=2): image = Image.open(image_path).convert("RGB") draw = ImageDraw.Draw(image) img_w, img_h = image.size # 根据解析时的页面宽高缩放适配,确保绘制坐标准确 scale_x = img_w / page_width if page_width else 1 scale_y = img_h / page_height if page_height else 1 for d in details: pos = d.get("position") if pos and len(pos) == 8: points = [ (pos[0]*scale_x, pos[1]*scale_y), (pos[2]*scale_x, pos[3]*scale_y), (pos[4]*scale_x, pos[5]*scale_y), (pos[6]*scale_x, pos[7]*scale_y), (pos[0]*scale_x, pos[1]*scale_y) ] draw.line(points, fill=color, width=line_width) out_path = image_path.replace('.png', '_boxed.png') image.save(out_path) return out_path # 绘制所有页面全部坐标 def annotate_pdf_with_boxes(pdf_path, page_details, output_dir="./annotated_images"): os.makedirs(output_dir, exist_ok=True) image_paths = pdf_to_images(pdf_path, output_dir=output_dir) result_paths = [] for i, page in enumerate(page_details): img_path = image_paths[i] out_path = draw_boxes_on_image( img_path, page["details"], page["width"], page["height"] ) result_paths.append(out_path) return result_paths # 使用示例 if __name__ == "__main__": import json PDF_PATH = "your_document.pdf" JSON_PATH = "parse_result.json" with open(JSON_PATH, "r", encoding="utf-8") as f: parse_result = json.load(f) page_details = extract_coordinates_from_parse_result(parse_result) result_imgs = annotate_pdf_with_boxes(PDF_PATH, page_details) print("标注图片:", result_imgs) ``` ### 代码使用说明 **1. 准备环境** ```bash theme={null} pip install PyMuPDF pillow ``` **2. 绘制图片和坐标** 将上述代码保存为`pdf_coordinate_drawer.py`文件,替换main函数中的`your_document.pdf`和`parse_result.json`为真实文件路径,运行: ```bash theme={null} python3 pdf_coordinate_drawer.py ``` **3. 输出结果** * 每页生成一个标注后的PNG图片 * 坐标框用指定颜色绘制,您也可以自定义颜色,不同类型元素用不同颜色绘制 * 生成坐标类型图例 该示例生成的标注最终效果如下: 可以看到,Textin xParse针对复杂布局的文档,能够精准识别并细粒度还原坐标,方便您将解析结果与原文件进行对比,查看解析效果以及审核校正。 ## 前端开源项目 此外,我们开源了web前端项目[xparse-frontend](https://github.com/intsig-textin/xparse-frontend/),该项目包含跟我们[在线web平台](https://www.textin.com/console/recognition/robot_markdown?service=pdf_to_markdown)效果一致的全套前端代码,具备文件预览、坐标回显、动态交互对照、编辑校正、导出多种格式结果文件等丰富功能。上手方便,开箱即用,欢迎体验! ## 常见问题 ### **坐标偏移不准确,有错位** **可能原因:** * 文档转图片使用了跟解析时不同的DPI,且坐标没有根据解析返回的页面宽高缩放适配 * 页面旋转角度未正确处理 **解决方案** * 文档转图片的时候使用跟解析相同的DPI,或者渲染时将页面和坐标值根据解析返回的页面宽高缩放适配(推荐,参考上述代码示例) * 确认页面是否经过旋转(angle),绘制时设置angle修正角度 ### **如何处理跨页段落和表格** 跨页信息可以在两个位置获取: * 在pages的structure中:跨页信息通过continue和next\_page\_id、next\_para\_id表示 * 在detail中:跨页信息通过通过split\_section\_page\_ids和split\_section\_positions表示 更多信息请参考[JSON结构说明](/xparse/parse-getjson)。 # 获取表格 Source: https://docs-docflow.textin.com/xparse/parse-gettable 在RAG应用中,为了更高的信息精度或稳定性,通常需要对表格做单独处理,如将表格区域保存为图片供前端展示,或者单独为表格设置分chunk策略等。在TextIn xParse文档解析API的输出中,对每个表格都有单独的定义,您可以获取每个表格并单独保存下来。 此外,我们也提供了一步到位的从PDF中提取表格并保存为Excel文件的教程,方便整合到您现有的基于Excel的业务任务流程中。 ## 如何获取表格 您可以参考以下步骤和示例代码将解析获取到的表格保存为 md 和 json 以及 excel 格式的文件。 ### 将表格保存为 md 和 json 文件 * * 参考[快速启动](/xparse/parse-quickstart),在 options 中设置URL参数 table\_flavor 为 md 或 html,这样API会以Markdown或HTML格式输出表格。您可根据实际需要进行设置。 * 在main函数中添加以下示例代码,解析API输出markdown中的表格,并保存为 md 和 json 文件。 ```python theme={null} import re if "result" in json_response and "markdown" in json_response["result"]: markdown_content = json_response["result"]["markdown"] # 提取所有表格 tables = re.findall(r'(?:\|.*\n)+', markdown_content) tables_md = '\n'.join(tables) # 保存为md文件 with open("tables.md", "w", encoding="utf-8") as f: f.write(tables_md) tables_json = [] for page in json_response["result"]["pages"]: for block in page.get("structured", []): if block.get("type") == "table": tables_json.append(block) # 保存为 json 文件 with open("tables.json", "w", encoding="utf-8") as f: json.dump(tables_json, f, ensure_ascii=False, indent=2) ``` ### 将表格保存为 excel 文件 * 参考[快速启动](/xparse/parse-quickstart),在 options 中设置URL参数 get\_excel=1,让API返回 excel\_base64 字段(Excel文件的base64编码)。 * 在main函数中添加以下示例代码,将表格保存为excel文件。 ```python theme={null} import base64 if "result" in json_response and "excel_base64" in json_response["result"]: excel_base64 = json_response["result"]["excel_base64"] excel_bytes = base64.b64decode(excel_base64) with open("result.xlsx", "wb") as f: f.write(excel_bytes) print("Excel 文件已保存为 result.xlsx") else: print("未检测到 excel_base64 字段,可能 PDF 中没有表格或参数设置有误。") ``` * 参考[快速启动](/xparse/parse-quickstart)中的示例文件,保存后的表格如下图(仅截取部分作为示例) Parse Gettable Pn # 多并发请求 Source: https://docs-docflow.textin.com/xparse/parse-max-workers 在实际使用过程中,您可能会需要在一定时间内集中性的批量解析文档;在这种情况下,即使TextIn xParse 文档解析API本身的速度足够快,但依次逐个解析大批量文档所需要的总耗时也可能会较长。 针对这种情况,TextIn xParse 文档解析API支持多并发请求,默认2 QPS,如果您有更大并发的需求,可以联系我们进行[商务咨询](https://www.textin.com/contact?type=28)。帮助您快速高效的完成大批量文档解析工作。 ### 多并发测试 您可以先参考以下示例代码进行文档解析API的多并发请求测试。 ```python theme={null} import concurrent.futures import subprocess import time # 要测试的命令 CMD = [ "python3", "用于跑示例的请求脚本.py" # 替换为你用于测试的请求脚本,也可以使用下文提供的脚本进行测试 ] # 并发数 CONCURRENCY = 5 # 总测试次数 TOTAL_RUNS = 5 def run_cmd(i): try: result = subprocess.run(CMD, capture_output=True, text=True, check=True) print(f"任务 {i} 成功,输出:{result.stdout.strip()}") except subprocess.CalledProcessError as e: print(f"任务 {i} 失败,错误:{e.stderr.strip()}") if __name__ == "__main__": print(f'并发测试,当前并发数为: {CONCURRENCY}') start_time = time.time() with concurrent.futures.ThreadPoolExecutor(max_workers=CONCURRENCY) as executor: futures = [executor.submit(run_cmd, i) for i in range(TOTAL_RUNS)] concurrent.futures.wait(futures) end_time = time.time() print(f"程序总耗时:{end_time - start_time:.2f} 秒") ``` 这里使用[快速启动](/xparse/parse-quickstart)中的示例文件进行多并发测试:[文档解析pdf示例.pdf](https://dllf.intsig.net/download/2025/Solution/textin/sample/pdf_to_markdown/sample_02.pdf) * 测试脚本如下:参考[快速启动](/xparse/parse-quickstart),解析位于URL的文件并保存结果;需替换您自己的 x-ti-app-id 和 x-ti-secret-code 注意:进行多并发测试等同对当前账号发起线上资源操作,请留意费用情况,请小心操作。 ```python theme={null} import json import requests class OCRClient: def __init__(self, app_id: str, secret_code: str): self.app_id = app_id self.secret_code = secret_code def recognize(self, file_content: bytes, options: dict) -> str: # 构建请求参数 params = {} for key, value in options.items(): params[key] = str(value) # 设置请求头 headers = { "x-ti-app-id": self.app_id, "x-ti-secret-code": self.secret_code, # 方式一:读取本地文件 # "Content-Type": "application/octet-stream" # 方式二:使用URL方式 "Content-Type": "text/plain" } # 发送请求 response = requests.post( f"https://api.textin.com/ai/service/v1/pdf_to_markdown", params=params, headers=headers, data=file_content ) # 检查响应状态 response.raise_for_status() return response.text def main(): # 创建客户端实例,需替换为你的API Key client = OCRClient("你的x-ti-app-id", "你的x-ti-secret-code") # 文件URL,这里为你提供了一份真实可用的示例URL file_content = "https://dllf.intsig.net/download/2025/Solution/textin/sample/pdf_to_markdown/sample_02.pdf" # 设置URL参数,可按需设置,这里已为你默认设置了一些参数 options = dict( dpi=144, get_image="objects", markdown_details=1, page_count=10, parse_mode="auto", table_flavor="html" ) import time # 在发送请求前记录开始时间 start_time = time.time() try: response = client.recognize(file_content, options) # 保存完整的JSON响应到result.json文件 with open("result.json", "w", encoding="utf-8") as f: f.write(response) # 解析JSON响应以提取markdown内容 json_response = json.loads(response) if "result" in json_response and "markdown" in json_response["result"]: markdown_content = json_response["result"]["markdown"] with open("result.md", "w", encoding="utf-8") as f: f.write(markdown_content) # 记录请求结束时间 end_time = time.time() print(f"请求耗时:{end_time - start_time:.2f} 秒") except Exception as e: print(f"Error: {e}") if __name__ == "__main__": main() ``` * 多并发测试结果如下图:可以看到文档解析API支持多并发请求,并且可以极大程度上节省时间。我们始终贯彻“您只需关心业务,剩下的文档解析处理工作交给TextIn”的理念,希望尽一切可能为您的业务发展提供帮助。 Concurrent1 Pn Concurrent5 Pn ### 多并发请求 当您想要进行文档解析API的多并发请求时,以下是一份完整的示例代码供您参考,您也可以根据实际使用需要进行修改调整。 ```python theme={null} import os import json import requests from concurrent.futures import ThreadPoolExecutor, as_completed class OCRClient: def __init__(self, app_id: str, secret_code: str): self.app_id = app_id self.secret_code = secret_code def recognize(self, file_content: bytes, options: dict) -> str: params = {key: str(value) for key, value in options.items()} headers = { "x-ti-app-id": self.app_id, "x-ti-secret-code": self.secret_code, "Content-Type": "application/octet-stream" } response = requests.post( "https://api.textin.com/ai/service/v1/pdf_to_markdown", params=params, headers=headers, data=file_content ) response.raise_for_status() return response.text def process_file(client: OCRClient, file_path: str, output_dir: str, options: dict): filename = os.path.basename(file_path) try: with open(file_path, "rb") as f: file_content = f.read() response = client.recognize(file_content, options) base_name = os.path.splitext(filename)[0] # 保存JSON with open(os.path.join(output_dir, f"{base_name}.json"), "w", encoding="utf-8") as fw: fw.write(response) # 保存Markdown json_response = json.loads(response) if "result" in json_response and "markdown" in json_response["result"]: markdown_content = json_response["result"]["markdown"] with open(os.path.join(output_dir, f"{base_name}.md"), "w", encoding="utf-8") as fw: fw.write(markdown_content) print(f"{filename} 处理完成") except Exception as e: print(f"{filename} 处理出错: {e}") def main(): client = OCRClient("你的x-ti-app-id", "你的x-ti-secret-code") input_dir = "./tmp" # 你的待解析文件夹 output_dir = "./output" # 输出结果的文件夹 os.makedirs(output_dir, exist_ok=True) exts = (".pdf",".png",".jpg",".jpeg",".bmp",".tiff",".webp",".doc",".docx",".html",".mhtml",".xls",".xlsx",".csv",".ppt",".pptx",".txt",".ofd",".rtf") files = [f for f in os.listdir(input_dir) if f.lower().endswith(exts)] file_paths = [os.path.join(input_dir, f) for f in files] # 设置URL参数,可按需设置,这里已为你默认设置了一些参数 options = dict( dpi=144, get_image="objects", markdown_details=1, page_count=10, parse_mode="auto", table_flavor="html" ) # 设置并发数 max_workers = 5 # 你可以根据需要调整并发数 with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = [ executor.submit(process_file, client, file_path, output_dir, options) for file_path in file_paths ] for future in as_completed(futures): # 这里可以捕获每个任务的异常 try: future.result() except Exception as exc: print(f"任务出错: {exc}") if __name__ == "__main__": main() ``` # 快速启动 Source: https://docs-docflow.textin.com/xparse/parse-quickstart 参考示例,快速将文档解析API接入到您的系统和应用流程中。 本教程基于python示例分步讲解如何使用文档解析API。我们另外提供了完整的多语言示例代码包,可在本地一键运行,助您10s跑通接口示例,请[点击下载](https://static.textin.com/docs/%E9%80%9A%E7%94%A8%E6%96%87%E6%A1%A3%E8%A7%A3%E6%9E%90-%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81.zip)。如需在线快捷调试API,请参考[Textin文档中心](https://www.textin.com/document/legacy/pdf_to_markdown)。 ## 为什么使用文档解析API ? 大模型时代,文档(尤其是复杂文档)中蕴含着海量高价值的数据内容,借助文档解析API将其结构化为大模型更容易理解的格式(如markdown),可以更大程度上增强大模型的能力、发挥更大价值,快速实现业务AI升级。 TextIn xParse 文档解析API 是专为大模型重新设计的文档理解引擎,可以满足AI开发者的核心需求:✅ 文档结构完整保持 ✅ 语义关系准确理解 ✅ 大模型原生友好 使用文档解析API解析一个或多个文档,您可以选择将输出结果作为markdown或JSON文件保存在指定的目录中,也可以对输出结果做进一步的处理以满足您的业务需求。如果您正在进行知识库、RAG、大模型原生应用、Agent等业务方向的产品建设,文档解析API会为您提供帮助。 ## 如何使用文档解析API ? 您可以参考以下示例文件和步骤,快速验证并将文档解析API接入到您的系统和应用流程中。 这里为您提供了一份Textin官方pdf示例文件,您可以点击下载或使用该链接:[文档解析pdf示例.pdf](https://dllf.intsig.net/download/2025/Solution/textin/sample/pdf_to_markdown/sample_02.pdf)