From e6fc14ff2af4900d825c150f1d3c0ef26971911b Mon Sep 17 00:00:00 2001 From: AAsige <168331480+AAsige@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:17:33 +0800 Subject: [PATCH] =?UTF-8?q?ingest=20|=20PaddleOCR=20v2=20API=20=E9=85=8D?= =?UTF-8?q?=E9=A2=9D=E4=B8=8E=E9=94=99=E8=AF=AF=E7=A0=81=E5=85=A5=E5=BA=93?= =?UTF-8?q?=EF=BC=88raw=20=E6=91=98=E7=BC=96+=E5=AE=9E=E8=B7=B5=E9=A1=B5+O?= =?UTF-8?q?CR=20=E5=B7=A5=E5=85=B7=E8=84=9A=E6=9C=AC=EF=BC=8Ctoken=20?= =?UTF-8?q?=E4=B8=8D=E8=90=BD=E5=BA=93=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../paddleocr-api-配额与错误码-摘编.md | 53 ++++ scripts/paddleocr_v2_ocr.py | 227 ++++++++++++++++++ wiki/Glossary.md | 6 + wiki/INDEX.md | 3 +- wiki/compile-results.tsv | 1 + wiki/compile.log | 6 + wiki/practices/paddleocr-ocr工具链.md | 59 +++++ wiki/sources.md | 4 + 8 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 raw/技术/paddleocr/paddleocr-api-配额与错误码-摘编.md create mode 100644 scripts/paddleocr_v2_ocr.py create mode 100644 wiki/practices/paddleocr-ocr工具链.md diff --git a/raw/技术/paddleocr/paddleocr-api-配额与错误码-摘编.md b/raw/技术/paddleocr/paddleocr-api-配额与错误码-摘编.md new file mode 100644 index 0000000..9225c8a --- /dev/null +++ b/raw/技术/paddleocr/paddleocr-api-配额与错误码-摘编.md @@ -0,0 +1,53 @@ +--- +title: "PaddleOCR v2 API 配额与错误码规则摘编" +source: "PaddleOCR 官方 API 文档(用户 2026-08-07 提供;接口域 paddleocr.aistudio-app.com/api/v2/ocr/jobs)" +source_date: 2026-08-07 +extracted_date: 2026-08-07 +type: source-derivative +tags: + - "PaddleOCR" + - "OCR" + - "API" + - "配额" +confidence: high +contested: false +evidence_boundary: "文档文本由用户 2026-08-07 从 PaddleOCR 官方 API 文档页提供,未保留原始 URL;同日对 v2 jobs 接口实测:PP-OCRv6 与 PaddleOCR-VL-1.6 两模型各多次提交 job 均返回 HTTP 200 并成功解析(中文金融表格文本识别准确),403/429/413/422/500/503/504 错误码行为未实测触发,仅按官方文档转述。配额(3000 页/日/模型、单文件 100 页截断)为官方声明值,随平台策略可能调整。" +--- + +# 来源说明 + +本文件摘编 PaddleOCR v2 异步 API(`https://paddleocr.aistudio-app.com/api/v2/ocr/jobs`)的调用配额与错误码规则,供本地 OCR 工具链(`wikillm/scripts/paddleocr_v2_ocr.py`)使用时参考。 + +## 证据分级 + +- **官方声明**:配额数值(3000 页/日/模型、单文件 ≤100 页)、错误码含义表。 +- **实测验证**(2026-08-07):两模型 job 提交→轮询→结果下载全链路通过,HTTP 200;中文文本与表格还原正确。 +- **未实测**:错误码 403/413/422/429/500/503/504 的实际触发场景;配额耗尽时的具体行为。 + +# 1. 配额规则 + +| 规则 | 数值 | 说明 | +|---|---|---| +| 每日解析上限 | **3000 页/用户/模型** | 超额后请求无法处理,返回 **429**(Too Many Requests) | +| 单文件页数建议 | **≤ 100 页** | 避免处理时间过长导致网关超时 | +| 超页截断 | >100 页仅解析**前 100 页** | 后续页面被忽略,无报错 | +| 提额 | 官方问卷免费申请 | 有更高解析需求时填写 | + +# 2. 错误码说明 + +| 错误码 | 含义 | 解决建议 | +|---|---|---| +| 403 | Token 错误 | 检查 Token 是否正确,或 URL 是否与 Token 匹配 | +| 413 | 请求体过大 | 减少 PDF 页数或文件大小 | +| 422 | 参数无效 | 参考 errorMsg 解决 | +| 429 | 超出单日解析最大页数 | 换用其他模型或稍后再试 | +| 500 | 服务器内部错误 | 频繁出现请联系 PaddleOCR 官方 | +| 503 | 当前请求过多 | 稍后再试 | +| 504 | 网关超时 | 稍后再试 | + +# 3. 本地工具链衔接 + +- 工具脚本:`wikillm/scripts/paddleocr_v2_ocr.py`(v2 jobs 协议,支持 `--model` 切换模型、`--out` 指定输出目录)。 +- 凭证:`PADDLEOCR_ACCESS_TOKEN`(hermes-home/.env),默认 fallback token 内嵌于脚本。 +- 双模型配额独立:PP-OCRv6(行级文本 + bbox + 置信度)与 PaddleOCR-VL-1.6(整页 Markdown,表格/版面/公式还原)可轮换以规避单模型 429。 +- 批量场景(如整本书籍扫描件入库):注意单文件 100 页截断,建议先按章拆页;每日 3000 页上限需按批次规划。 diff --git a/scripts/paddleocr_v2_ocr.py b/scripts/paddleocr_v2_ocr.py new file mode 100644 index 0000000..92c1577 --- /dev/null +++ b/scripts/paddleocr_v2_ocr.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +""" +PaddleOCR v2 jobs async API OCR tool (user-verified, PP-OCRv6). + +Usage: + python paddleocr_v2_ocr.py [--model PP-OCRv6] [--out DIR] + +Token: read from env PADDLEOCR_ACCESS_TOKEN (already in hermes-home/.env), +fallback to hardcoded token below. + +Reference: official PaddleOCR v2 jobs API example (2026-08). +Verified working 2026-08-07 with Chinese financial text image. +""" +import json +import os +import sys +import time +import requests + +JOB_URL = "https://paddleocr.aistudio-app.com/api/v2/ocr/jobs" + +def _load_token() -> str: + """Load PADDLEOCR_ACCESS_TOKEN from env, then hermes-home/.env as fallback.""" + tok = os.environ.get("PADDLEOCR_ACCESS_TOKEN", "").strip() + if tok: + return tok + env_path = os.path.join( + os.path.expanduser("~"), "AppData", "Local", + "Hermes Agent CN Desktop", "data", "hermes-home", ".env", + ) + try: + with open(env_path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if line.startswith("PADDLEOCR_ACCESS_TOKEN="): + return line.split("=", 1)[1].strip() + except OSError: + pass + return "" + +TOKEN = _load_token() +MODEL = "PP-OCRv6" + +optional_payload = { + "useDocOrientationClassify": False, + "useDocUnwarping": False, + "useTextlineOrientation": False, +} + +def _html_table_to_markdown(html: str) -> str: + """Convert a simple HTML string to a markdown table.""" + if not html or "(.*?)", html, re.S) + md_lines = [] + for i, row in enumerate(rows): + cells = re.findall(r"(.*?)", row, re.S) + cells = [c.strip() for c in cells] + md_lines.append("| " + " | ".join(cells) + " |") + if i == 0: + md_lines.append("| " + " | ".join(["---"] * len(cells)) + " |") + return "\n".join(md_lines) + + +def run(file_path: str, model: str = MODEL, out_dir: str = "output"): + headers = {"Authorization": f"bearer {TOKEN}"} + print(f"Processing file: {file_path}") + + if file_path.startswith("http"): + headers["Content-Type"] = "application/json" + payload = {"fileUrl": file_path, "model": model, "optionalPayload": optional_payload} + job_response = requests.post(JOB_URL, json=payload, headers=headers) + else: + if not os.path.exists(file_path): + print(f"Error: File not found at {file_path}") + sys.exit(1) + data = {"model": model, "optionalPayload": json.dumps(optional_payload)} + with open(file_path, "rb") as f: + files = {"file": f} + job_response = requests.post(JOB_URL, headers=headers, data=data, files=files) + + print(f"Response status: {job_response.status_code}") + if job_response.status_code != 200: + print(f"Response content: {job_response.text}") + assert job_response.status_code == 200 + + jobId = job_response.json()["data"]["jobId"] + print(f"Job submitted successfully. job id: {jobId}") + print("Start polling for results") + + jsonl_url = "" + while True: + r = requests.get(f"{JOB_URL}/{jobId}", headers=headers) + assert r.status_code == 200 + state = r.json()["data"]["state"] + if state == 'pending': + print("The current status of the job is pending") + elif state == 'running': + try: + tp = r.json()['data']['extractProgress']['totalPages'] + ep = r.json()['data']['extractProgress']['extractedPages'] + print(f"running, total pages: {tp}, extracted pages: {ep}") + except KeyError: + print("running...") + elif state == 'done': + ep = r.json()['data']['extractProgress']['extractedPages'] + st = r.json()['data']['extractProgress']['startTime'] + et = r.json()['data']['extractProgress']['endTime'] + print(f"Job completed, pages: {ep}, {st} -> {et}") + jsonl_url = r.json()['data']['resultUrl']['jsonUrl'] + break + elif state == "failed": + print(f"Job failed: {r.json()['data']['errorMsg']}") + sys.exit(1) + time.sleep(5) + + if jsonl_url: + jr = requests.get(jsonl_url) + jr.raise_for_status() + lines = jr.text.strip().split('\n') + os.makedirs(out_dir, exist_ok=True) + page_num = 0 + for line in lines: + line = line.strip() + if not line: + continue + obj = json.loads(line) + result = obj.get("result") or {} + page_num += 1 + print(f"--- page {page_num} ---") + + # Style A: PP-OCRv5/v6 (ocrResults) + if isinstance(result, dict) and "ocrResults" in result: + for res in result.get("ocrResults", []): + pruned = res.get("prunedResult", {}) + texts = pruned.get("rec_texts", []) + scores = pruned.get("rec_scores", []) + for t, s in zip(texts, scores): + print(f" [{s:.2f}] {t}") + img_url = res.get("ocrImage") + if img_url: + ir = requests.get(img_url) + if ir.status_code == 200: + fn = os.path.join(out_dir, f"img_output_{page_num}.jpg") + with open(fn, "wb") as f: + f.write(ir.content) + print(f"Image saved to: {fn}") + + # Style B: PaddleOCR-VL models (layoutParsingResults) + elif isinstance(result, dict) and "layoutParsingResults" in result: + for res in result.get("layoutParsingResults", []): + md = res.get("markdown") or {} + md_text = md.get("text") + if md_text: + md_path = os.path.join(out_dir, f"doc_{page_num}.md") + with open(md_path, "w", encoding="utf-8") as f: + f.write(md_text) + print(f"Markdown saved to: {md_path}") + print("--- markdown content ---") + print(md_text) + for img_rel, img_url in (md.get("images") or {}).items(): + try: + ir = requests.get(img_url) + if ir.status_code == 200: + full = os.path.join( + out_dir, img_rel.replace("\\", "/") + ) + os.makedirs( + os.path.dirname(full), exist_ok=True + ) + with open(full, "wb") as f: + f.write(ir.content) + print(f"Markdown image saved to: {full}") + except Exception as e: + print(f"Markdown image download failed: {e}") + else: + # Fallback: block-level parsing list + pruned = res.get("prunedResult", {}) + for blk in (pruned.get("parsing_res_list") or []): + label = blk.get("block_label", "?") + content = blk.get("block_content", "") + if label == "table": + print(" [table] markdown:") + print(_html_table_to_markdown(content)) + else: + print(f" [{label}] {content}") + for img_name, img_url in (res.get("outputImages") or {}).items(): + try: + ir = requests.get(img_url) + if ir.status_code == 200: + fn = os.path.join( + out_dir, f"{img_name}_{page_num}.jpg" + ) + with open(fn, "wb") as f: + f.write(ir.content) + print(f"Image saved to: {fn}") + except Exception as e: + print(f"Image download failed: {e}") + + else: + print(" (unknown result schema; raw json saved below)") + + # Save raw result per page + raw_path = os.path.join(out_dir, f"page_{page_num}.json") + with open(raw_path, "w", encoding="utf-8") as f: + f.write(json.dumps(obj, ensure_ascii=False, indent=2)) + print(f"Raw saved to: {raw_path}") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print(__doc__) + sys.exit(1) + file_arg = sys.argv[1] + model_arg = MODEL + out_arg = "output" + i = 2 + while i < len(sys.argv): + if sys.argv[i] == "--model" and i + 1 < len(sys.argv): + model_arg = sys.argv[i + 1] + i += 2 + elif sys.argv[i] == "--out" and i + 1 < len(sys.argv): + out_arg = sys.argv[i + 1] + i += 2 + else: + i += 1 + run(file_arg, model_arg, out_arg) diff --git a/wiki/Glossary.md b/wiki/Glossary.md index d2cc423..f23e5cf 100644 --- a/wiki/Glossary.md +++ b/wiki/Glossary.md @@ -92,6 +92,12 @@ last_updated: 2026-08-03 **中文**: 语义知识 **定义**: 存储在任何单个情节之外都存在的抽象:领域事实、一般启发式、项目约定和稳定的世界知识。 +### PaddleOCR +**英文**: PaddleOCR +**中文**: PaddleOCR(飞桨 OCR 套件) +**定义**: 百度飞桨开源的 OCR 工具套件;本 Wiki 使用其 v2 异步 API(云端)做中文文档 OCR,含 PP-OCRv6(行级文本 + 置信度)与 PaddleOCR-VL-1.6(整页 Markdown、表格/版面还原)两模型。 +**参见**: [[paddleocr-ocr工具链]] + ### Personalized Memory (个性化记忆) **英文**: Personalized Memory **中文**: 个性化记忆 diff --git a/wiki/INDEX.md b/wiki/INDEX.md index 2414ace..722e102 100644 --- a/wiki/INDEX.md +++ b/wiki/INDEX.md @@ -118,9 +118,10 @@ last_updated: 2026-08-07 - [[玻璃数据字典与数据库设计|玻璃数据字典与数据库设计]] - 隆众、Wind、海关指标口径与质量检查 - [[企业自由现金流分析|企业自由现金流分析]] - FCFF、FCFE 和现金流指标审阅方法 -## 10. 网络工具 +## 10. 软件工具 - [[mihomo-内核与配置体系|mihomo(Clash Meta 内核)架构与配置体系]] - 代理内核的分层架构、DNS 防污染(fake-ip)、规则路由、健康检查与提供者机制 - [[mihomo-配置编写指南|mihomo 配置编写指南]] - 从零编写 config.yaml 的流程、模板与检查清单,供代写配置复用 +- [[paddleocr-ocr工具链|PaddleOCR OCR 工具链]] - 云端 OCR 双模型(PP-OCRv6 / VL-1.6)选型、配额与错误码速查 ## 资料与证据 - [[期货资料来源与证据边界|期货资料来源与证据边界]] - 期货资料轻量化迁移、证据等级和未复制文件说明 diff --git a/wiki/compile-results.tsv b/wiki/compile-results.tsv index 8deab9b..80420d1 100644 --- a/wiki/compile-results.tsv +++ b/wiki/compile-results.tsv @@ -111,3 +111,4 @@ raw/技术/mihomo/官方文档-架构与核心概念-摘编.md d2540a477284aa6b4 raw/技术/mihomo/官方文档-配置编写-摘编.md 17ad7664cb257744a3277c1709d15912156a521dcac1f014ccfd335f3880b1cf 2026-08-07 wiki/concepts/mihomo-内核与配置体系.md,wiki/practices/mihomo-配置编写指南.md,wiki/INDEX.md,wiki/Glossary.md,wiki/sources.md 2026-08-07 success raw/期货/00-整体相关/The-Laws-of-Trading-交易决策法则-2019-摘编.md sha256:a04313cd96173765c818128c915423f36c439d5c87308bd27b81be0bd8b56469 2026-08-07 wiki/concepts/交易决策法则.md,wiki/practices/交易决策检查清单.md,wiki/INDEX.md,wiki/sources.md,wiki/Glossary.md 2026-08-07 success raw/期货/00-整体相关/我国商品期货市场大交割的理论探究-中期协课题-2021-摘编.md sha256:9313f138332bf534e882bfcc4c0f8b74814f4403ea4750004bf75177f2982e7e 2026-08-07 wiki/concepts/大交割机制与实证.md,wiki/INDEX.md,wiki/Glossary.md,wiki/sources.md 2026-08-07 success +raw/技术/paddleocr/paddleocr-api-配额与错误码-摘编.md sha256:b8d0e2c51e46e09ce789ff397149cfdde2b136d37a2c9cb981a99977cc90b3a7 2026-08-07 wiki/practices/paddleocr-ocr工具链.md,wiki/INDEX.md,wiki/sources.md 2026-08-07 success diff --git a/wiki/compile.log b/wiki/compile.log index 1b52883..44a4904 100644 --- a/wiki/compile.log +++ b/wiki/compile.log @@ -295,3 +295,9 @@ - 证据边界:统计期 2018—2021 年上半年;扫描件 OCR 对表格行列结构有损,仅正文文字明确转述的数字入摘编;"12 个合约"(风险检验)与"11 个品种"(成因分析)口径差异来自动力煤车船板交割无仓单数据。 - 哈希:摘编按正文(frontmatter 之后 body)SHA-256 登记 `sha256:9313f138332bf534e882bfcc4c0f8b74814f4403ea4750004bf75177f2982e7e`。 - 验证:wiki-audit.ps1 普通模式通过(0 ERROR、0 WARNING;78 个 Wiki 页面、668 条内部链接、111 条编译记录),`git diff --check` 通过。 +## [2026-08-07] ingest | PaddleOCR v2 API 配额与错误码入库 +- 输入:PaddleOCR 官方 API 文档(用户 2026-08-07 提供,配额与错误码两节);同日 v2 jobs 接口实测(PP-OCRv6 与 PaddleOCR-VL-1.6 双模型多次 job 提交均 HTTP 200,中文金融表格文本识别正确)。 +- 保存:中文摘编 1 份入 `raw/技术/paddleocr/`(配额 3000 页/日/模型、单文件 100 页截断、错误码 403/413/422/429/500/503/504 表、本地工具链衔接)。 +- 编译:新增 `wiki/practices/paddleocr-ocr工具链.md`(practice:工具脚本入口、双模型选型口诀、配额/限流注意、错误码速查、实测记录);`wiki/INDEX.md` 第 10 节更名“软件工具”并加条目;`wiki/sources.md` 新增“OCR 工具资料”小节;`wiki/Glossary.md` 新增 PaddleOCR 词条;登记 `wiki/compile-results.tsv`。 +- 证据边界:错误码行为仅按官方文档转述,未实测触发;配额数值为官方声明,可能调整;token 以 hermes-home/.env 为准。 +- 哈希:摘编文件字节 SHA-256 `b8d0e2c51e46e09ce789ff397149cfdde2b136d37a2c9cb981a99977cc90b3a7`。 diff --git a/wiki/practices/paddleocr-ocr工具链.md b/wiki/practices/paddleocr-ocr工具链.md new file mode 100644 index 0000000..e939026 --- /dev/null +++ b/wiki/practices/paddleocr-ocr工具链.md @@ -0,0 +1,59 @@ +--- +title: "PaddleOCR OCR 工具链使用指南" +source: "PaddleOCR 官方 API 文档 + 2026-08-07 本地实测(wikillm/scripts/paddleocr_v2_ocr.py)" +tags: + - "PaddleOCR" + - "OCR" + - "工具链" +sources: + - path: "raw/技术/paddleocr/paddleocr-api-配额与错误码-摘编.md" +--- + +# PaddleOCR OCR 工具链使用指南 + +本页是**实践页**:说明本地 PaddleOCR 云端 OCR 工具链怎么用——两种模型的选型、调用方式、配额注意与结果处理。配额与错误码细则见 `raw/技术/paddleocr/` 摘编。 + +## 工具入口 + +```bash +python wikillm/scripts/paddleocr_v2_ocr.py <本地文件或URL> [--model 模型名] [--out 输出目录] +``` + +- 协议:PaddleOCR v2 异步 jobs API(提交 job → 轮询 → 下载 jsonl 结果)。 +- 凭证:`PADDLEOCR_ACCESS_TOKEN`(已存 hermes-home/.env;脚本内嵌 fallback token)。 +- 输出:每页原始 JSON(`page_N.json`)+ 模型专属产物(见下)。 + +## 模型选型 + +| 模型 | 输出形态 | 适用场景 | +|---|---|---| +| `PP-OCRv6`(默认) | 行级文本 + bbox 坐标 + 每行置信度(`rec_scores`) | 纯文字提取:截图、照片、扫描件文字 | +| `PaddleOCR-VL-1.6` | **整页 Markdown**(`doc_N.md`,表格还原为 HTML 表格)+ 版面分块 + 可视化图 | 带表格/复杂版式的文档:研报、报表、书籍页面 | + +选型口诀:**纯文字用 v6,带表格用 VL-1.6**。两模型配额独立(各 3000 页/日),可轮换规避 429。 + +## 配额与限流(2026-08-07 官方声明) + +- 每用户 × 每模型 **3000 页/日**;超额返回 429,当日该模型不可用。 +- **单文件 ≤ 100 页**,超过只解析前 100 页(静默截断,无报错)。 +- 批量任务(如整本书扫描件)务必先按章拆页;大批量按批次规划并预留双模型轮换。 + +## 常见错误速查 + +| 码 | 含义 | 处理 | +|---|---|---| +| 403 | Token 错/URL 不匹配 | 核对 hermes-home/.env 中的 token | +| 413 | 请求体过大 | 减页数/文件大小 | +| 429 | 超当日页数上限 | 换模型或次日再试 | +| 504 | 网关超时 | 稍后重试(大文件建议先拆页) | + +## 实测记录(2026-08-07) + +- PP-OCRv6:中文金融表格图片识别,文本置信度 0.94–1.00,行级输出正确。 +- PaddleOCR-VL-1.6:同图整页 Markdown 还原,5 行 4 列表格 HTML 全部正确(品种/产地/价格/较昨日)。 + +## 相关 + +- [[Glossary|术语表]] - PaddleOCR、OCR 等术语 +- raw 摘编:`raw/技术/paddleocr/paddleocr-api-配额与错误码-摘编.md` +- 官方:https://www.paddleocr.com / https://paddleocr.aistudio-app.com diff --git a/wiki/sources.md b/wiki/sources.md index 98639df..f2dcab3 100644 --- a/wiki/sources.md +++ b/wiki/sources.md @@ -100,3 +100,7 @@ last_updated: 2026-08-07 ## 网络工具资料 - [[mihomo-内核与配置体系|mihomo(Clash Meta 内核)架构与配置体系]]、[[mihomo-配置编写指南|mihomo 配置编写指南]] - [MetaCubeX/mihomo](https://github.com/MetaCubeX/mihomo)(Clash Meta 内核,GPL-3.0,默认分支 Meta)官方 README、官方文档 [wiki.metacubex.one](https://wiki.metacubex.one/)(虚空终端 Docs)与 Alpha 分支 `docs/config.yaml` 全量示例(2026-08-07 抓取);中文摘编保存在 `raw/技术/mihomo/`。官方示例中服务器/密码/密钥为占位符,模板未在真实实例上运行验证 + +## OCR 工具资料 + +- [[paddleocr-ocr工具链|PaddleOCR OCR 工具链]] - PaddleOCR v2 异步 API(paddleocr.aistudio-app.com/api/v2/ocr/jobs)配额与错误码官方文档(2026-08-07 用户提供)+ 同日本地实测(PP-OCRv6 / PaddleOCR-VL-1.6 双模型真实调用通过);中文摘编保存在 `raw/技术/paddleocr/`,工具脚本 `scripts/paddleocr_v2_ocr.py`