---
title: 百度智能云 PaddleOCR-VL 接入实践：一个文档解析校样工具的实现
description: 用 Python 标准库接入百度智能云 PaddleOCR-VL 文档解析 API，串起异步任务、结果下载、原件预览与坐标联动，分享开源校样工具的实现与边界。
image: "https://zengliangyi.com/og/blog/paddleocr-vl-api-proofreading.png"
---

文档解析接口返回了 Markdown，接入工作就完成了吗？如果只是提取文字，或许已经够用。但面对双栏 PDF、跨页表格和混排图片，还需要回答几个问题：段落顺序对不对，表格有没有漏掉，结果中的这段文字究竟来自原件哪里？

我做了一个本地 Web 工具，把百度智能云 PaddleOCR-VL 文档解析 API 的调用和结果校样放在同一个页面里。左侧显示原件或重建版面，右侧显示阅读顺序、Markdown 和 JSON，点击元素可以定位到对应页面和区域。

[工具源码已按 MIT 协议开源](https://github.com/ZengLiangYi/paddleocr-vl-document-parser-demo)。文档识别由百度智能云完成，本地运行的是调用服务和展示界面，无需下载模型或准备 GPU。本文围绕这个工具的实现展开，不涉及模型训练，也不把界面展示当作识别准确率的证明。

## 从可运行的调用链开始

项目的主要代码只有两个文件：`server.py` 负责本地 HTTP 服务与云端调用，`index.html` 负责上传和校样。后端使用 Python 标准库中的 `http.server`、`urllib.request` 和 `json`，不需要安装第三方 Python 包。前端的 PDF.js 和 Markdown 渲染库通过 CDN 加载，因此整个工具仍有网络依赖。

调用过程可以拆成六步：



```text
浏览器读取文档，发送文件名和 Base64  
      ↓  
  本地服务读取密钥，获取 Access Token  
      ↓  
  提交解析任务，取得 task_id  
      ↓  
  轮询任务，等待成功或失败  
      ↓  
  下载解析 JSON 和 Markdown  
      ↓  
  浏览器展示结果，建立元素与页面之间的定位关系
```



浏览器只调用本地的 `/api/parse`。百度 API Key 和 Secret Key 由 Python 服务读取，不需要写进前端页面。

这里有两层不同的请求格式。浏览器向本地服务发送 JSON；Python 调用百度的任务提交和查询接口时，发送的是 `POST + application/x-www-form-urlencoded`。代码中的 `_post_form()` 统一完成表单编码、网络错误处理和 JSON 解析，不能把其中任意一层的请求格式直接套到另一层。

本文使用的任务接口路径是：



```text
提交：/rest/2.0/brain/online/v2/paddle-vl-parser/task  
  查询：/rest/2.0/brain/online/v2/paddle-vl-parser/task/query
```



这些路径对应百度智能云的文档解析服务。接入时应使用[对应 API 文档](https://cloud.baidu.com/doc/OCR/s/Qmncwhwdt)，确认凭据和请求参数，避免混用其他 PaddleOCR 部署方式的示例。

## 任务提交成功，还没有拿到解析结果

提交接口返回 `task_id`，表示任务已经建立。此时继续查询任务状态，直到 `success` 或 `failed`，成功后才有可下载的结果链接。

下面是项目轮询逻辑的简化写法，省略了响应结构检查；`query_document()` 是项目中已经封装好的查询函数：



```python
deadline   =   time.monotonic()   +   MAX_WAIT_SECONDS  
  
  while   time.monotonic()   <   deadline:  
      payload   =   query_document(access_token, task_id)  
      result   =   payload.get(  "result"  , {})  
      status   =   result.get(  "status"  )  
  
      if   status   ==   "success"  :  
          return   payload  
      if   status   ==   "failed"  :  
          raise   BaiduAPIError(  f  "解析失败:   {  result.get(  'task_error'  )  }  "  )  
  
      time.sleep(  POLL_SECONDS  )  
  
  raise   BaiduAPIError(  "等待解析超时"  )
```



当前实现将轮询间隔设为 5 秒，等待阈值设为 600 秒。使用 `time.monotonic()` 计算经过时间，可以避免系统时钟调整影响截止时间判断。不过，这并不是整个请求严格限定在十分钟内：鉴权、提交、下载各有耗时，进行中的查询请求也有自己的超时。

另一个容易忽略的区别是：云端任务是异步的，但这个 Demo 的浏览器请求会一直等待本地服务完成轮询和下载。后端没有把任务保存到数据库，也没有给浏览器提供可恢复的任务查询接口。这个实现适合单人评估；如果要支持多人使用、断线恢复或长任务，需要进一步设计任务管理。

任务成功后，代码检查 `parse_result_url` 和 `markdown_url` 是否都存在，再分别下载 JSON 和 Markdown。这样，前端拿到的是可展示的内容，不必再自行请求这两个主结果地址。

下载主结果并不等于归档完成。Markdown 或 JSON 引用的图片仍可能指向云端临时地址；当前工具也没有持久化结果，需要保留的内容应及时导出。

## 阅读顺序来自接口，内容可能在别的字段里

结果展示以 `pages` 为入口。每页的 `layouts` 提供版面元素，而表格、图片的具体内容还可能分别保存在 `tables` 和 `images` 中。

如果界面只遍历 `layouts` 并显示 `layout.text`，就可能遇到这样的情况：版面上明明识别出了表格，右侧却显示为空。此时需要先看关联记录，不能直接认定接口没有返回内容。

工具通过同一页内的 `layout_id` 做关联，显示内容的优先级是：

- 找到对应表格时，取表格 Markdown 或 HTML。

- 找到对应图片时，取图片描述或图片地址。

- 没有对应记录时，再回退到 `layout.text`。

这个关联保留了两种信息：`layouts` 决定元素的顺序和位置，表格或图片记录补足元素的内容。

右侧的阅读顺序列表沿用接口返回的元素顺序，不再按坐标重新排序。双栏文档中的“从上到下、从左到右”并不一定等于正确的阅读顺序；在评估阶段自行排序，也会改变正在检查的模型输出。界面同样保留接口返回的版面类型，未知类型也显示出来。

## 把解析坐标映射回页面

有了内容，还需要建立“这条结果来自哪里”的联系。

工具先将不同形式的位置数据归一化为 `[x, y, width, height]`。对于多边形坐标，当前实现取各顶点的最小、最大横纵坐标，得到轴对齐的外接矩形。这个矩形用于定位，不代表精确描绘了倾斜文字的轮廓。

页面显示尺寸会随窗口变化，覆盖框因此采用相对坐标。假设解析页面的宽高为 `pageWidth`、`pageHeight`，核心计算是：



```javascript
const   [  x  ,   y  ,   width  ,   height  ]   =   box;  
  
  const   leftPercent   =   (x   /   pageWidth)   *   100  ;  
  const   topPercent   =   (y   /   pageHeight)   *   100  ;  
  const   widthPercent   =   (width   /   pageWidth)   *   100  ;  
  const   heightPercent   =   (height   /   pageHeight)   *   100  ;
```



源码还对位置和最小显示尺寸做了限制，以便很小的区域仍能看见。PDF 的画布渲染尺寸则另外处理设备像素比，覆盖框跟随页面的显示比例。

但坐标换算成立有一个前提：接口的页面坐标与当前显示的原件必须对应。

### 坐标不匹配时回退到重建版面

`coordinateSpaceMatches()` 会检查解析页面的宽高、旋转角度，以及它与原件的宽高比。当前代码在角度为零、宽高有效，且宽高比相对差异小于 `0.035` 时，才尝试把坐标叠加在原件上。

这是一道启发式检查，并不是精确的图像配准。相同宽高比也无法证明页面没有发生裁切或偏移，因此最终仍需观察覆盖框是否对齐。

检查不通过时，工具使用解析结果的页面尺寸、元素位置和内容重建版面。PDF.js 加载失败，或者浏览器无法直接预览某种文档时，也可以尝试这条路径。

重建版面的用途是检查元素之间的位置关系，不能拿它代替原件来证明识别正确。界面会区分“原件 + 版面”和“PaddleOCR-VL 版面”，让使用者知道自己在看什么。

### 用页码和元素标识连接两侧界面

点击右侧元素时，界面保存目标的页面索引、`layout_id` 和坐标。目标不在当前页，就先切换页面，再更新覆盖框和元素卡片的选中状态。

匹配卡片时同时使用页面索引和 `layout_id`，避免只凭元素标识定位到另一页。行级定位则读取 `span_boxes`，让检查可以从整个段落细化到具体文字行。

这使校样形成一个短路径：发现右侧内容可疑，点击定位，再回到左侧检查原件。使用者不用在一长段 Markdown 和多页 PDF 之间反复查找。

## 保留四种视图，让问题有处可查

阅读顺序、Markdown、MD 源码和 JSON 各自回答不同的问题：

| 视图 | 适合检查的问题 |

| --- | --- |

| 阅读顺序 | 元素顺序是否合理，内容对应原件的哪个区域 |

| Markdown | 文档作为连续内容阅读时，标题、段落和表格怎样呈现 |

| MD 源码 | 渲染异常是否来自 Markdown 本身，链接和标记是什么 |

| JSON | 接口实际返回了哪些字段、坐标和关联关系 |

例如，表格在 Markdown 视图中显示异常时，可以先看源码，再检查 JSON 的表格记录。问题可能来自解析结果，也可能来自展示方式；保留原始输出，才能继续区分。

工具没有增加业务字段提取、正则修正或章节截断。对于接入评估来说，先看清云端返回了什么，才方便决定后续应用需要补哪些处理。

## 运行这个开源示例

准备 Python 3.9 或更高版本，以及有权调用对应文档解析服务的百度智能云 OCR 应用凭据。以下命令用于 macOS 或 Linux：



```bash
git   clone   https://github.com/ZengLiangYi/paddleocr-vl-document-parser-demo.git  
  cd   paddleocr-vl-document-parser-demo  
  cp   .env.example   .env
```



在 `.env` 中填写：



```dotenv
BAIDU_API_KEY  =  你的_API_Key  
  BAIDU_SECRET_KEY  =  你的_Secret_Key
```



然后运行：



```bash
python3   server.py
```



访问 `http://127.0.0.1:8765`，选择文档并开始解析。不要直接双击 `index.html`，因为页面需要调用本地服务。Windows 的配置与启动命令见[仓库 README](https://github.com/ZengLiangYi/paddleocr-vl-document-parser-demo#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)。

当前 Demo 使用 Base64 上传：图片大小检查上限为 10 MiB，其他支持的文档为 50 MiB，没有实现 `file_url` 上传路径。这是工具当前实现的范围；服务支持的格式、额度和计费应以百度官方文档及账户配置为准。

运行时，文档会发送到百度智能云，调用消耗自己的账户额度。服务默认只监听本机，没有应用账号、访问鉴权或生产级安全设计，不应直接作为公开上传服务使用。

如果要复用到业务系统，可以先从 `server.py` 的调用链和 `index.html` 的结果关联逻辑入手。任务持久化、并发限制、上传校验，以及返回内容的安全渲染，需要按实际使用场景补齐。

## 源码与参考

本文依据项目提交 [`e633377`](https://github.com/ZengLiangYi/paddleocr-vl-document-parser-demo/tree/e63337742b9548c70a6aa8b1a43533bcfbacab7c) 中的实现整理，未进行识别准确率或性能评测。

- [后端调用与轮询实现](https://github.com/ZengLiangYi/paddleocr-vl-document-parser-demo/blob/e63337742b9548c70a6aa8b1a43533bcfbacab7c/server.py)

- [前端结果关联、坐标与预览实现](https://github.com/ZengLiangYi/paddleocr-vl-document-parser-demo/blob/e63337742b9548c70a6aa8b1a43533bcfbacab7c/index.html)

- [百度智能云文档解析 API](https://cloud.baidu.com/doc/OCR/s/Qmncwhwdt)

- [Access Token 获取说明](https://ai.baidu.com/ai-doc/REFERENCE/Ck3dwjhhu)

- [本站项目介绍](/projects/paddleocr-vl/)

分享：

##  从文章到代码

到我的 GitHub 看看 ChatCrystal、开发者工具和日常学习记录，欢迎交流实现细节。

[访问我的 GitHub](https://github.com/ZengLiangYi)

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "ZengLiangYi Blog",
  "alternateName": [
    "Zeng Liangyi",
    "ZengLiangYi",
    "zengliangyi.com"
  ],
  "url": "https://zengliangyi.com",
  "description": "曾亮懿的个人网站。记录 AI Native 探索、开发者工具与全栈产品实践。"
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "百度智能云 PaddleOCR-VL 接入实践：一个文档解析校样工具的实现",
  "description": "用 Python 标准库接入百度智能云 PaddleOCR-VL 文档解析 API，串起异步任务、结果下载、原件预览与坐标联动，分享开源校样工具的实现与边界。",
  "url": "https://zengliangyi.com/blog/paddleocr-vl-api-proofreading/",
  "image": "https://zengliangyi.com/images/blog/paddleocr-vl-api-proofreading.svg",
  "datePublished": "2026-09-19T00:00:00.000Z",
  "dateModified": "2026-09-19T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "曾亮懿",
    "url": "https://zengliangyi.com/about"
  },
  "publisher": {
    "@type": "Organization",
    "name": "ZengLiangYi Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://zengliangyi.com/favicon-96x96.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://zengliangyi.com/blog/paddleocr-vl-api-proofreading/"
  }
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "首页",
      "item": "https://zengliangyi.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "文章",
      "item": "https://zengliangyi.com/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "百度智能云 PaddleOCR-VL 接入实践：一个文档解析校样工具的实现",
      "item": "https://zengliangyi.com/blog/paddleocr-vl-api-proofreading/"
    }
  ]
}
```
