python-docx实战:自动生成房地产可行性研究报告
2026/9/18 14:30:25 网站建设 项目流程

简介:文档为房地产从业者、投资决策者和可研报告编写人员提供了一份完整的花园住宅项目可行性研究实例。内容覆盖申报单位概况、项目背景、建设规模与户型配置、投资估算与资金筹措、经济效益评价、节能节水、征地拆迁及环境生态影响等模块,并结合南宁市隆安县华侨经济开发区的实际地块条件,给出容积率、绿地率、建筑密度等关键指标。资源包共1个docx文件,大小仅70KB,便于直接查阅和编辑复用。文档以隆安市场调研和政策依据为起点,到研究结论与建议结束,完整呈现可研报告的目录体系、编制思路和常用经济数据框架,可帮助读者快速掌握房地产项目投资评估与风险分析方法。目前已有63人学习下载,适合用于同类项目报告撰写、教学示范或内部评审参考。

1. 从《某房地产花园项目可行性研究报告.docx》看一套可落地的报告生成方案

一份《某房地产花园项目可行性研究报告.docx》在投拓部门往往意味着一整周的体力活:市场数据贴进 Word,投资估算放在 Excel,财务评价又是另一套表,最后人工把图表和单元格拷进几十页的文档里。做过一次的人都清楚,真正耗时间的不是分析,而是排版和对数。对 IT 从业者来说,这份 docx 不应该是一个静态结果,而是一条可以由数据驱动生成的技术链路。

把报告章节拆成模板,把地块指标、成本参数、销售均价存进数据表,再用脚本计算财务指标、渲染图表并写入 Word,可行性研究报告就能从反复复制粘贴变成可批量产出的报表产品。下面按"拆结构、算数据、生成文档、批量复用"的顺序,讲清楚一套不需要购买商业插件、直接用 Python 就能实现的方案。适合正在做投拓系统、Office 文档批处理和报表自动化的一线工程师参考。

2. 拆解可行性研究报告的固定骨架:从调研结论到现金流模型

2.1 报告里哪些内容可以固化成模板结构

做 docx 自动化第一步是先做减法。拿三份不同地块的房地产花园项目报告并排比对,章节标题、说明性段落、表格列名都一样的部分基本就是固定骨架。通常一本可行性研究报告的主干是项目概况、市场分析、建设规模与规划方案、建设进度、投资估算、资金筹措、财务评价、风险分析、结论与建议。这个顺序在大多数项目里保持稳定,真正变化的只有区位描述、具体参数和财务结论。

我一般会把报告结构拆成一张映射表,左边写固定内容,右边写动态字段,并且把字段名和后续数据字典里的键严格统一。这样模板维护成本很低,市场分析那段文字无论怎么改,都不影响代码逻辑,只要保证字段渲染时能拿到值就行。

报告章节固定内容动态字段自动化方式
项目概况项目名称、区位模板句地块面积、容积率、车位比占位符替换
市场分析市场环境描述模板周边均价、去化周期、竞品列表字典渲染
投资估算成本科目说明文字土地费、建安费、开发期费用pandas 计算后填表
财务评价评价方法说明文字NPV、IRR、回收期、利润率Python 计算并生成图表
风险分析风险应对模板句敏感性因素、价格波动区间条件判断生成段落
结论建议标准结论句是否可行、建议内容分支逻辑输出

提示:字段命名尽量带上单位后缀,例如area_m2cost_wanprice_wan_m2,避免在 docx 里出现"面积 38000 / 平方米"这类单位错乱。

2.2 把报告内容抽象成字段字典,而不是直接拼字符串

在写 python-docx 之前,先要把整份报告文档抽象成一张字段字典。模板里的每个可变位置对应一个 key,比如project_nameland_areaplot_ratiototal_investment。字段值不一定是单个数字,也可能是一段由条件生成的多行结论。这种做法的可维护性比按行拼接 Word 段落好很多:数字算错了直接查数据源,不用逐个打开 Word 找错。

下面是一个最小化的数据模型示例,用 dataclass 把报告参数汇总成可供模板替换的字典:

from dataclasses import dataclass, field from typing import Dict @dataclass class GardenProjectReport: project_name: str = "某房地产花园项目" land_area_m2: float = 38000.0 # 净用地面积,单位平方米 plot_ratio: float = 2.0 # 容积率,用于计算计容建筑面积 total_construction_m2: float = 0.0 # 计容总建筑面积,由面积*容积率得出 land_cost_wan: float = 25000.0 # 土地成本,单位万元 construction_cost_wan: float = 56000.0 # 建安成本,单位万元 average_price_wan_m2: float = 1.65 # 销售均价,单位万元/平方米 def calc_total_area(self) -> float: self.total_construction_m2 = self.land_area_m2 * self.plot_ratio return self.total_construction_m2 def to_field_dict(self) -> Dict[str, object]: return { "project_name": self.project_name, "land_area": f"{self.land_area_m2:,.0f}", "plot_ratio": f"{self.plot_ratio:.2f}", "total_construction_m2": f"{self.total_construction_m2:,.0f}", "average_price": f"{self.average_price_wan_m2:.2f}", }

land_area_m2存净用地面积,plot_ratio是容积率,二者相乘得到计容总建筑面积。land_cost_wanconstruction_cost_wan统一用万元,避免后续和元混用。to_field_dict里用格式化字符串输出,:,表示千分位,.0f表示不保留小数。最终这个字典就是模板替换和表格填充的数据源。

2.3 模板字段与数据字典的一致性检查

字段字典设计好后,最容易出问题的环节是模板和字典对不上。报告里写了{{ total_investment }},但代码里返回的 key 是total_invest_wan,最后生成的 Word 里就会残留占位符。常见做法是在渲染前做一次键名校验:把模板里所有占位符提取出来和字典的 keys 做差集,差集为空再继续生成。

import re def check_placeholders(template_text: str, data: dict) -> None: placeholders = set(re.findall(r"\{\{\s*(\w+)\s*\}\}", template_text)) missing = placeholders - set(data.keys()) if missing: raise ValueError(f"缺失字段: {missing}")

这段代码用正则从模板文本中提取所有双花括号变量,然后和数据字典的键做差集。只要出现missing不为空就直接抛异常,避免把一份带着{{xxx}}的破损报告交给业务方。这个检查函数我会放在每次渲染入口的第一步。

3. 用 pandas 算出投资估算与财务指标,再转成报告数据

3.1 成本科目表:一行一个费用项

投资估算表在可行性研究报告中通常是按成本科目展开的。数据建模时建议用 pandas 的二维表结构,每一行是一个成本科目,列包括科目名称、金额、备注,后续既能生成报告表格,也能直接喂给 matplotlib 画图。

import pandas as pd cost_items = [ {"cost_item": "土地费用", "amount_wan": 25000, "note": "含土地出让金及契税"}, {"cost_item": "前期工程费", "amount_wan": 3000, "note": "勘察设计、报批报建"}, {"cost_item": "建筑安装工程费", "amount_wan": 56000, "note": "含主体工程及安装"}, {"cost_item": "基础设施费", "amount_wan": 4200, "note": "道路、管网、绿化、景观"}, {"cost_item": "开发期费用", "amount_wan": 6800, "note": "管理费用、销售费用、财务费用"}, ] cost_df = pd.DataFrame(cost_items) cost_df["ratio"] = cost_df["amount_wan"] / cost_df["amount_wan"].sum() print(cost_df.round(4))

amount_wan这一列所有金额以万元为单位,ratio列在构造后是每项成本占总投资的百分比。这里先算占比再进 Word,比生成文档时临时除以总数更安全。以后如果要调整某一项成本,只要改这个列表里的值,表格和图表会自动跟着变。

3.2 销售收入、利润、NPV 与 IRR 的计算口径

财务评价部分的逻辑比成本表稍复杂,需要同时看静态指标和动态指标。常见做法是先用可售面积乘以去化均价得到总收入,再用总收入减总投资得到利润总额。现金流模型则把项目生命周期的投入和回款按年份排列,计算净现值 NPV 和内部回报率 IRR。

import numpy_financial as npf # 基础指标 saleable_ratio = 0.92 saleable_area_m2 = 76000 * saleable_ratio # 可售面积 total_revenue_wan = saleable_area_m2 * 1.65 # 总收入,约 = 11.5亿 total_invest_wan = cost_df["amount_wan"].sum() # 总投资,约 = 9.5亿 profit_before_tax_wan = total_revenue_wan - total_invest_wan profit_rate = profit_before_tax_wan / total_revenue_wan # 简化现金流:前两年投入为主,后两年去化回款 cash_flow = [ -total_invest_wan * 0.50, # 第1年支出土地及前期费用 -total_invest_wan * 0.30 + total_revenue_wan * 0.15, -total_invest_wan * 0.20 + total_revenue_wan * 0.45, total_revenue_wan * 0.40, # 第4年清盘 ] npv = npf.npv(0.08, cash_flow) # 折现率8%的净现值 irr = npf.irr(cash_flow)

npf.npv(0.08, cash_flow)的第一个参数是折现率,第二个参数必须从第 1 年开始排列,不要把第 0 期单独列进去,否则会多算一期折现。npf.irr返回的是小数形式,后面格式化时需要转成百分比。这个简化模型里假设第 2 年开始有回款,实际项目如果预售节点靠后,需要调整cash_flow中的系数。

注意:numpy_financial和旧版的numpy.financialAPI 一致,但新环境建议直接pip install numpy-financial,避免依赖已经被移除的模块。

3.3 把计算结果整理成渲染用数值字典

pandas 计算完的是 DataFrame,Word 里需要的却是已经格式化过的字符串。因此最后一步是把数值统一转成报告语言:金额带千分位、占比显示百分比、NPV 保留整数、IRR 保留两位小数。

total_invest_wan = cost_df["amount_wan"].sum() report_data = { "total_revenue_wan": f"{total_revenue_wan:,.0f}", "total_revenue_yi": f"{total_revenue_wan / 10000:.2f}", "total_invest_wan": f"{total_invest_wan:,.0f}", "profit_before_tax_wan": f"{profit_before_tax_wan:,.0f}", "profit_rate": f"{profit_rate:.2%}", "npv_wan": f"{npv:,.0f}", "irr": f"{irr:.2%}", }

这里把原始数值和格式化后字符串区分开,原始数值继续用于后续敏感性分析,格式化后的字符串直接填入 docx。.2%会把小数值自动乘以 100 并补两位小数,比手动* 100再拼接%更不容易出错。报告里涉及"亿元"口径时,用total_revenue_wan / 10000转单位。

4. 用 python-docx 把图表和表格渲染进《某房地产花园项目可行性研究报告.docx》

4.1 环境准备与中文字体处理

生成 docx 最核心的依赖是python-docx,图表用matplotlib,数据计算用pandas和前面提到的numpy-financial。安装命令如下:

pip install python-docx pandas matplotlib numpy-financial

python-docx 默认样式在中文环境下需要额外设置字体,否则打开 Word 会出现中文字体不生效的情况。关键是同时设置w:eastAsia属性:

from docx import Document from docx.shared import Pt from docx.oxml.ns import qn doc = Document() normal = doc.styles["Normal"] normal.font.name = "Times New Roman" normal.font.size = Pt(12) normal.element.rPr.rFonts.set(qn("w:eastAsia"), "宋体")

w:eastAsia是 Word 中文字体的内部属性名,只设font.name只改变了西文字体,中文仍会落到默认主题字体上。这里的qn("w:eastAsia")是把命名空间字符串转成 lxml 可识别的 QName。

4.2 报告生成辅助函数:标题、段落、表格

报告里的标题层级和正文段落会在多个章节重复出现,所以先封装四个小函数:添加标题、添加正文、添加表格、添加图片。函数越短越好,目的是在后续生成时不用重复控制样式。

def add_heading(doc, text, level=1): doc.add_heading(text, level=level) def add_para(doc, text, bold=False): p = doc.add_paragraph() run = p.add_run(text) run.bold = bold return p def add_data_table(doc, headers, rows): table = doc.add_table(rows=len(rows) + 1, cols=len(headers)) table.style = "Light Grid Accent 1" for i, header in enumerate(headers): table.cell(0, i).text = header for r, row in enumerate(rows, start=1): for c, value in enumerate(row): table.cell(r, c).text = str(value) return table

add_data_tableLight Grid Accent 1是 python-docx 内置的带边框表格样式,行数列数由len(rows)动态计算。如果模板里已经插入了目录,后续新增章节需要同步刷新目录,这个在最后一部分会处理。

4.3 插入投资估算表与财务指标表

表头和数据来自前文的cost_dfreport_data。投资估算表直接遍历 DataFrame 的每一行,把科目、金额、占比、说明写入 Word 表格:

headers = ["成本科目", "金额(万元)", "占比", "说明"] table_rows = [ [row["cost_item"], f"{row['amount_wan']:,.0f}", f"{row['ratio']:.1%}", row["note"]] for _, row in cost_df.iterrows() ] add_data_table(doc, headers, table_rows)

财务指标表不需要遍历 DataFrame,直接把报告数据字典里的格式化字符串取出。这种写法适合指标数量固定、每项只有一行的情况,例如:

投资利润率:17.5% 税后净现值:xxxxx万元 内部收益率:xx.xx%

实际业务中财务指标表还会有"基准收益率""动态投资回收期"等行,直接在列表里追加(指标名, 值)元组即可。

4.4 用 matplotlib 生成投资结构图并插入 docx

报告正文里需要"投资估算构成图"和"敏感性分析图"。图形先用 matplotlib 保存成 PNG,再通过doc.add_picture插入。关键是中文字体设置,否则图里中文会变成方框。

import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False fig, ax = plt.subplots(figsize=(6, 4)) ax.pie( cost_df["amount_wan"], labels=cost_df["cost_item"], autopct="%.1f%%", startangle=90, counterclock=False, ) ax.set_title("投资估算构成图") plt.tight_layout() plt.savefig("investment_structure.png", dpi=150) plt.close(fig) doc.add_picture("investment_structure.png", width=Cm(12)) doc.paragraphs[-1].alignment = WD_ALIGN_PARAGRAPH.CENTER

startangle=90让第一个扇区从 12 点方向开始,counterclock=False顺时针排布扇区,这样阅读习惯更自然。dpi=150是印刷质量下限,后续如果要压缩 docx 体积可以降到 100。插入图片后doc.paragraphs[-1]是 add_picture 自动创建的段落,直接把它设为居中即可。

4.5 生成后校验:检查残留占位符和空段落

渲染完成后不要直接交付,先重新打开 docx 检查一遍。最常见的两个问题:字段字典缺 key 导致{{xxx}}残留在正文里,以及表格列宽异常导致出现大量空段落。

def verify_docx(path): check_doc = Document(path) all_text = "\n".join(p.text for p in check_doc.paragraphs) for table in check_doc.tables: for row in table.rows: for cell in row.cells: all_text += cell.text + "\n" if "{{" in all_text or "}}" in all_text: raise ValueError("docx 中存在未替换占位符") print(f"校验通过: {path}, 段落数 {len(check_doc.paragraphs)}, 表格数 {len(check_doc.tables)}")

把段落文本和每个表格单元格文本都拼进同一个字符串,再检查双花括号。这个函数在生成流程末尾调用,有错误就抛异常,保证移交到业务方手里的 docx 一定是干净可读的。

5. 多方案批量出稿与 docx 目录自动刷新

5.1 一个循环生成多个方案的可行性研究

同一块地通常要做容积率高低两个方案对比,不同方案只是容积率和楼面价不同。把生成报告的逻辑包成函数后,批量出稿就变成循环:

def generate_report(project_name, land_area, plot_ratio, price): report = GardenProjectReport(project_name=project_name, land_area_m2=land_area, plot_ratio=plot_ratio, average_price_wan_m2=price) report.calc_total_area() data = report.to_field_dict() # 计算成本、财务指标、渲染docx doc = build_docx(data) doc.save(f"{project_name}-可行性研究报告.docx") for scheme in [ {"name": "某房地产花园项目-A方案", "land": 38000, "ratio": 2.0, "price": 1.65}, {"name": "某房地产花园项目-B方案", "land": 38000, "ratio": 2.5, "price": 1.70}, ]: generate_report(**scheme)

每个方案是独立 docx,互不影响。如果方案数量多,建议再加一步把各方案的 IRR、NPV 汇总成对比表,直接在项目级报告中作为附件插入。

5.2 给 Word 加目录域,打开时自动更新

可行性研究报告动辄几十页,目录是必需项。python-docx 没有现成的"插入目录"方法,需要通过底层 XML 写入 TOC 域代码。TOC 域的原理是让 Word 在打开文档时根据标题样式重新扫描页码。

from docx.oxml import OxmlElement from docx.oxml.ns import qn def add_toc_field(doc): paragraph = doc.add_paragraph() fld_begin = OxmlElement("w:fldChar") fld_begin.set(qn("w:fldCharType"), "begin") instr = OxmlElement("w:instrText") instr.set(qn("xml:space"), "preserve") instr.text = r'TOC \o "1-3" \h \z \u' fld_separate = OxmlElement("w:fldChar") fld_separate.set(qn("w:fldCharType"), "separate") placeholder = OxmlElement("w:t") placeholder.text = "右键更新目录" fld_end = OxmlElement("w:fldChar") fld_end.set(qn("w:fldCharType"), "end") run = paragraph.add_run() run._r.extend([fld_begin, instr, fld_separate, placeholder, fld_end])

完成插入后还要追加updateFields设置,让 Word 首次打开文档时主动弹出"是否更新目录"并重排页码:

settings = doc.settings.element update_fields = OxmlElement("w:updateFields") update_fields.set(qn("w:val"), "true") settings.append(update_fields) doc.save("某房地产花园项目可行性研究报告.docx")

updateFields加在doc.settings.element根节点上,key 为w:updateFields,Value 为布尔字符串。这个配置只对 Microsoft Word 客户端生效,LibreOffice 打开时不一定自动重算,仍需手动按F9更新目录。如果用户环境主要是 WPS,建议在交付说明里写一句"打开后按 F9 或右键更新目录"。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询