Skip to content

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

158 Commits

Folders and files

Repository files navigation

PDF 转 Word

本地版拿显卡换到了什么

一句话:云端 API 买不到 MinerU 最好的那个后端。

本地版跑的是官方的 hybrid-engine,解析强度开到 effort=high。这两个开关云端 API 都没有 —— 它只认五个参数:model_version(只能填 vlm 或 pipeline)、 language、is_ocr、enable_formula、enable_table。往请求里塞 backend=hybrid-engine 或 effort=high 不报错,但服务端直接忽略(2026-09-09 实测:带不带这两个参数,产出的 word/document.xml 哈希一模一样)。

差别落在并排的配图上。讲义里「甲、乙、丙」三张图排一行,或者选择题的 A/B/C/D 四张图排一行,这是常态。effort=high 会打开图片分析,把这一行当成一个 完整的图形区域整体切下来,进了 Word 还是并排的;medium 和云端做不到,只能一 张图算一块,四个选项变成四行,一道题占掉大半页。

effort 为什么关键,MinerU 官方源码 mineru/backend/hybrid/hybrid_analyze.py 里写得很直白:

def _resolve_effective_image_analysis(effort, image_analysis):
    """medium 强制关闭以保持快速路径。"""
    if effort == "medium":
        return False
    return image_analysis

是无条件关掉,传 True 也没用。而云端连 hybrid 这个后端都不提供。

同一份 21 页物理讲义,两边实测:

本地 hybrid-engine + high 云端 vlm
图片块 29 40(并排图被拆散)
原生公式 147 192
填空线 40 42

公式和填空线云端多一些。但图片块那一栏是本质差别:多出来的 11 块不是多认了 11 张图,是原来并排的一张被切成了好几张。

另一条:本地版全程不联网,文件不出这台电脑。学生信息、还没考的卷子, 这类材料只能走本地版。

怎么选:讲义、试卷这类版式要紧、手头又有 N 卡的,用这个;没显卡,或者只要 把内容捞成可编辑的文字和公式、不在乎图排成几行, 云端版 够用而且省事 —— 不用下 7.4 GB,见下面那个框。

🌩 没有 N 卡、或者不想下 7.4 GB?用云端版

teach-studio · PDF 转 Word 云端版

同一套出 Word 的管线,只把识别那一步放到了 MinerU 的云端。 下面「四件事得先知道」里最挡人的两条,那边都不需要:

本地版(这个) 云端版
显卡 必须有 NVIDIA 独显 不用
首次下载 7.4 GB 不用
依赖体积 5.1 GB 95 MB
并排的配图 原样保留 拆成一张一行

(Office 两边都不用装了 —— 2026-09-09 起本地版也把那个转换文件 打进包里了。门槛上的差别只剩显卡和下载体积;但转出来的东西 不一样,见开头「本地版拿显卡换到了什么」。)

门槛从「N 卡 + 7.4 GB」降到「一个免费 token」。

代价说在前面:文件要上传到 MinerU 的服务器——不能外传的材料 请继续用本地版;每个账号每天有额度限制(可以多个号叠加);还有 并排的配图会被拆散,讲义、试卷这类版式要紧的材料建议留在本地版。

把 PDF 讲义转成 Word,文字、公式、表格、图片都是原生对象,不是截图。 公式转出来是 Word 原生公式,可以直接编辑、可以被搜索。

正文用宋体 + Times New Roman,标题靠字号加粗区分。选这两个是因为 任何 Windows 都自带(Calibri 那些要装 Office 才有,而收文件的学生家长 不一定装),而且跟公式的 Cambria Math 同为衬线体,一行里插公式不打架。

终末诗篇工作台(edu_book_generator)的专精版——那边做「PDF → 题库 → 重新组卷」, 这边只做「PDF → Word」这一段。

最新发行版:v0.3.1


给使用者

下载 PDF2Word-Setup-v0.3.1.exe(291 MB),双击 → 选文件夹 → 解压完成 → 双击里面的 PDF转Word.exe。

四件事得先知道:

  1. 必须有 NVIDIA 独立显卡。 这软件只用显卡转换,不用 CPU 顶替 (理由见下面「没有显卡能用吗」)。没有 N 卡的机器装了也转不了, 点开始会当场报错,不会让你白等。
  2. 必须装微软 Office 2026-09-09 起不用了。公式要用的 MML2OMML.XSL 已经随软件打包(runtime/xsl/),装完就能用, 不用装 Office,也不用装 WPS。
  3. 别装在 C:\Program Files。 所有文件都留在软件自己的文件夹里, 而那个位置普通用户写不了文件。 路径里可以有中文、空格和括号——D:\我的软件\PDF转Word (正式版) 这种没问题(见下面「装在中文路径里可以吗」)。
  4. 第一次打开要下约 7.4 GB:GPU 运行库 2.8 GB + 识别模型 4.6 GB。 两件事在同一个流程里,只等一次。软件会实测各下载源的真实速度, 最快的默认选中。

装完连同下载的东西一共占硬盘约 10 GB。不想用了直接删掉整个文件夹, 模型、日志、缓存跟着一起走,不写注册表。转好的 Word 不受影响。

删不掉的只有三样,都不是这个软件私有的:

  • 微软 C++ 运行库 —— 装在系统里,控制面板可卸载
  • pip 的下载缓存 —— 在 %LOCALAPPDATA%\pip\cache,别的 Python 程序也在用同一个
  • pip 装到一半中断留下的残骸 —— 在 %TEMP%,正常装完 pip 自己会 清,进程被杀 / 断网 / 关机就会永久留下,一次可能是几个 GB

后两样在软件里「关于 → 环境检测」都看得到有多大、里面是什么,想清就清。

更新:软件里点「检查更新」→ 自动下载安装(约 0.5 MB)→ 点「立即重启」。 不用再来 GitHub。

界面

620×440 的小窗口,一屏干完:

┌ 添加文件  添加文件夹  移除全部        输出:跟原 PDF 放一起  更改 ┐ 工具条
├──────────────────────────────────────────────────────────────┤
│ ☑ 解不等式.pdf                                        10 页  │
│ ☑ 电场中力的性质.pdf          第 7、12 页没有文字层    23 页  │ 主体
│ ☑ 圆锥曲线综合.pdf                                     8 页  │ 永远是
│ ● 加密的文件.pdf              这个 PDF 加了密码,打不开       │ 这张表
├──────────────────────────────────────────────────────────────┤
│ ● 显卡 ✓ · 公式 ✓   历史 关于    选中 9 份 · 128 页 [开始转换]│ 状态栏
└──────────────────────────────────────────────────────────────┘

转换时这张表原地变,不跳屏:顶部换成「还要约 14 分 23 秒」加总进度条, 每行显示自己的状态(绿点完成、蓝点正在转、灰点排队),转完的行就地长出 「打开 / 文件夹」两个链接。

环境自检压在状态栏,只有必须让用户拿主意时才拦主区,按严重度排: 安装目录不可写 → 公式引擎缺失 → 转换引擎缺失 → 缺 C++ 运行库 → 缺 GPU 运行库 → 显卡不达标。 前五种硬拦(但都给出路,缺运行库的两屏都有「现在就装」按钮); 显卡不达标是软拦,给「仍然继续」和「退出」让用户自己选。

不给黑盒

下载(GPU 运行库、模型、更新包)显示三样东西:进度条(带真实字节数)、 跑的那条完整命令、滚动的输出日志。失败了日志留在原地不跳走—— 2026-09-02 真机上就是靠这个才定位到「torch 的 c10.dll 加载不了」, 而那跟网络毫无关系。

转换的日志在状态栏一个「日志」按钮后面,点开覆盖主区,顶上的剩余 时间和总进度条保留。不做成固定一块是因为转一批书时文件列表本来就长。 全量日志落在 logs/convert.log(含进度刷屏),界面上那份滤掉了 tqdm。

停止是当场生效的:转换中点停止会直接杀掉 MinerU 的进程树,不用等 这一份转完。(09-02 之前只在两份 PDF 之间检查,只转一份的话等于没有。)

检查更新以前是这里唯一的例外:前端发一个请求然后干等,用户看不到 试了哪些镜像、谁通谁不通。09-03 改成并发查多条线路,结果里多一行

经 GitHub 官方 · 6/6 条可用   线路 ▾

点开是完整的线路表,行本身就是选择器——平时用「自动(最快的)」, 想手动指一条也行,因为最快的未必最稳。

查版本是六条一起发,每条最多等 3.5 秒。界面上是 4 秒倒计时,数到 0 那一刻结果一起出现——结果早到也压着,不让倒计时自己转、结果自己蹦 (小蔡 2026-09-05 定的)。代价是网络好的时候本来 1.1 秒能出结果,也要等满 4 秒。

线路自己超时的那条写「超时」。只有线程卡死(DNS 解析卡住那种, socket 超时管不着)才会拖过 3.8 秒的兜底窗口,那条写「未测」, 不写「连不上」——没验证过的结论不报,这时上面那行会变成 「5 条已通过,1 条未测」。

速度那一列没测过就是空的(—),不拿别的数字顶替。测速有两个触发点, 行为不一样:

  • 手动点「测下载速度」 —— 表格里填上实测速度,并自动选中最快的那条 (在此之前测速只重排显示、下载走的还是名单第一条,等于测了个寂寞)
  • 点「更新」时没手动指定线路 —— 后端自己并发测一次挑最快的下,界面显示 「正在挑最快的线路…」。它不动你的选择,线路表里仍然停在「自动」

这两件事分得很开是有原因的: 2026-09-03 实测同一批线路,响应最快的直连(903 毫秒)下载只排第四 (725 KB/s),而下载最快的 ghproxy.net(795 KB/s)连 API 都代理不了。 响应快和下载快几乎无关,拿延迟排序会让人选错。

下载更新包时一条线路挂了会自动换下一条。 以前只试一条,挂了整个更新 就失败——而且那句「再点一次会换个源重试」是不成立的:更新包只有 0.5 MB, 走的是「小包不值当测速」的捷径,每次都落到名单第一条,重试还是它。 2026-09-03 发这一版当天就撞上:ghfast.top 的 SSL 挂了,自动更新当场断掉, 而旁边六条是通的。唯一不换源的情况是校验没过——内容跟 GitHub 上的原件 对不上属于安全事件,换源等于在一堆不可信的源里找一个碰巧对得上的。

🔴 只有「会换下一条」还不够(2026-09-05 补)。 换是串行换的,而 _fetch_one 的连接超时是 30 秒 —— 第一条连不上就得干等满半分钟才轮到 第二条,六条最坏 180 秒。用户看到的是「点了更新,半天不出进度条」。 所以现在点更新时若没手动指定线路,先并发测一次速(一两秒)再下: 测速本身就是并发探测,一次就能同时探明哪条通、哪条不通,把「等第一条 超时」整个绕开。多花两秒,省掉最坏半分钟。

转换进行中,「检查更新」是禁用的(按钮还在,只是点不动)。更新包覆盖 的正是 pipeline/*.py,而转换每处理一份 PDF 就新起一次 MinerU 子进程—— 转到一半换掉代码,后面几份读到的就是新代码;装完还要重启,一重启这批 就全废了。转完自己会亮回来。

转出来是什么样

同一份讲义(10 页数学)实测:

公式 213 个   全部是 Word 原生公式对象,可编辑可搜索,不是图片
表格 2 个     含合并单元格,有边框
图 4 张       按原书尺寸还原(4.45 / 1.29 / 1.61 / 1.25 英寸)
用时 252 秒   其中 MinerU 提取占绝大部分

进度:一条百分比 + 一个倒计时,各管各的

顶上那条百分比按「阶段权重」走,不跟倒计时挂钩。 各阶段耗时差 100 倍 ——同一次转换的真实时间线:

分析版面    0→10     16 秒
逐页识别    0→10    122 秒   ← 占 42%
识别公式和文字 0→202  244 秒   ← 占 49%
定位文字    0→188   249 秒
处理页面    0→10    250 秒   ← 只要 1 秒

所以不能按「第几个阶段」平分,按各阶段的实际耗时占比折算。只增不减, 真转完之前最多 99%——宁可最后跳一下,也不让它退回去。

倒计时是另一套:开工时估一次,之后老老实实倒数,中途不重算。 页数在 体检时就知道,速率有出厂值(14.2 秒/页 + 0.82 秒/元素),转完一份会用 真实耗时更新它,越用越准。估不出来时显示「正在估算…」——宁可不显示, 也不给一个编的数。

阶段内的 x/y:只有两轮识别带数字

MinerU 换阶段时总数会换单位——前一个按页(0→11),下一个按检测到的文本块 (0→247)。用户看到「5/11」变成「5/247」,第一反应是出 bug 了。

所以默认只给阶段名。例外是那两轮识别,因为名字里就写着单位: 「逐页识别 23/56 页」「识别公式和文字 544/1100 项」——看得懂它在数什么, 不会再跳得莫名其妙。

加载模型那几十秒进度条不动

MinerU 起来到第一条进度输出之间有几十秒在加载模型,一个字都不吐, 所以那段没法给真实进度。界面上阶段名会写「正在加载识别模型」, 但百分比确实不走——这是已知的,不是卡死。

转完之后:报告和历史

报告:全都转成功时不出现。有转失败的、有扫描页、有公式没转成、 或者删掉过图内文字,才在底下给一个「看报告」按钮,点开能看能复制。 不落盘——你要留就自己复制走。开头有一句「没列出来的不代表一定对」。

历史:底部状态栏的「历史」,任何时候都能点(转换中也能,它是只读的)。 每转完一份就记一条,关掉软件也还在,最多留 200 条。每行能:打开转好的 Word、打开它所在的文件夹(转失败的话打开源 PDF 所在的文件夹, 想重转就从那儿拖回来),鼠标停在失败那行上能看完整报错。

转换途中还能再加文件

软件里永远只有两个队列:正在跑的,和攒着的。

转换进行中把 PDF 拖进窗口(或者点「再加几份」),它们不会塞进正在跑的 那批,而是排进待办队列,底下显示「待办 N 份,这批转完自动接上」,每份 都能单独移除。等这批全部转完,待办整批自动接上开始转,不问——倒计时 和总进度条从头重新算。

不并进当前那批是有原因的:那批的倒计时是开工时估死的、进度条的分母也是 那时定的(见上一节)。中途加文件会让倒计时走到 0 了还在转、进度条因为 分母变大而卡住不动,而它的规矩是「只涨不退」。攒着接上,两个数各自都是 诚实的。

摁停止时待办不会丢:并回待转清单,下次点「开始转换」时一起转。

待办一晋升,列表立刻换成新一批,所以转换屏上会多一个「上一批的报告」—— 上一批那张校对清单还看得到(前提是它真有东西可报,全都干干净净就不给 这个按钮,那是死按钮)。

出问题了:一键生成诊断文件

「关于 → 环境检测」里有个「生成诊断文件」。点一下,软件把能摸到的一切写成 一个 txt,然后弹出资源管理器并选中它,直接拖进微信发出来就行。

里面有:环境细节(CUDA 通道、Office 的 XSL、node、pandoc、用的哪个 解释器)、路径与编码(安装路径和用户名含不含非 ASCII、%TEMP% 的长名短名)、 模型实际指向哪、各项磁盘占用、升级状态和备份、最近 50 条转换历史(路径 完整保留,因为路径本身常常就是病根)、前端 JS 报错、以及三个日志文件的 尾部。

某一项读不到不会让整份文件失败——那一行会写「读不到:具体异常」,而 那句话本身往往就是线索。写入位置有三级兜底(logs\ → 安装目录 → 临时 目录),文件带 BOM 和 CRLF,记事本直接打开不乱码、不连成一行。

升级识别引擎,和后悔药

「关于 → 环境检测」能看到 MinerU、GPU 运行库有没有新版,可以单独升。

装之前会先把旧版整份备份下来(torch 一份 4 GB 上下),装到一半断电 下次开机会自动爬回来。装成功那份也留着,就在同一页列出来:

20260907_145754   torch 2.11.0+cu128   4.14 GB   [退回这一份]

中间那列是关键——你得先知道退回去会变成什么才好决定。点两次才真退 (拷 4 GB 要几分钟,误触代价不小),转换进行中按钮是灰的。退完给一个 「立即重启」按钮,重启之后就是旧版本了。

重启之后那份备份会自己消失。 你已经在这个版本上了,再「退回」它没有 意义;真要再升级,装之前又会重新备一份,不缺。

下好的升级包不会跟着丢:退回之后它还躺在硬盘上,界面上直接还能装回 去,不用重新下 2.8 GB。

备份不会无限堆:同一个版本只留一份,不同版本最多留两个。09-08 之前 是只增不减——装完没人删旧的,实测在开发机上堆到过 12.4 GB(三份内容几乎 一样的 2.11.0)。想彻底清干净,环境检测页勾「升级备份」点清理,那条路会 只留最新一份。

公式:必须走 MML2OMML.XSL

LaTeX --KaTeX(打包的 node)--> MathML --MML2OMML.XSL(随包分发)--> OMML

2026-09-01 起 XSL 是硬性要求,不降级到 Pandoc。 之前的策略是 「有 XSL 用 XSL,没有退 Pandoc」,改掉是因为两条路的产物有实质差异, 不是风格差异:

Office XSL Pandoc
空集 ∅ ∅ U+2205 ⌀ U+2300 直径符号,是错的
\leqslant ⩽ U+2A7D ≤ U+2264
括号 OMML 定界符,Word 里随内容伸缩 当成普通文字

与其让一部分人拿到次等产物还不知情,不如在门口就拦下来说清楚。

公式怎么塞进 Word:先把每个公式换成一个占位符,pandoc 全程只当普通 文字搬运,出 docx 后按占位符精确定位塞回 XSL 的结果。位置由占位符决定, 不靠「数量必须相等」——2026-09-02 之前是按顺序一一对应的,一个 OCR 粘连出来的 \gtan 让 pandoc 少产出一个公式,从缺口往后 70 个全部张冠 李戴,只能整份判失败(11 份真实讲义里 1 份中招)。

现在:少数公式转不成不废掉整份——错位已经不可能发生,那几处保留 LaTeX 原文,报告里点名是第几个,其余照常。只有「一个都没转成」 (没 XSL、没 node、XSL 链路坏了)才判整份失败。

判失败时那份已经写出的 Word 不删,改名成 xxx【公式未完全转换】.docx 留下——要防的是「被当成正品」,不是 「它存在」。转一份要几分钟,不该因为公式没转全就把 132 张图、18 个表 一起扔掉。改不动名字(文件被 Word 占着)时会明确警告,不静默。

⚠️ Pandoc 不能从包里去掉:整个 docx 是它生成的(md→html→docx), XSL 只是把生成物里的公式替换掉。

XSL 从哪来(2026-09-09 改定):runtime/xsl/MML2OMML.XSL 随软件打包 分发,转换一律用这一份,不再要求用户装 Office。改的原因是小蔡定的使用范围 (「这个软件只给三个人用」),云端版先做了这个决定,两边保持一致。 ⚠️ 这是微软随 Office 分发的文件,本项目又是 GPL-3.0,授权上是冲突的, 小蔡知情并拍板。

找的顺序:自带的优先,找不到才回头探测用户的 Office —— 先查注册表(Click-to-Run 和传统 MSI 两种键位都读),再扫常见目录。 Office 探测那套没删,留作兜底:万一自带文件被杀软删了还能自己找回来。 (那套探测本机实测 0.7 毫秒命中——现代 Office 全是 Click-to-Run, 传统那个 InstallRoot 键存在但值是空的,只读它会一个都找不到。)

文字准确度:不做承诺

2026-08-31 实测推翻了初版设计。同一份 PDF 跑 MinerU 三种模式:

模式 正文字符 行内公式 「己知」 「已知」
txt 10187 131 1 13
auto 10187 131 1 13
ocr 8972 213 1 13

那个错字在原 PDF 的文字层里就是错的(原书排版打错的字),三种模式都消不掉。 而 txt / auto 丢了 38% 的公式。所以默认 ocr。

界面因此不显示「文字版 / 扫描版」这种质量暗示,只报客观事实: 几页、哪几页没有文字层。详见 docs/DESIGN.md 第二节。

没有显卡能用吗

不能。这个软件只用 GPU。(2026-09-02 定的规矩)

技术上 CPU 是跑得动的——2026-08-31 实测同一份 10 页讲义:GPU 262 秒、 纯 CPU 460 秒,慢 2.0 倍而已。改成只用 GPU 不是因为 CPU 跑不动,而是因为 静默降级最坑人:MinerU 的 get_device() 探不到显卡就自己换 CPU, 用户完全不知道自己在等一件本可以快一倍的事,只觉得「这软件真慢」。

所以 MINERU_DEVICE_MODE 写死成 cuda,探测这一步直接跳过。

没有 N 卡的机器上会怎样:当场报错,不是白等。实测两种情况——

报错
装的是 CPU 版 PyTorch AssertionError: Torch not compiled with CUDA enabled
装了 CUDA 版但没有 N 卡 RuntimeError: No CUDA GPUs are available

界面在启动时就会警告显卡不达标,但不拦着你点——点了立刻失败, 不会浪费半小时。

GPU 运行库要单独下

安装包里不带 CUDA 版 PyTorch。它解压后 4.2 GB,打进安装包会让包从 291 MB 涨到 1.5~2 GB,逼近 GitHub 单文件 2 GiB 的上限,而且没有显卡的人 也得跟着下这 4 GB。

改成首次启动时按需下(wheel 约 2.8 GB),跟那 4.6 GB 模型走同一个流程, 用户只等一次。软件会自己检查并在界面上给一个「现在就装」的按钮。

下哪个 CUDA 版本由「驱动够不够」和「这张卡的机器码在不在里面」一起 决定,不写死:

显卡 / 驱动 用哪个
新卡(算力 12.0)+ 驱动 ≥ 580 cu132 / cu130
主流卡(5.0~9.0)+ 驱动 ≥ 528.33 cu126
老驱动(≥ 452.39)/ 读不到显卡 cu118

🔴 不是「CUDA 号越大越好」。 实测各条线路当时的最新 torch: cu118 停在 2.7.1、cu128 停在 2.11.0(官方已停更)、cu129 只到 2.9.0, 而 cu126 / cu130 / cu132 都在 2.14.0。按 CUDA 号从大到小挑的话,驱动 572 的用户会拿到 cu128 的 2.11.0 —— 比低一档的 cu126 还旧三个次版本。

🔴 还要看显卡型号,不能只看驱动。 各条线路里编进去的显卡机器码是 交叉的:cu126 有老卡(5.09.0)没新卡,cu13x 反过来(7.512.0)。 1080Ti(算力 6.1)配新驱动,只看驱动会挑中 cu128,而 cu128 里根本没有 6.1 的机器码 —— import torch 能过、检测显卡也说「有」,只有真跑起来 才报「找不到这张卡的机器码」。

写死 cu128 的话,驱动低于 570 的机器会在 import torch 时报 [WinError 1114] 动态链接库(DLL)初始化例程失败——而 modelscope 的 import 链里有 import torch,连模型下载都一起废掉,用户看到的是 「下载器崩了」,完全猜不到跟显卡驱动有关。

装完还会真的 import 一次再说「装好了」:torch/version.py 是个文本 文件,它在不代表 c10.dll 加载得起来。

C++ 运行库排在它前面,是首启的第一步。 GPU 运行库要靠 Visual C++ 运行库才加载得起来,顺序反了就是白下一趟 2.8 GB。

软件会直接装一次 vc_redist(约 25 MB,微软官方包),不去判断这台 电脑装没装 —— 重复安装是安全的,微软自己的文档就说直接跑最新的 vc_redist 可以修复运行库、不用先卸载,已有更新版本时它返回 0x666 自己退出、什么都不改。

为什么不判断:这个项目在「判断某个东西在不在」上连着栽过四次, 形状完全一样 —— 把「文件在」当成「能用」:

判据 结果
torch/version.py 在 报「装好了」,而 c10.dll 加载不了
models 目录在 报「模型齐了」,而里面是半截的
数 DLL 清单 只查了 msvcp140.dll 一个,缺 msvcp140_1.dll 的机器放行
数 DLL + 自己的目录 没装 VC 的机器也打勾 —— Python embeddable 自带 vcruntime140*,哪台机器上都在

每次的反应都是把判断做得更准,第五次换了思路:不判断。装一次, 在安装目录记个 vc_done.json,下次看这个记号 —— 它记的是 「这个软件在这台机器上装过一次」,我们自己说了算,准确; 「这台机器装没装 VC++」永远猜不准。

装的时候会弹系统的权限确认框(要改系统目录,绕不过去)。点了「否」 或者账号没有管理员权限,软件会说清楚是哪种情况,并给手动装的路。


装在中文路径里可以吗

可以。D:\我的软件\PDF转Word (正式版) 这种中文加空格加括号的路径都行。

以前不行。 2026-09-03 有用户把软件放在桌面的「新建文件夹 (2)」里, 转换跑满一分钟,然后失败:

Failed to load FastText model: ...\lid.176.ftz cannot be opened for loading!

文件明明在那儿。问题出在识别引擎依赖的一个语言检测库:它的模型加载 是 C++ 写的,Python 把路径按 UTF-8 编码递过去,那边按系统的 GBK 解, 中文字节对不上——那个文件在 C++ 眼里就是不存在。

修法是在把路径交给它之前先转成 GBK 字节,第三方库本身没动过。

补丁挂在哪儿,比补丁本身还费事。 常规做法是靠 PYTHONPATH + sitecustomize 让 Python 启动时自动加载,开发环境完全可行、测试全绿—— 但发行版的 Python 是 embeddable 版,目录里有 python312._pth, 而 ._pth 一存在,PYTHONPATH 就被整个忽略,那份补丁在真实发行版上 一次都不会生效。放进 runtime/python/Lib/site-packages/ 倒是能加载, 可更新包只覆盖 pipeline/ server/ app/,碰不到 runtime/, 老用户更新完照样没有。

最后落在 pipeline/sitepatch/run_mineru.py:一个引导脚本,先打补丁再把 控制权交给 MinerU 的 CLI。用脚本路径启动时 Python 会把脚本所在目录放进 sys.path[0],这个机制不看 ._pth 也不看 PYTHONPATH,两个环境完全 一致;而它在 pipeline/ 底下,更新包覆盖得到。

(那个文件的开头还记着为什么「用 Windows 短路径名」那个看着更漂亮的 方案是死路。)

但上面那些只修好了一半,2026-09-05 才发现。 识别引擎自己还会再起一个 进程干活(python -m mineru.cli.fast_api,见它的 cli/api_client.py:511), 而「判断这段字是中文还是英文」这件事只发生在那个进程里 —— 调它那个 detect_lang() 的全在 backend/ 底下,CLI 侧一处都没有。引导脚本那层的 补丁根本够不着它。

所以 9-03 到 9-05 之间的每一版,装在中文路径下照样失败,报的还是一模一样 那句话。而且崩的位置很不巧:在 cli/common.py:315 的 _process_output 里, 识别全做完了、正在把结果拼成 markdown,写盘之前 —— 等好几分钟, 一个字节都不留。

现在 sitecustomize.boot_argv() 认出 [python, '-m', 'mineru.*', ...] 这种 启动命令就改写成经引导脚本启动,patch_subprocess() 把这个改写挂在 subprocess.Popen.__init__ 上(只能包 __init__:MinerU 有 subprocess.Popen[bytes] 和 isinstance(p, subprocess.Popen) 两种用法, 把 Popen 换成函数会当场炸)。

引导脚本同时补了 __main__ 守卫 —— Windows 上 multiprocessing 用 spawn, 子进程会把主脚本重跑一遍来重建命名空间,那一次 sys.argv 里没有模块名, 再去 pop(1) 会拿到 --host,PDF 渲染进程池整个起不来。

唯一还不行的:文件夹名里带 emoji,或者中文系统上混进韩文这类当前 系统编码表示不了的字。碰上了软件会直接说清楚是哪个路径、该怎么改, 不会让你对着一串英文堆栈发愣。


给开发者

装开发环境

.venv\Scripts\python.exe tools\setup_env.py --check   # 看缺什么
.venv\Scripts\python.exe tools\setup_env.py           # 装齐

下载约 3 GB(大头是 CUDA 版 torch),装完 .venv 占约 5.1 GB。

注意:runtime\pandoc\pandoc.exe(233 MB)不在版本库里,.gitignore 排除了它,setup_env.py 也不下载它。从 git clone 出来需要自己放一份 (pandoc 3.11 发布页)。

目录

pipeline/   probe(体检) gpu(显卡) extract(调MinerU) tomath(XSL公式)
            todocx(出Word) convert(串起来) sources(多源测速)
            paths(落点集中管理 + 怎么跑 MinerU) models(模型认领与下载)
            torchdep(GPU 运行库在不在、怎么装) update(检查更新)
            tex2mml.js(KaTeX 转 MathML) vendor/katex
            sitepatch/(挂进子进程的补丁,只为中文安装路径)
server/     FastAPI,只绑 127.0.0.1,端口系统分配
app/        Electron 外壳 + 前端(纯 JS 无框架,主屏 + 首次选源屏)
            icon.ico / icon_source.png(GitHub 头像做的图标)
runtime/    pandoc.exe + 许可证。node.exe 和 python/ 是**发行版才有的** ——
            打包时 put_node() 从系统 PATH 复制 node.exe 进去,开发机上
            直接走 PATH 里那个
tools/      setup_env(装开发环境) build_release(组装发行版) make_icon(做图标)
tests/      626 条 Python + 219 条前端检查 + 四个真实数据验证脚本
docs/       DESIGN.md(设计与决策台账) RELEASE.md(发行版规矩)

跑测试

.venv\Scripts\python.exe -m unittest discover -s tests -q   # 626 条,37 秒
node tests\front_check.js                                   # 219 条,真渲染

🔴 必须是 .venv\Scripts\python.exe,不能用全局的 python。 torch 和 mineru 只装在 .venv 里,用全局 Python 跑会得到 9 个假失败:

ERROR: test_server            (整个文件导入失败)
FAIL:  test_开发环境是CUDA版    (torchdep)
FAIL:  test_读得出本地装的版本   (deps)
FAIL:  test_判MinerU在不在...   (paths)
...

这几条测的是「本机真实环境」,缺依赖时它们应该红 —— 但那是环境 不对,不是代码坏了。看见这一组一起红,先确认用的哪个 python, 别当成真 bug 去查(2026-09-05 全量审查时一上来就栽在这儿)。

这两个都是离线的、秒级的。另有四个依赖本机真实文件的验证脚本, 不在自动套件里:

tests\real_probe_check.py     真实 PDF 的体检(扫资料库里的 544 份)
tests\real_convert_check.py   拿现成的 MinerU 产物转 Word(11 份,不用 GPU)
tests\real_pdf_to_word.py     端到端:一份 PDF 直接出 Word(要 GPU,约 4 分半)
tests\real_cpu_bench.py       同一份书强制走 CPU,量真实差距(约 8 分钟)
                              ⚠️ 2026-09-02 起产品只用 GPU,这个脚本
                              留着是为了保留那组对照数据,不代表支持 CPU

它们存在的理由:单元测试用的是脚本造的假数据,只能证明逻辑自洽, 不能证明在真书上做对了。这个项目里已经栽过三次「测试全绿但实际没生效」, 最近一次是 v0.0.1 发出去之后才发现它在任何非开发机上都跑不了 (详见 docs/RELEASE.md 已知的坑里那条 pip launcher)。

发行版

见 docs/RELEASE.md——版本号规则、打包命令、命名规矩、 上传清单、不能破坏的硬约束。发版前从头照着走一遍,别凭记忆。

.venv\Scripts\python.exe tools\build_release.py --version v0.0.1               组装 + 装依赖
.venv\Scripts\python.exe tools\build_release.py --version v0.0.1 --sfx         打成自解压 exe
.venv\Scripts\python.exe tools\build_release.py --version v0.0.1 --update-only 只打更新包

落点:全部在安装文件夹内

删掉文件夹 = 卸载干净,不留注册表、不留 AppData。靠这几处保证:

靠什么
模型 / 临时文件 / MinerU 配置 / 设备模式 paths.child_env() 的四个环境变量
Electron 缓存 app.setPath,必须在 app ready 之前调
补上的 C++ 运行库 放 runtime/python/msvcp140.dll,不装系统级的
转好的 Word 用户自己选(唯一的例外)

那四个环境变量:MODELSCOPE_CACHE、HF_HOME、MINERU_TOOLS_CONFIG_JSON (前三个管落点)、MINERU_DEVICE_MODE=cuda(管不许悄悄用 CPU)。

还有第五个 MINERU_TASK_RESULT_TIMEOUT_SECONDS=86400,跟落点无关,管的是 单份 PDF 最多等 24 小时。MinerU 客户端默认只等 1 小时,按实测 66 秒/页 折算约 54 页就是红线,过线的讲义会在临门一脚被掉、已算的全作废 (2026-09-06 一份 56 页的踩过,详见 child_env() 的注释)。

日志落在 logs/:model_download.log、torch_install.log、convert.log。 出问题让用户直接把文件发过来,比让他描述现象快得多——2026-09-02 那次 就是靠界面给出的日志路径才定位到根因的。

代价是不能装进 C:\Program Files,paths.writable() 在启动自检里拦这种情况。

另外不碰用户的全局 ~/mineru.json——那是 MinerU 的全局配置,用户机器上 可能装着别的用 MinerU 的东西,我们通过 MINERU_TOOLS_CONFIG_JSON 指向自己那份。

跑 MinerU 一律用「解释器 + 我们自己的 .py」

paths.mineru_cmd()           # [python.exe, sitepatch/run_mineru.py, 'mineru.cli.client']
paths.models_download_cmd()  # [python.exe, sitepatch/run_mineru.py, 'mineru.cli.models_download']

中间那层 run_mineru.py 是引导脚本:先打上中文路径补丁,再把模块名 转交给 MinerU 的 CLI。为什么补丁只能挂在这儿——发行版的 Python 是 embeddable 版,目录里有 python312._pth,而 ._pth 一存在, PYTHONPATH 就被整个忽略,常规的 sitecustomize 那条路在发行版上 一次都不会生效(开发环境却是好的,测试照样全绿)。而放进 runtime/python/Lib/site-packages/ 又对老用户无效:更新包只覆盖 pipeline/ server/ app/,碰不到 runtime/。同时满足「更新包能覆盖」 和「不依赖 site 机制」的位置,只有这一个。

绝不调 runtime/python/Scripts/mineru*.exe。 那些是 pip 给 console_scripts 生成的 launcher,尾部硬编码着生成它那一刻的 python.exe 绝对路径——换台机器(或者换个解压位置)就找不到解释器。v0.0.1 就是栽在 这上面:模型下载和转换全废,而开发机上永远是好的,因为包就是在那个路径下打的。

许可证

本软件采用 GPL-3.0-or-later,全文见根目录 LICENSE。 作者:终末诗篇。你可以自由使用、修改、分发,但分发修改版时必须同样开源。

用到的第三方组件:

协议 约束
MinerU Apache 2.0 + 附加条款 商用免费(门槛月活 1 亿/月入 2000 万美元);做在线服务须标注
Pandoc GPL-2.0+ 独立调用不传染;分发须附协议全文(见 runtime/pandoc/COPYRIGHT.txt)
PyTorch BSD-3-Clause 不打包,首次启动时从 pytorch.org 下到用户自己机器上
KaTeX MIT 见 pipeline/vendor/katex/LICENSE
Node.js MIT 发行版里打包了 node.exe
微软 MML2OMML.XSL 闭源 只读用户本机的,不分发

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages