# sali-file-parser **Repository Path**: salierime/sali-file-parser ## Basic Information - **Project Name**: sali-file-parser - **Description**: 自研高效文档解析工具。可用于RAG系统入库。codex+superpower - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-26 - **Last Updated**: 2026-06-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: RAG ## README # sali-file parser 这个项目只做一件事:提供一个文件解析接口,输入公开 `file_url` 或上传文件,输出统一的解析结果。 返回结果的核心字段是 `text`,固定为按页数组: ```json { "filename": "example.pdf", "extension": "pdf", "source_type": "url", "text": [ { "page_no": 1, "page_content": "Page 1 text" }, { "page_no": 2, "page_content": "Page 2 text" } ], "warnings": [] } ``` ## 一、支持解析的文件类型和对应的解析思路 | 文件类型 | Extensions | Primary path | Fallback path | | --- | --- | --- | --- | | 纯文本 | `txt`, `vtt` | 直接按字节解码文本 | 无 | | Markdown | `md`, `markdown`, `mdx` | 直接读取 Markdown 文本 | 无 | | HTML | `html`, `htm` | `BeautifulSoup` 提取正文文本 | 无 | | JSON | `json` | 反序列化后格式化输出 | 无 | | YAML | `yaml`, `yml` | `PyYAML` 读取后格式化输出 | 无 | | XML | `xml` | `BeautifulSoup(..., "xml")` 提取文本 | 无 | | CSV | `csv` | `pandas.read_csv()` 读取,再转统一表格文本 | 编码异常时改用 `gb18030` 重试 | | Excel 新格式 | `xlsx` | `openpyxl` 读取 workbook、sheet、超链接、单元格内容,首行表头拼入每一行 | 补充单元格图片 OCR;`read_only` 无结果时回退标准模式 | | Excel 旧格式 | `xls` | `pandas.ExcelFile()` 按 sheet 解析,首行表头拼入每一行 | 失败时回退 Tika;如能解包图片,再补图片 OCR | | WPS 表格 | `et` | Tika 抽文本 | Tika 无文本时回退 `pandas.ExcelFile()` | | 图片 | `jpg`, `jpeg`, `png`, `webp`, `bmp`, `gif`, `tiff` | 直接走 `Qwen-VL` / VL OCR 单图识别 | 无 | | PDF | `pdf` | `PyMuPDF` 提取文本、表格、页内图片 | 页文本为空或疑似乱码时整页 `Qwen-VL` OCR;页内图片单独 OCR 后拼回页面 | | Word 新格式 | `docx` | `python-docx` 读取段落、表格、超链接、内嵌图片,表格首行表头拼入后续每行 | 失败或文本不足时回退 Tika,再回退 `unstructured` | | Word 旧格式 / WPS 文字 | `doc`, `wps` | Tika 抽文本 | 失败时回退 `unstructured` | | PPTX | `pptx` | 若 MinerU 已配置,先走 MinerU,读取结果 zip 中的 Markdown 文本 | Markdown 图片再用 `Qwen-VL` OCR 替换;MinerU 不可用时回退 `python-pptx`,再回退 Tika,再回退 `unstructured` | | PPT 旧格式 | `ppt` | Tika 抽文本 | 失败时回退 `unstructured` | | 其他文档 | `epub`, `eml`, `msg` | `unstructured` 对应 partition 解析 | 无 | ### MinerU 相关流程说明 - 当前项目里,只有 `pptx` 会优先走 MinerU。 - 触发条件是同时满足: - `PARSER_MINERU_ENABLED=true` - `PARSER_MINERU_API_BASE` 已配置 - `PARSER_MINERU_API_KEY` 已配置 - 满足条件时,`pptx` 的主链路是: - 提交 MinerU 异步任务 - 轮询任务结果 - 下载结果 zip - 读取 zip 里真正的 Markdown / 文本结果 - 如果 Markdown 里有图片引用,再交给 `Qwen-VL` 做 OCR,并把 OCR 文本替换回对应位置 - 不满足条件时,`pptx` 不会走 MinerU,而是直接走 `python-pptx` 本地解析链路。 ### Qwen-VL 相关流程说明 - 触发前提是: - OCR 总开关有效 - `PARSER_VL_MODEL_URL` 已配置 - 当前项目里,`Qwen-VL` 会用于这些场景: - 图片文件本身:`jpg`、`jpeg`、`png`、`webp`、`bmp`、`gif`、`tiff` - `pdf` 页内提取出的图片 - `pdf` 整页文本为空或疑似乱码时的整页 OCR fallback - `docx` 内嵌图片 - `xlsx` / `xls` / `et` 中提取出的图片 - `pptx` 通过 `python-pptx` 解析到的图片 - `pptx` 走 MinerU 后,zip 结果 Markdown 里引用到的图片 - 不满足 OCR 配置时: - 图片文件会直接报错,因为图片类型本身就依赖 `Qwen-VL` - 其他文档类型只是失去图片 OCR 和整页 OCR 补充能力,不一定整单失败 ## 二、依赖、启动、调接口、返回结果示例 ### 装依赖 ```bash python -m pip install -r requirements.txt ``` 如果你需要更完整的 `unstructured` 文档分区能力,再按需补: ```bash python -m pip install "unstructured[all-docs]" ``` 说明: - `requirements.txt` 已经锁定了当前项目直接依赖的版本,包括 `tika` 和基础版 `unstructured`。 - `tika` 主要用于 `doc`、`wps`、`ppt`、`xls`、`et` 以及部分回退链路。 - `unstructured` 主要用于 `epub`、`eml`、`msg` 和部分 Office 回退链路;`unstructured[all-docs]` 适合需要更完整本地分区能力的场景。 - `Qwen-VL` 和 MinerU 都是外部服务,本项目只负责调用。 ### 运行时最小配置 `.env` 建议只保留这类部署参数: ```bash PARSER_HOST=0.0.0.0 PARSER_PORT=7010 PARSER_MAX_FILE_SIZE_MB=100 PARSER_MAX_PDF_PAGES=300 PARSER_REQUEST_TIMEOUT_SECONDS=300 ``` `Qwen-VL`: ```bash PARSER_VL_MODEL_URL=https://your-qwen-vl-endpoint/v1/chat/completions PARSER_VL_MODEL=qwen-vl PARSER_VL_API_KEY=your-qwen-api-key ``` MinerU: ```bash PARSER_MINERU_ENABLED=true PARSER_MINERU_API_BASE=https://mineru.net/api/v4 PARSER_MINERU_API_KEY=your-mineru-api-key PARSER_MINERU_TIMEOUT_SECONDS=300 ``` Tika: ```bash PARSER_TIKA_PORT=9998 ``` 日志: ```bash PARSER_LOG_LEVEL=INFO PARSER_LOG_RETENTION_DAYS=7 PARSER_LOG_DIR=logs ``` 说明: - `PARSER_REQUEST_TIMEOUT_SECONDS` 同时复用于文件下载、VL 请求和部分解析链路的超时控制。 - `PARSER_TIKA_PORT` 是当前唯一需要暴露的 Tika 配置;服务内部会按这个端口找本地 Tika。 - `PARSER_VL_API_KEY` 用于 `Qwen-VL` 鉴权。 - 日志默认同时输出到控制台和 `logs/` 目录下的按天滚动文件,并自动清理超过保留天数的旧日志。 ### 怎么启动 ```bash python app.py --host 0.0.0.0 --port 7010 ``` 健康检查: ```bash curl http://127.0.0.1:7010/health curl http://127.0.0.1:7010/config ``` ### 调接口 `POST /parse` 支持两种输入方式,只能二选一: 1. 上传文件 ```bash curl -X POST http://127.0.0.1:7010/parse \ -F "file=@/path/to/example.pdf" ``` 2. 公开 URL ```bash curl -X POST http://127.0.0.1:7010/parse \ -H "Content-Type: application/json" \ -d "{\"file_url\":\"https://example.com/example.pdf\"}" ``` 也支持在 `multipart/form-data` 里传 `file_url`: ```bash curl -X POST http://127.0.0.1:7010/parse \ -F "file_url=https://example.com/example.pdf" ``` 调用约束: - `file` 和 `file_url` 必须二选一。 - `file_url` 必须是 public `http` or `https` URL。 - 当前只支持公开 URL,不支持私网地址、`localhost`、回环地址或需要额外鉴权才能访问的地址。 - `file_url` 的 URL 路径里必须能拿到文件名和扩展名。 ### 返回结果 成功响应: ```json { "filename": "example.pdf", "extension": "pdf", "source_type": "url", "text": [ { "page_no": 1, "page_content": "Page 1 text" } ], "warnings": [] } ``` 字段说明: - `filename`:解析时使用的文件名。 - `extension`:最终识别到的扩展名。 - `source_type`:`upload` 或 `url`。 - `text`:固定结构 `[{ "page_no": 1, "page_content": "..." }, ...]`。 - `warnings`:非致命告警列表,表示解析已成功返回,但中间发生过降级、回退、局部失败或能力缺失。 `warnings` 常见含义: - 某个回退链路失败了,但后续备用链路成功了。 - 某一页、某张图片、某个 sheet 局部提取失败,但整体仍有结果。 - PDF 页数过多被截断。 - Tika、MinerU、OCR 之类的外部能力暂时不可用,系统自动降级到其他实现。 失败响应: ```json { "error_type": "unsupported_input", "error": "Unsupported file extension: exe" } ``` 常见错误类型: - `request_validation_error` - `url_resolution_error` - `url_download_error` - `url_not_found` - `file_too_large` - `unsupported_input` - `parse_execution_error` - `internal_error` ## 三、当前项目有哪些生产级的实现细节梳理清楚 ### 1. 图片 OCR 先做过滤,再调用 Qwen-VL - 所有走 OCR 的图片都会先过 `is_valid_image()`。 - 过小图、纯色图、坏图不会继续触发 `Qwen-VL`。 - 这样能减少 PDF、PPT、Excel、Word 里的空白小图和装饰图带来的无效请求。 ### 2. Qwen-VL 结果会清洗、去重、缓存 - OCR 结果会去掉 Markdown fence 和 `[NO_TEXT_DETECTED]`。 - 重复尾段、重复行、重复字符会做压缩去重。 - `warm_cache()` 会按图片字节 hash 去重,同一张图不会被重复 OCR。 ### 3. PDF 不是只抽纯文本,而是按版面合并 - 先抽表格,再抽页内图片 OCR,再抽普通文本块。 - 表格框、图片框里的重复文本会跳过,最后按纵向顺序合并。 - 页文本为空或疑似乱码时,会触发整页 OCR fallback。 ### 4. Excel 和 DOCX 表格都会把首行表头拼进每一行 - 表格输出会改写成类似 `"表头":"单元格值"` 的结构,更适合 RAG 切块和召回。 - `xlsx`、`xls`、`docx` 都按这套规则输出。 - 如果首行有横向合并表头,右侧空表头会继承左侧已有表头。 ### 5. Excel 图片单元格和旧格式回退单独处理 - `xlsx` 会识别 `DISPIMG` 这类图片单元格公式,不把公式原文当正文输出。 - 如果 OCR 能识别出图片文字,会把文字补回对应单元格位置。 - `xlsx` 的 `read_only` 模式无有效数据时会自动重试标准模式。 - `xls` / `et` 失败时会转向 Tika,尽量避免老格式直接整单失败。 ### 6. DOCX、PPTX、MinerU 图片只在识别出有效文本后回填 - `docx` 和 `python-pptx` 路径下,图片 OCR 为空时不会硬塞空占位。 - MinerU 结果里如果 Markdown 仍保留图片引用,会再交给 `Qwen-VL` OCR,并把结果回填到对应位置。 - 最终目标是尽量返回纯文本结果,而不是图片链接。 ### 7. 外部能力失败时优先降级,不轻易整单失败 - Tika、MinerU、OCR 的失败很多时候会先进入 `warnings`,然后继续走其他可用路径。 - 真正不支持的扩展名才会返回 `unsupported_input`。 - 真正中断主链路的解析异常才会返回 `parse_execution_error`。 ### 8. 后台持久化日志记录请求处理进度 - 每次 `/parse` 请求都会生成 `request_id`,用来串联整条解析链路日志。 - 日志会同时写控制台和 `logs/parser-service.log`,按天滚动,并按 `PARSER_LOG_RETENTION_DAYS` 自动清理旧文件。 - 请求级日志会记录:请求开始、输入加载、下载开始/完成、解析完成、失败原因、总耗时。 - `pdf` 会记录:第几页 / 总页数、当前页表格数、当前页图片数、当前页第几张图片。 - `xlsx` / `xls` 会记录:第几个 sheet、sheet 名称、行列数、sheet 图片数、当前第几张图片。 - `docx` 会记录:第几个表格、表格行列数、表格图片单元格数,以及当前第几张图片。 - `pptx` 会记录:第几个 slide、slide 中表格数、图片数、当前第几张图片。 - `MinerU` 会记录:任务提交、轮询、结果下载、zip 图片替换等阶段进度。