关于 docx-processing
面向通过代码生成、填充和转换 Word 文档的技能,适用于文档自动化流程。它涵盖 python-docx 与 docxtpl,可按需求选择直接创建文档或使用模板。
主要功能
- 01使用 python-docx 创建文档
- 02通过 docxtpl 填充模板
- 03控制样式、表格、页眉和页脚
- 04批量生成邮件合并文档
- 05规划 DOCX 到 PDF 的转换
适用场景
- 01生成格式化报告
- 02用数据填充固定版式模板
- 03批量制作个性化信函
- 04将 Word 文档转换为 PDF
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ pixel-process-ug/ superkit-agents --skill docx-processing安装后在 Claude Code 中输入/docx-processing确认已加载。
详细介绍
DOCX 处理
概述
以编程方式生成、操作和套用 Word 文档模板。本技能涵盖用于直接创建文档的 python-docx、基于 Jinja2 填充模板的 docxtpl、格式控制(标题、表格、图片、页眉/页脚)、邮件合并操作、样式管理和转换策略。
当需要通过代码而非手动编辑来创建、填充或转换 Word 文档时,请使用本技能。
多阶段流程
阶段 1:需求分析
- 确定是从头创建还是填充模板
- 确定文档结构(章节、页眉、表格、图片)
- 定义数据来源(JSON、CSV、数据库、API)
- 规划样式要求(字体、颜色、页边距)
- 确定输出格式(DOCX,是否需要转换为 PDF)
停止 — 在确定方案(从头创建还是使用模板)并确认数据来源之前,不要开始实施。
阶段 2:实施
- 设置文档模板或从头创建文档
- 实现数据绑定和内容生成
- 应用格式和样式
- 添加页眉、页脚和页码
- 处理图片和嵌入对象
停止 — 实现所有文档章节后,才能进入验证阶段。
阶段 3:验证
- 验证文档在 Word/LibreOffice 中的呈现效果
- 检查各页格式是否一致
- 验证生成文档中的数据准确性
- 使用边界情况进行测试(长文本、缺失数据、特殊字符)
- 如有需要,验证 PDF 转换效果
方案选择表
| 场景 | 方案 | 库 | 原因 |
|---|---|---|---|
| 一次性报告生成 | 从头创建 | python-docx | 完全以编程方式控制 |
| 布局固定的定期报告 | 模板 | docxtpl | 在 Word 中设计布局,再填充数据 |
| 批量生成信件(邮件合并) | 模板 | docxtpl | 一个模板,生成多个输出 |
| 复杂格式、自定义样式 | 从头创建 | python-docx | 可直接访问文档模型 |
| 由非技术用户设计模板 | 模板 | docxtpl | 用户在 Word 中编辑,开发者绑定数据 |
| 需要 PDF 输出 | 任一方案 + 转换 | libreoffice / docx2pdf | 后处理步骤 |
python-docx 模式
创建文档
from docx import Document
from docx.shared import Inches, Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
doc = Document()
# Set default font
style = doc.styles['Normal']
font = style.font
font.name = 'Calibri'
font.size = Pt(11)
# Add heading
doc.add_heading('Monthly Report', level=0)
# Add paragraph with formatting
para = doc.add_paragraph()
run = para.add_run('Important: ')
run.bold = True
run.font.color.rgb = RGBColor(0xCC, 0x00, 0x00)
para.add_run('This section requires attention.')
# Add table
table = doc.add_table(rows=1, cols=3, style='Light Grid Accent 1')
hdr_cells = table.rows[0].cells
hdr_cells[0].text = 'Name'
hdr_cells[1].text = 'Department'
hdr_cells[2].text = 'Revenue'
for name, dept, rev in data:
row_cells = table.add_row().cells
row_cells[0].text = name
row_cells[1].text = dept
row_cells[2].text = f'${rev:,.2f}'
# Add image
doc.add_picture('chart.png', width=Inches(5.5))
# Save
doc.save('report.docx')
页眉和页脚
from docx.enum.section import WD_ORIENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
section = doc.sections[0]
# Page setup
section.page_width = Cm(21)
section.page_height = Cm(29.7)
section.left_margin = Cm(2.5)
section.right_margin = Cm(2.5)
section.top_margin = Cm(2.5)
section.bottom_margin = Cm(2.5)
# Header
header = section.header
header_para = header.paragraphs[0]
header_para.text = 'Company Name — Confidential'
header_para.alignment = WD_ALIGN_PARAGRAPH.RIGHT
header_para.style.font.size = Pt(9)
header_para.style.font.color.rgb = RGBColor(0x88, 0x88, 0x88)
# Footer with page numbers
footer = section.footer
footer_para = footer.paragraphs[0]
footer_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
# Add page number field
run = footer_para.add_run()
fldChar = OxmlElement('w:fldChar')
fldChar.set(qn('w:fldCharType'), 'begin')
run._r.append(fldChar)
run2 = footer_para.add_run()
instrText = OxmlElement('w:instrText')
instrText.set(qn('xml:space'), 'preserve')
instrText.text = ' PAGE '
run2._r.append(instrText)
run3 = footer_para.add_run()
fldChar2 = OxmlElement('w:fldChar')
fldChar2.set(qn('w:fldCharType'), 'end')
run3._r.append(fldChar2)
表格格式
from docx.shared import Cm, Pt
from docx.oxml.ns import nsdecls
from docx.oxml import parse_xml
# Set column widths
table.columns[0].width = Cm(4)
table.columns[1].width = Cm(6)
table.columns[2].width = Cm(3)
# Cell shading
for cell in table.rows[0].cells:
shading = parse_xml(f'<w:shd {nsdecls("w")} w:fill="2F5496"/>')
cell._tc.get_or_add_tcPr().append(shading)
for paragraph in cell.paragraphs:
for run in paragraph.runs:
run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
run.font.bold = True
# Cell alignment
for row in table.rows:
for cell in row.cells:
cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
docxtpl 模板模式
模板语法(Jinja2)
Template file (template.docx) contains:
{{ company_name }}
Date: {{ report_date }}
Dear {{ recipient_name }},
{% for item in items %}
- {{ item.name }}: ${{ item.price }}
{% endfor %}
Total: ${{ total }}
{%if urgent %}
URGENT: This requires immediate attention.
{%endif %}
模板渲染
from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm
tpl = DocxTemplate('template.docx')
context = {
'company_name': 'Acme Corp',
'report_date': '2025-03-15',
'recipient_name': 'Alice Johnson',
'items': [
{'name': 'Widget A', 'price': '29.99'},
{'name': 'Widget B', 'price': '49.99'},
],
'total': '79.98',
'urgent': True,
'chart': InlineImage(tpl, 'chart.png', width=Mm(120)),
}
tpl.render(context)
tpl.save('output.docx')
模板中的富文本
from docxtpl import RichText
rt = RichText()
rt.add('Normal text ')
rt.add('bold text', bold=True)
rt.add(' and ')
rt.add('red text', color='FF0000')
rt.add(' with ')
rt.add('a link', url_id=tpl.build_url_id('https://example.com'))
context = {'formatted_text': rt}
模板中的表格
Template table row with loop:
{% tr for row in table_data %}
{{ row.name }} | {{ row.value }} | {{ row.status }}
{% endtr %}
邮件合并
from docxtpl import DocxTemplate
import csv
template = DocxTemplate('letter_template.docx')
with open('recipients.csv') as f:
reader = csv.DictReader(f)
for i, row in enumerate(reader):
context = {
'name': row['name'],
'address': row['address'],
'amount': row['amount'],
'due_date': row['due_date'],
}
template.render(context)
template.save(f'letters/letter_{i:04d}_{row["name"]}.docx')
template = DocxTemplate('letter_template.docx') # Re-load for next iteration
样式管理
自定义样式
from docx.enum.style import WD_STYLE_TYPE
# Create custom paragraph style
style = doc.styles.add_style('CustomHeading', WD_STYLE_TYPE.PARAGRAPH)
style.font.name = 'Arial'
style.font.size = Pt(16)
style.font.bold = True
style.font.color.rgb = RGBColor(0x2F, 0x54, 0x96)
style.paragraph_format.space_before = Pt(12)
style.paragraph_format.space_after = Pt(6)
# Apply custom style
doc.add_paragraph('Section Title', style='CustomHeading')
样式继承
Normal → Heading 1 → Heading 2 → ...
Normal → Body Text → List Paragraph
Normal → Table Normal → Table Grid
转换策略
DOCX 转 PDF
# Option 1: LibreOffice (most reliable, server-friendly)
import subprocess
subprocess.run([
'libreoffice', '--headless', '--convert-to', 'pdf',
'--outdir', output_dir, input_file
])
# Option 2: docx2pdf (Windows/macOS with Word installed)
from docx2pdf import convert
convert('input.docx', 'output.pdf')
# Option 3: Generate PDF directly with reportlab for full control
错误处理
import jinja2
def safe_generate_document(template_path, context, output_path):
try:
tpl = DocxTemplate(template_path)
tpl.render(context)
tpl.save(output_path)
return True
except jinja2.UndefinedError as e:
print(f"Missing template variable: {e}")
return False
except FileNotFoundError as e:
print(f"Template not found: {e}")
return False
except Exception as e:
print(f"Document generation failed: {e}")
return False
反模式 / 常见错误
| 反模式 | 失败原因 | 替代做法 |
|---|---|---|
| 硬编码字号,而不是使用样式 | 格式不一致,难以维护 | 一次定义样式,随处应用 |
| 不处理缺失的模板变量 | 数据不完整时会导致运行时崩溃 | 使用 jinja2.Undefined 或默认过滤器 |
| 表格过大却不分页 | 输出难以阅读,布局错乱 | 将表格拆分到多页,或进行摘要 |
| 使用绝对图片路径 | 在不同环境中无法移植 | 使用相对路径或嵌入图片 |
| 不使用不同版本的 Word 进行测试 | 格式问题可能悄然出现 | 在 Word、LibreOffice 和 Google Docs 中测试 |
| 有 API 可用时仍直接修改 XML | 代码脆弱且依赖版本 | 优先使用 python-docx API 方法 |
| 全部使用直接格式设置,不使用样式 | 无法保持格式一致 | 创建并应用命名样式 |
| 忽略 Unicode 字符 | 生成的文档中会出现乱码 | 使用带重音符号的字符、CJK 字符和符号进行测试 |
| 邮件合并时不重新加载模板 | 首次渲染后输出可能损坏 | 每次迭代都重新实例化 DocxTemplate |
防止自我合理化
- 不要跳过方案决策(从头编写还是使用模板)——这会决定整个实现方案。
- 不要在未至少使用 Word 和另一款查看器进行测试的情况下生成文档。
- 不要忽略缺失数据——应为为空或 null 的字段设置默认值,或使用条件章节。
- 不要在生产环境的文档生成流水线中跳过错误处理。
- 当可以使用样式时,不要硬编码格式。
集成点
| 技能 | 关联方式 |
|---|---|
pdf-processing | DOCX 转 PDF,或直接选择生成 PDF |
xlsx-processing | Excel 中的数据会输入文档生成上下文 |
email-composer | 将生成的文档附加到专业邮件中 |
content-research-writer | 将研究内容排版为白皮书和报告 |
file-organizer | 输出文件命名和目录结构约定 |
deployment | CI/CD 或服务器环境中的文档生成流水线 |
技能类型
灵活 — 根据文档复杂程度,在 python-docx(程序化)和 docxtpl(基于模板)之间进行选择。简单报告可能不需要模板;复杂的重复性文档则适合使用模板。
评论
支持 Markdown