跳到主要内容Python 实现 Markdown 转 Word 文档(markdown+python-docx 方案) | 极客日志Python
Python 实现 Markdown 转 Word 文档(markdown+python-docx 方案)
使用 Python 的 markdown 和 python-docx 库将 Markdown 文件转换为 Word 文档。核心流程是先解析 MD 为 HTML,再渲染至 Word。提供基础版和增强版代码,支持标题、列表、表格、图片等语法。关键优化包括中文字体设置、扩展语法启用及异常处理。此外还推荐了 pandoc 作为无代码替代方案。
不羁1 浏览 Python 实现 Markdown 转 Word(markdown+python-docx 方案)
一、核心方案说明
要实现 Markdown 文件 → Word(.docx) 文件 的格式转换,核心思路是:
✅ 先用 python-markdown 库把 Markdown 文本/文件解析成 HTML 格式;
✅ 再用 python-docx 库将解析后的 HTML 内容,逐节点渲染到 Word 文档中,完成最终转换。
二、完整环境安装(一键执行)
该方案依赖 3 个核心库,直接在终端执行以下命令安装所有依赖:
pip install python-markdown python-docx beautifulsoup4
python-markdown:核心 Markdown 解析库,负责 MD → HTML;
python-docx:核心 Word 操作库,负责生成/编辑 .docx 文档;
beautifulsoup4:辅助解析 HTML 节点,方便精准提取内容渲染到 Word。
三、完整可运行代码(直接复用)
版本 1:基础版(支持绝大多数 MD 语法,满足日常需求)
支持标题(1-6 级)、段落、加粗、斜体、有序列表、无序列表、超链接、图片、换行等核心语法,代码可直接复制运行:
import markdown
from docx import Document
from docx.shared import Pt, Inches
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_PARAGRAPH_ALIGNMENT
from docx.oxml.ns import qn
from bs4 import BeautifulSoup
import os
def markdown_to_word(md_file_path, docx_file_path=None):
""" Markdown 文件转 Word 文档核心函数
:param md_file_path: 源 Markdown 文件路径(必填,如:./test.md)
:param docx_file_path: 输出 Word 文件路径(可选,默认同目录同名.docx)
"""
if not os.path.exists(md_file_path):
print(f"错误:源文件 {md_file_path} 不存在!")
return
if docx_file_path is None:
docx_file_path = os.path.splitext(md_file_path)[0]+".docx"
with open(md_file_path,"r", encoding="utf-8") as f:
md_content = f.read()
html_content = markdown.markdown(
md_content,
extensions=['extra', 'sane_lists', 'nl2br'],
extension_configs={}
)
doc = Document()
doc.styles['Normal'].font.name = '宋体'
doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'),'宋体')
doc.styles['Normal'].font.size = Pt(12)
soup = BeautifulSoup(html_content,"html.parser")
parse_html_node(soup, doc)
doc.save(docx_file_path)
print(f"转换成功!Word 文件已保存至:{docx_file_path}")
def parse_html_node(node, doc):
"""递归解析 HTML 节点,映射为 Word 对应样式"""
if node.name in [f'h{i}' for i in range(1,7)]:
level = int(node.name[1])
p = doc.add_paragraph()
run = p.add_run(node.get_text(strip=True))
run.font.size = Pt(20- level *2)
run.font.bold = True
run.font.name = '黑体'
run._element.rPr.rFonts.set(qn('w:eastAsia'),'黑体')
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
elif node.name == 'p':
p = doc.add_paragraph()
parse_inline_content(node, p)
elif node.name == 'ul':
for li in node.find_all('li', recursive=False):
p = doc.add_paragraph(style='List Bullet')
parse_inline_content(li, p)
elif node.name == 'ol':
for li in node.find_all('li', recursive=False):
p = doc.add_paragraph(style='List Number')
parse_inline_content(li, p)
elif node.name == 'br':
doc.add_paragraph()
for child in node.children:
if child.name:
parse_html_node(child, doc)
def parse_inline_content(node, paragraph):
"""解析行内元素(加粗、斜体、超链接等),添加到指定段落"""
for content in node.contents:
if isinstance(content,str):
run = paragraph.add_run(content)
run.font.name = '宋体'
run._element.rPr.rFonts.set(qn('w:eastAsia'),'宋体')
run.font.size = Pt(12)
elif content.name == 'strong':
run = paragraph.add_run(content.get_text())
run.font.bold = True
run.font.name = '宋体'
run._element.rPr.rFonts.set(qn('w:eastAsia'),'宋体')
elif content.name == 'em':
run = paragraph.add_run(content.get_text())
run.font.italic = True
run.font.name = '宋体'
run._element.rPr.rFonts.set(qn('w:eastAsia'),'宋体')
elif content.name == 'a':
text = content.get_text()
link = content.get('href','')
run = paragraph.add_run(f"{text}({link})")
run.font.color.rgb = None
run.font.name = '宋体'
run._element.rPr.rFonts.set(qn('w:eastAsia'),'宋体')
if __name__ == "__main__":
INPUT_MD_FILE = "./test.md"
OUTPUT_DOCX_FILE = None
markdown_to_word(INPUT_MD_FILE, OUTPUT_DOCX_FILE)
版本 2:增强版(额外支持 表格、代码块、图片 语法)
日常使用中表格、代码块、图片是高频 MD 语法,在基础版上扩展该能力,满足更复杂的转换需求:
import markdown
from docx import Document
from docx.shared import Pt, Inches, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
from docx.oxml.ns import qn
from bs4 import BeautifulSoup
import os
def markdown_to_word(md_file_path, docx_file_path=None):
if not os.path.exists(md_file_path):
print(f"错误:源文件 {md_file_path} 不存在!")
return
if docx_file_path is None:
docx_file_path = os.path.splitext(md_file_path)[0]+".docx"
with open(md_file_path,"r", encoding="utf-8") as f:
md_content = f.read()
html_content = markdown.markdown(
md_content,
extensions=['extra','sane_lists','nl2br','codehilite'],
extension_configs={}
)
doc = Document()
doc.styles['Normal'].font.name = '宋体'
doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'),'宋体')
doc.styles['Normal'].font.size = Pt(12)
soup = BeautifulSoup(html_content,"html.parser")
parse_html_node(soup, doc)
doc.save(docx_file_path)
print(f"转换成功!Word 文件已保存至:{docx_file_path}")
def parse_html_node(node, doc):
if node.name in [f'h{i}' for i in range(1,7)]:
level = int(node.name[1])
p = doc.add_paragraph()
run = p.add_run(node.get_text(strip=True))
run.font.size = Pt(20- level *2)
run.font.bold = True
run.font.name = '黑体'
run._element.rPr.rFonts.set(qn('w:eastAsia'),'黑体')
elif node.name == 'p':
p = doc.add_paragraph()
parse_inline_content(node, p)
elif node.name == 'ul':
for li in node.find_all('li', recursive=False):
p = doc.add_paragraph(style='List Bullet')
parse_inline_content(li, p)
elif node.name == 'ol':
for li in node.find_all('li', recursive=False):
p = doc.add_paragraph(style='List Number')
parse_inline_content(li, p)
elif node.name == 'table':
rows = node.find_all('tr')
row_count = len(rows)
col_count = len(rows[0].find_all(['th','td'])) if row_count > 0 else 0
if row_count == 0 or col_count == 0:
return
table = doc.add_table(rows=row_count, cols=col_count)
table.alignment = WD_TABLE_ALIGNMENT.CENTER
for r_idx, row in enumerate(rows):
cells = row.find_all(['th','td'])
for c_idx, cell in enumerate(cells):
tc = table.cell(r_idx, c_idx)
tc.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
p = tc.paragraphs[0]
parse_inline_content(cell, p)
if cell.name == 'th':
for run in p.runs:
run.font.bold = True
elif node.name == 'pre':
code_node = node.find('code')
if code_node:
p = doc.add_paragraph()
run = p.add_run(code_node.get_text())
run.font.name = 'Consolas'
run.font.size = Pt(10)
run.font.color.rgb = RGBColor(0,0,0)
elif node.name == 'img':
img_src = node.get('src','')
img_alt = node.get('alt','图片')
if os.path.exists(img_src):
try:
doc.add_picture(img_src, width=Inches(4))
p = doc.add_paragraph(img_alt)
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
except Exception as e:
doc.add_paragraph(f"图片加载失败:{img_src} | 错误:{str(e)}")
else:
doc.add_paragraph(f"图片不存在:{img_src}(描述:{img_alt})")
for child in node.children:
if child.name:
parse_html_node(child, doc)
def parse_inline_content(node, paragraph):
for content in node.contents:
if isinstance(content,str):
run = paragraph.add_run(content)
run.font.name = '宋体'
run._element.rPr.rFonts.set(qn('w:eastAsia'),'宋体')
run.font.size = Pt(12)
elif content.name == 'strong':
run = paragraph.add_run(content.get_text())
run.font.bold = True
elif content.name == 'em':
run = paragraph.add_run(content.get_text())
run.font.italic = True
elif content.name == 'a':
text = content.get_text()
link = content.get('href','')
run = paragraph.add_run(f"{text}({link})")
if __name__ == "__main__":
INPUT_MD_FILE = "./test.md"
markdown_to_word(INPUT_MD_FILE)
四、使用方法(3 步极简操作)
步骤 1:准备源文件
将需要转换的 Markdown 文件(如 test.md)放在代码同目录下,或填写绝对路径(如 C:/文档/我的笔记.md)。
步骤 2:修改文件路径
在代码末尾的 if __name__ == "__main__": 块中,替换 INPUT_MD_FILE 为你的 MD 文件路径:
INPUT_MD_FILE = "./你的文件.md"
步骤 3:运行代码
直接执行该 Python 文件,终端输出 转换成功! 即完成,转换后的 Word 文件会保存在同目录(默认)或你指定的路径下。
五、关键优化点(解决常见坑)
✅ 坑 1:中文乱码/字体异常
- 解决方案:为所有文字节点指定中文字体(宋体/黑体),通过
qn('w:eastAsia') 强制生效;
- 核心代码:
run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')。
✅ 坑 2:Markdown 语法解析不全
- 解决方案:启用
python-markdown 的扩展库(extra/codehilite),支持表格、代码块、脚注等扩展语法。
✅ 坑 3:列表嵌套/格式错乱
- 解决方案:用
recursive=False 限制列表子节点解析层级,配合 Word 内置的 List Bullet/List Number 样式,保证列表格式统一。
✅ 坑 4:图片加载失败
- 解决方案:增加图片路径校验,若路径不存在则在 Word 中提示错误信息,避免程序崩溃。
六、支持的 Markdown 语法清单
✅ 基础语法(基础版 + 增强版均支持)
- 标题:
# 一级标题 ~ ###### 六级标题
- 段落:自然换行/空行分隔
- 加粗:
**加粗内容**
- 斜体:
*斜体内容*
- 无序列表:
- 列表项 1 / * 列表项 1
- 有序列表:
1. 列表项 1 / 2. 列表项 2
- 超链接:
[链接文本](链接地址)
✅ 扩展语法(仅增强版支持)
- 表格:
| 表头 1 | 表头 2 | + |---|---| + | 内容 1 | 内容 2 |
- 代码块:
python 代码内容
- 图片:

- 换行:
\n 或 <br>
七、备选方案(更轻量化,一行命令转换)
如果需要极简、无代码的转换方案,推荐使用成熟工具 pandoc,比手动开发的脚本支持的语法更全、兼容性更强:
1.安装 pandoc
- Windows:
winget install pandoc
- Mac:
brew install pandoc
- Linux:
sudo apt install pandoc
2.一行命令转换
pandoc -s 你的文件.md -o 输出文件.docx
✅ 优势:支持所有 Markdown 语法(公式、脚注、目录、引用等),无需编写代码,转换速度极快。
总结
- 手动开发方案(python-markdown + python-docx):适合需要自定义转换规则(如字体、样式、格式)的场景,代码可灵活扩展;
- 工具方案(pandoc):适合快速批量转换,无需开发,开箱即用,兼容性最优;
- 核心避坑:处理中文时必须指定中文字体 + UTF-8 编码,解析列表时限制递归层级。
两种方案均可满足 Markdown → Word 的转换需求,可根据实际场景选择。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online