
简介这是一套面向Java开发者与文档自动化处理场景的多格式文档互转工具包解决办公文档、网页内容、数据报表及图像资料在不同业务系统间流转时的格式兼容难题。工具支持Word、HTML、Excel、PDF、JPEG与Markdown六类主流格式的双向转换内置Aspose与Spire系列商业级解析引擎并集成wkhtmltopdf等跨平台渲染组件兼顾内容完整性与样式还原度。资源包共46个文件含20个核心Java源码、4个关键jar依赖库如aspose-words、Spire.Pdf、3个说明文档md、2个配置文件xml及1个Windows可执行转换器exe整体体积167.44MB结构清晰便于二次开发与本地部署。目前已有405人学习下载提供开箱即用的转换示例、典型测试文件docx/xlsx/pdf/html等及模块化工具类调用说明适合需要快速集成文档转换能力的中高级Java工程师与企业级OA系统开发者。 做个能处理 Word、HTML、Excel、PDF、JPEG 和 Markdown 这六种格式互转的工具包这事儿我琢磨了挺久。刚接到这个需求时第一反应是“这不就是套壳 Pandoc 再加几个库吗”真动手做下来才发现每个格式之间的转换都有它的脾气尤其是 Excel 和 PDF 这种“边界分明”的格式处理起来比想象中麻烦得多。这篇文章不是讲理论我把整套工具包的架构设计、每个转换路径的实现思路、选型理由和踩过的坑都记下来了。代码基于 Python 3.9用到的核心库是 python-docx、openpyxl、PyMuPDF、markdown、BeautifulSoup、img2pdf 这些全部开源免费可以直接在公司内部或本地环境部署。1. 为什么需要这样一个工具包日常文档流转的真实困境1.1 三种典型的“格式焦虑”场景我最初做这个工具包是因为在自家项目里被文档格式折磨得够呛。第一类是交付场景做技术方案时老板说“给我个 Word 版”做完了甲方又改口“还是 PDF 吧”你总不能每次都在 WPS 和 Office 之间人工转换尤其是方案里还有截图、表格、代码块这些元素人工操作一次就得检查半天格式效率极低。第二类是内容复用场景。团队内部用 Markdown 写技术文档、写周报、写接口说明但业务同事用不惯 Markdown他们需要的是带目录、带样式的 Word 文档。每次从 Typora 复制粘贴到 Word表格样式全丢行内代码还得手动改格式。这个过程中浪费的时间远远超过写文档本身。第三类是数据报告场景。运营同事发来一份 Excel 数据表里面有十几个 Sheet 的业绩明细和透视表你既希望它能打印成 PDF 发给客户又希望把核心指标整理成 Markdown 表格贴进周报。如果靠人工一张张截图、复制、排版一个下午基本就搭进去了。这三类场景的共同点是格式转换本身没有技术含量但不转换工作就卡在那里。而市面上的在线转换工具要么有文件大小限制要么明晃晃地传文件到对方服务器涉及公司内部数据根本不敢用。本地跑一个轻量工具包是解决这类问题最直接的方式。1.2 转换需求的本质内容与样式如何取舍做这个工具包之前我先把“格式转换”这件事拆解了一下。表面上是文件格式变了本质上是在内容完整性和样式还原度之间做权衡。比如 Word 转 Markdown段落和文本几乎无损但页眉页脚、分页符、复杂的首行缩进这些信息Markdown 本身就不支持硬转只会产生一堆垃圾语法。所以我给工具包定了一个原则优先保证内容无损样式做到合理近似。不可能每个转换都做到 100% 还原但至少要保证文本、表格、图片这类核心信息不丢标题层级这类的结构信息不丢。这个原则贯穿了后面所有转换路径的设计。2. 技术选型与整体架构为什么我用 Python 而不是直接调 Pandoc2.1 对比了一圈还是 Python 生态最合适最先考虑是 Pandoc它在 Markdown 和 Word、HTML 之间的转换效果确实强悍但有两个问题一是 Pandoc 对 Excel 和 JPEG 基本无解二是格式定制需要写 Lua filter维护成本不低。也有想过用 Java 的 POI iText但对轻量工具来说太重了部署起来还得装 JRE。最终我选定了 Python。理由很直白Python 在文档处理这一块的库生态是最全的python-docx、openpyxl、PyMuPDF即 fitz、markdown、BeautifulSoup、img2pdf、WeasyPrint每个都成熟稳定组合起来正好覆盖六种格式的互转需求。整个工具包的核心架构分三层第一层是统一入口converter.py接收源文件路径、目标路径、目标格式自动判断转换路径。第二层是路径分发器把“Word→HTML”这种具体转换映射到对应的处理函数。第三层是各格式处理单元每个方向一个模块互不干扰方便后续单独替换。2.2 核心接口设计一行命令完成所有转换实际使用时我希望达到这种效果不管你要把什么转成什么一条命令搞定。所以我把接口设计成了函数式调用支持两种调用方式# 方式一直接调用 from converter import convert_file convert_file( src_pathinput.docx, dst_pathoutput.pdf, target_formatpdf ) # 方式二命令行 # python converter.py input.xlsx --to pdf --output result.pdf内部实现上我维护了一个路径映射表。伪代码如下CONVERSION_REGISTRY { (docx, html): docx_to_html, (html, docx): html_to_docx, (docx, pdf): docx_to_pdf_via_html, (xlsx, markdown): xlsx_to_markdown, (xlsx, pdf): xlsx_to_pdf_via_html, (pdf, jpeg): pdf_to_jpeg, # ... 其他路径 } def convert_file(src_path, dst_path, target_format): src_ext src_path.suffix.lower().lstrip(.) handler CONVERSION_REGISTRY.get((src_ext, target_format)) if handler is None: raise UnsupportedConversionError( f目前不支持 {src_ext} - {target_format} 的转换 ) return handler(src_path, dst_path)这种注册表模式的好处是以后要加新格式比如要支持 PPT只需要在注册表里加一条映射再实现对应的处理器对现有代码零侵入。3. Word、HTML 与 Markdown三种“文本型”格式的互相打通3.1 Word 转 HTML保留结构比保留样式更重要写这个转换函数的时候我踩过最大的坑是直接用 python-docx 遍历段落把 style 属性全部丢弃生成的 HTML 只有一个接一个的p标签完全看不出标题层级。后来我改成先读 paragraph.style.name把 “Heading 1”“Heading 2” 这类样式映射成h1h2段落里的加粗、斜体、下划线通过遍历 run 的 bold/italic/underline 属性来还原。from docx import Document def docx_to_html(docx_path, html_path): doc Document(docx_path) html_body [] for paragraph in doc.paragraphs: style_name paragraph.style.name # 标题样式 - 对应 h 标签 if Heading in style_name: level int(style_name.split()[-1]) tag fh{min(level, 6)} text for run in paragraph.runs: text run.text html_body.append(f{tag}{text}/{tag}) else: # 普通段落处理行内加粗/斜体 inline_html for run in paragraph.runs: if run.bold: inline_html fstrong{run.text}/strong elif run.italic: inline_html fem{run.text}/em else: inline_html run.text html_body.append(fp{inline_html}/p) # 表格处理 for table in doc.tables: html_body.append(table_to_html(table)) html_content fhtmlheadmeta charsetutf-8/headbody{.join(html_body)}/body/html with open(html_path, w, encodingutf-8) as f: f.write(html_content)这个版本只做到了基础还原。如果要做得更精细还需要处理图片python-docx 里图片是内嵌在 run 里的需要检测 run 里是否有drawing或inline_shapes相关的 XML再提取出来写入 HTML 的img标签。这个留给后续迭代目前版本先不处理图片只保留文字和表格。3.2 HTML 转 Word用 python-docx 重建文档结构反向转换 HTML 到 Word总体思路是用 BeautifulSoup 解析 DOM根据标签类型决定调 python-docx 的哪个 API。重点在于映射关系h1~h6→doc.add_heading(text, leveln)p→doc.add_paragraph(text)ul/ol→doc.add_paragraph(styleList Bullet / List Number)table→ 先doc.add_table(rows, cols)再逐格填充strong→ 加粗 runem→ 斜体 runcode→ 等宽字体 run我一般设置run.font.name Consolas说一下坑。BeautifulSoup 解析时一定要注意表格的嵌套问题。HTML 里一个td里可能还套着段落、列表甚至另一个表格而 Word 表格的单元格里也能放多行内容。我在实现时先用一个辅助函数提取 td 内部的纯文本如果有嵌套结构就递归处理成多个段落再插入单元格。from bs4 import BeautifulSoup from docx import Document def html_to_docx(html_path, docx_path): with open(html_path, r, encodingutf-8) as f: soup BeautifulSoup(f.read(), html.parser) doc Document() for element in soup.body.descendants: if element.name h1: doc.add_heading(element.get_text(), level1) elif element.name h2: doc.add_heading(element.get_text(), level2) elif element.name p: doc.add_paragraph(element.get_text()) elif element.name table: rows element.find_all(tr) if not rows: continue cols_count len(rows[0].find_all([td, th])) table doc.add_table(rowslen(rows), colscols_count) table.style Light Grid Accent 1 for i, row in enumerate(rows): cells row.find_all([td, th]) for j, cell in enumerate(cells): table.cell(i, j).text cell.get_text(stripTrue) doc.save(docx_path)注意上面代码为了简洁直接用了element.get_text()这会导致strong的加粗样式丢失。如果希望保留需要递归处理内联标签这个细节建议在最终版本中补充完整。3.3 Markdown 转 Word一条走 HTML 中间层的捷径Markdown 转 Word最经典的做法是先转 HTML再走 HTML 转 Word 的通道。我这里直接用 Python 的markdown库把 Markdown 字符串渲染成 HTML 字符串然后复用html_to_docx的逻辑只不过 source 换成一段字符串而非文件路径。这样两点之间直线最短没必要再写一个独立的 Markdown→Word 处理器。import markdown as md def markdown_to_docx(md_path, docx_path): with open(md_path, r, encodingutf-8) as f: md_text f.read() html_text md.markdown(md_text, extensions[tables, fenced_code]) # 复用 html_to_docx 的内部逻辑这里省略extensions[tables]这一项一定不能漏。不加的话Markdown 里的管道符表格会被当作普通段落处理转换后 Word 里全是散落的竖线字符非常难看。3.4 Word 转 Markdown直接提取没什么好怕的反过来 Word 转 Markdown我采用的是“降级”策略标题变成#列表变成-表格变成| 列名 |的管道格式。图片暂时用的占位表示。这个方向遇到的问题不多唯一需要留意的是编码保存 Markdown 文件时必须带上encodingutf-8否则 Windows 上默认编码会导致中文全部乱码。4. Excel 的特殊地位从数据表到“成稿”的转换思路4.1 Excel 转 MarkdownPandas 一行搞定但别忽略索引单纯从表格数据转 Markdown其实一条 Pandas 代码就够import pandas as pd def xlsx_to_markdown(xlsx_path, md_path): df pd.read_excel(xlsx_path, sheet_nameNone) with open(md_path, w, encodingutf-8) as f: for sheet_name, df_sheet in df.items(): f.write(f## {sheet_name}\n\n) f.write(df_sheet.to_markdown(indexFalse)) f.write(\n\n)这里有几个需要小心的地方。一是to_markdown需要tabulate库忘了装的话会直接报错。二是多个 Sheet 的情况只转第一个 Sheet 容易漏数据所以我上面用了sheet_nameNone读取全部 Sheet再逐个写入。三是indexFalse一定要加否则 Pandas 默认会把行号也当一列输出到 Markdown 表里干扰阅读。4.2 Excel 转 PDF绕道 HTML 表格是性价比最高的方案Excel 直接转 PDF如果只用 openpyxl 把单元格值读出来再用 PdfPages 画出来工作量很大而且难以复现合并单元格和背景色。我的方案是先用 openpyxl 读取数据生成一个简单的 HTML 表格再用 WeasyPrint 或 wkhtmltopdf 把 HTML 渲染成 PDF。具体的 HTML 生成逻辑里重点是处理合并单元格from openpyxl import load_workbook def xlsx_to_html_table(xlsx_path, sheet_name): wb load_workbook(xlsx_path, data_onlyTrue) ws wb[sheet_name] html_rows [] for row in ws.iter_rows(): html_cells [] for cell in row: # 合并单元格处理 colspan 1 rowspan 1 if isinstance(cell, MergedCell): # 找到合并区域 for merged_range in ws.merged_cells.ranges: if cell.coordinate in merged_range: # 如果是非左上角单元格直接跳过由左上角负责渲染 break html_cells.append(ftd colspan{colspan} rowspan{rowspan}{cell.value}/td) html_rows.append(tr .join(html_cells) /tr) return ftable{.join(html_rows)}/tabledata_onlyTrue非常关键。如果不加openpyxl 读出来的不是单元格的显示值而是公式字符串如果单元格里存的是SUM(A1:A10)你得到的就是这串公式本身。对于转 PDF 的场景绝大多数情况下要的是公式的计算结果。我实际写这段代码时还单独处理了一个问题openpyxl 对于公式单元格data_onlyTrue只能拿到上一次 Excel 打开时缓存的计算结果。如果这个文件是程序生成的、从未被 Excel 打开过那cell.value就是None。这种情况只能考虑用 LibreOffice 在 headless 模式重新打开并另存一份来触发计算结果这个坑我在踩坑部分会详细说。4.3 Excel 转 Word表格结构直接进文档Excel 转 Word 相对简单就是把每个 Sheet 变成一个 Word 表格。我直接复用了html_to_docx里处理表格的代码把 Excel 的数据组织成二维列表再用 python-docx 生成表格。要注意的是 Word 表格单元格有最大宽度限制如果 Excel 里有一列超长文本比如备注列建议在插入前用字符串截断或换行来避免版式爆炸。5. PDF 与 JPEG不可编辑格式的入口与出口5.1 PDF 转 JPEG按页导出高清图片PDF 转 JPEG 是最符合直觉的方向——PDF 本身就可以视为每页是一个图像容器。用 PyMuPDF 处理非常高效import fitz # PyMuPDF def pdf_to_jpeg(pdf_path, output_dir, zoom2.0): doc fitz.open(pdf_path) for page_index in range(len(doc)): page doc.load_page(page_index) # 设置缩放比例zoom 越大图片分辨率越高 mat fitz.Matrix(zoom, zoom) pix page.get_pixmap(matrixmat) output_path f{output_dir}/page_{page_index 1}.jpg pix.save(output_path) doc.close()zoom参数要重点说一下。默认 1.0 的分辨率大约是 96 DPI做工作汇报投屏够用但打印或者放进印刷材料里就明显模糊。我一般设 2.0也就是 192 DPI。如果你的 PDF 里有大量文字型内容这个大小已经足够清晰了如果是扫描件、图纸类建议直接设 3.0 或更高。代价是文件体积变大一张 A4 页面 3.0 倍导出可能到 2~3 MB这个要按需取舍。5.2 JPEG 转 PDF多张图片合成一个文档JPEG 转 PDF 有两种常用方案。一种是 Pillowfrom PIL import Image def jpegs_to_pdf(image_paths, pdf_path): images [Image.open(path).convert(RGB) for path in image_paths] images[0].save(pdf_path, save_allTrue, append_imagesimages[1:])另一种是 img2pdf专门为这个场景优化的库压缩率更可控import img2pdf def jpegs_to_pdf_img2pdf(image_paths, pdf_path): with open(pdf_path, wb) as f: f.write(img2pdf.convert(image_paths))个人建议首选 img2pdf它对图片的处理是原数据直写不会像 Pillow 那样做一次编码转换速度快、画质保留更好。Pillow 方案的优势在于可以统一设置页面尺寸比如把多张小图按固定 A4 排布这个 img2pdf 默认做不到。具体用哪个取决于你是要“多图合成一册”还是“扫描仪批量建档”。5.3 PDF 转 Word不追求完美还原只追求“可编辑”这个方向全网最热门。PDF 转 Word 的难点在于PDF 是一种按“位置”描述的格式而 Word 是一种按“逻辑流”描述的格式两者之间没有直接的映射。我用 PyMuPDF 做文本提取通过文本块的坐标信息重建段落。基本思路用page.get_text(dict)按块提取文字和坐标。根据块与块之间的垂直间距判断是否属于同一段落。根据字体大小和样式判断是否属于标题。把识别出的段落文本按顺序写入 Word。但这里有个现实问题这种方案对纯文本型 PDF 效果尚可对排版复杂的 PDF双栏、图文混排、表格穿插就力不从心了。所以我在工具的文档里明确写了适用范围并建议用户在核心场景下优先使用专业 OCR 方案。如果遇到扫描版 PDF建议直接用你本地已有的 OCR 工具先跑一遍文本识别然后再送到我们这里处理。5.4 PDF 转 Markdown比转 Word 更“激进”的降级PDF 转 Markdown 和转 Word 的思路类似只是输出格式变成了 Markdown 文本。实际操作中我是从 PDF 里提取每个文本块根据坐标判断它前面是否有缩进然后把标题行转成#语法。这个版本我没有做复杂的布局还原只做到了“文本可读、结构基本保留”想要更精确的效果建议配合专门的 PDF 结构分析工具先行处理。6. 批量转换设计、编排与踩坑记录值得警惕的细节6.1 批量转换目录扫描与格式自动识别真正上线使用的时候没有人会一个个文件手动调用 API。我加了一个批处理入口传入一个目录自动扫描所有支持的文件按照预设的规则统一转换。目录遍历用pathlib比os.path更安全而且天然处理路径分隔符问题。from pathlib import Path def batch_convert(input_dir, target_format, output_dir): input_dir Path(input_dir) output_dir Path(output_dir) output_dir.mkdir(parentsTrue, exist_okTrue) supported_exts [docx, html, xlsx, pdf, jpg, jpeg, md] for file_path in input_dir.rglob(*): if file_path.suffix.lower().lstrip(.) in supported_exts: dst_path output_dir / (file_path.stem f.{target_format}) try: convert_file(file_path, dst_path, target_format) print(f成功: {file_path.name} - {dst_path.name}) except Exception as e: print(f失败: {file_path.name} 原因: {e})批处理时有个隐藏风险如果同时把源文件目录和输出目录指向同一个文件夹转换得到的文件会再次被扫描到造成无限循环。比如你把一堆.docx转成.pdf输出目录也是源目录扫描时rglob会把刚生成的.pdf也扫进来如果目标格式是pdf且 PDF 也在支持列表里就可能对 PDF 再做一次转换。解决方式是扫描前先排除已经转换过的文件或者把输出目录放在源目录之外。6.2 踩坑记录格式化工具中最容易出问题的几个环节这个工具包从第一版到能稳定运行我前前后后踩了不少坑。挑几个典型的问题整理成表格方便对照现象根因解决方案打开 HTML 时中文乱码没有声明meta charsetutf-8生成 HTML 时强制头部加上 UTF-8 声明转换出来的 Word 表格没有边框python-docx 默认Table Grid样式未应用创建表格后手动设置table.style Table GridMarkdown 表格在 Word 里变成了竖线散落一页Markdown 扩展未开启tables调用markdown.markdown时带上extensions[tables]Excel 读取公式单元格返回 None文件从未被 Excel 打开缓存结果不存在用 LibreOffice headless 先转换一次或手动在 Excel 中另存PDF 转 JPEG 输出图片尺寸过大zoom 参数设置过高按实际用途调整 zoomPPT 用 2.0打印用 3.0批处理时出现重复处理输出目录在源目录内强制输出目录与源目录分离或用集合记录已处理文件其中第六个坑LibreOffice 的依赖我单独说一句。如果你的 Excel 文件里到处都是公式并且需要转 PDF建议在工具包旁边加一个可选的--calc开关调用 LibreOffice headlesslibreoffice --headless --convert-to pdf input.xlsx这能解决 openpyxldata_only读不到公式结果的问题。LibreOffice 转换时对复杂 Excel 样式条件格式、数据透视表的还原度比 openpyxl 高不少缺点是需要系统里装 LibreOffice而且启动速度慢。我的处理是做成可选依赖并不是默认路径。6.3 自动化工作流示例从 Markdown 到 Word、PDF 的发布链路这个工具包最有价值的场景是把文档转换嵌入到自动化流程里。以我自己用的一个例子说明团队里所有技术方案都用 Markdown 写存放在 Git 仓库。每次提交后触发一个脚本把所有 Markdown 文件转成 Word 版作为审阅稿再转成 PDF 版作为对外发布稿。Word 稿给研发负责人画批注PDF 稿给业务方做内部评审。整个过程不需要任何人手动打开编辑器另存省掉了大量重复劳动。# 伪代码发布链路 markdown_files list(Path(docs/).glob(*.md)) for md_file in markdown_files: markdown_to_docx(md_file, fdist/{md_file.stem}.docx) markdown_to_pdf(md_file, fdist/{md_file.stem}.pdf)类似地运营团队每周把 Excel 汇总表转成 PDF 发送给客户也可以接入这个流程把“手工导出再邮件发送”变成“定时任务自动生成”。6.4 关于 JPEG 作为中间格式的一点个人看法最后说一个我在设计时反复纠结、最终确定下来的细节JPEG 在六种格式里的地位。如果你仔细看会发现上面的转换路径里JPEG 基本只和 PDF 发生关系PDF 转 JPEG 是做预览或提取页面图片JPEG 转 PDF 是拼图建档。它不太适合作为“中间格式”把文字型文档转过来转过去。为什么因为 JPEG 是有损压缩格式一旦文字被栅格成图片再进行任何文字提取都需要 OCR成本极高且准确率不稳定。所以实际使用中如果有一个“Word 转 JPEG”的需求我建议正确路径是 Word → PDF → JPEG而不是直接 Word → JPEG。直接转出来的 JPEG 往往只是 docx 的截图效果清晰度、精度都不如经过 PDF 中转来的。这也是这个工具包架构设计的一个核心思想尽量利用 PDF 作为高保真中间格式JPEG 只做最终的展示输出。7. 现有版本的能力边界与后续扩展思路工具包目前完成了 15 条转换路径覆盖六种格式的主要互转需求但边界也很明显复杂 PDF双栏、图文混排、扫描件转 Word/Markdown 的效果只能算“可用”达不到“完美还原”。图片型文档如扫描存档的 PDF需要 OCR本工具包未内置 OCR 引擎。Word/HTML 中的图表、形状、SmartArt目前版本只保留文本和表格复杂元素会丢失。这些边界我不会在工程里刻意去绕开因为绕开的成本太高不如在文档层面明确说明。后续如果要做第二版我有三个优先扩展方向第一是接入 OCR 引擎补扫描件场景第二是增加 LibreOffice 作为可选后端提升复杂格式的还原度第三是把工具包封装成 Web API方便团队其他同事通过页面拖拽完成转换而不是每个人都来装 Python 环境。实际动手做这个工具包让我对“文档转换”这个看似简单的需求有了更深一点的理解。格式转换这件事难点从来不是“怎么把一个文件变成另一个文件”而是“哪些信息能保留、哪些信息必须牺牲、如何在两者之间做合理的决策”。把这套决策逻辑写清楚比堆一堆转换代码重要得多。如果你也在做类似的工具不妨先把你涉及到的格式组合列个矩阵标注清楚每种组合的保真度预期然后再动手写代码这样后面能省掉很多反复调整的时间。本文还有配套的精品资源点击获取