news 2026/9/29 1:21:50

Python自动化:PSCAD波形图批量生成Word报告全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python自动化:PSCAD波形图批量生成Word报告全流程

搞电力系统仿真的朋友应该都有过这种体验:PSCAD里把模型搭好、仿真跑完,波形一个个调出来看,觉得结果没问题了,接下来才是最折磨人的一步——把波形图整理进Word报告。手动截图、裁剪、统一尺寸、调整对齐,再配上图注和说明,一篇项目报告动辄几十张图,光排版就能耗掉大半天。如果中间改了一次参数重新仿真,那整套截图工作基本要重来一遍。今天分享的这套Python自动化流程,就是专门解决这个痛点的。我平时主要做新能源并网仿真,每次出报告都要倒腾几十张波形图,从最早纯粹手工,到后来半自动,再到现在的全流程脚本,算是把这条链路彻底理顺了。整套流程跑下来,从PSCAD仿真结束到Word文档排版完成,5分钟以内可以搞定,WPS上同样能用。

这篇博文会把我的完整方案拆开来讲,包括两条技术路线怎么选、PSCAD侧怎么准备、Word文档怎么批量组装、WPS兼容层怎么写,以及我在实际项目里踩过的坑。适合电力专业的研究生、设计院工程师,以及所有需要高频产出PSCAD仿真报告的人阅读。

1. 内容整体设计与思路拆解

1.1 手动流程的痛点到底在哪

先说一个最直观的场景:你做完了PSCAD仿真,波形窗口里有线路电压、电流、有功功率、无功功率好几个曲线,现在要把它们放到Word里。手动操作通常是这么几步:先逐个激活波形窗口,用截图工具框选图形区域,保存成图片;再打开Word,一张张插入图片,手动把尺寸改成统一宽度,居中对齐;最后在图片下方输入“图3-2 并网点电压波形”之类的图注。听起来不难,但几十张图重复操作下来,枯燥不说,还特别容易出错。尤其是当Word设置里图片默认不是嵌入型,或者上一张图的尺寸没改好,后面的图全跟着歪掉,返工成本很高。

更麻烦的是改参数重仿真的场景。电网模型里一个控制参数从0.5改成0.8,仿真结果全变了,波形图自然得全部重截。手动流程下这意味着把前面所有截图工作重新做一遍,没有任何复用价值。我最初决定做这个自动化脚本,就是被这种重复劳动逼的。

1.2 两条技术路线:截屏方案与数据重绘方案

围绕“把PSCAD波形放进Word”这件事,我试过两条主流路线,分别对应不同的使用场景。

第一条是界面截屏方案。用Python控制鼠标键盘,激活PSCAD的波形窗口,对指定区域截图,再用PIL统一裁剪,最后插入Word。优点是实现简单,所见即所得,PSCAD里波形是什么样,Word里就是什么样;缺点是依赖界面环境,窗口位置变了、系统缩放比例不同、PSCAD主题配色改了,都可能影响截图结果。

第二条是数据重绘方案。PSCAD支持把波形数据导出成CSV或文本文件,然后用Python的matplotlib等库重新画图,最后插入Word。优点是图片质量可控,线条、字体、坐标轴、DPI都能按论文或报告规范统一设置,而且只要数据文件在,随时可以重新生成图片,改参数后重仿真也不怕;缺点是需要额外处理数据列的映射关系,PSCAD导出的数据格式需要熟悉一下。

1.3 方案选型判断标准

我的建议是:如果只是临时出几张波形图,对图的要求不高,选截屏方案,脚本量小、见效快;如果是要写毕业论文、设计报告,或者图表数量大、需要统一风格重绘,一定要选数据重绘方案。很多实际项目是两种混着用——用截屏方案快速预览,用重绘方案出最终报告。这个判断标准我踩过几次坑才总结出来:初期图省事全用截屏,结果到了写论文阶段,几十张图风格不统一,分辨率还不高,最后全部重画一遍,比一开始就用重绘方案更浪费时间。

2. 环境准备:把“地基”打牢

2.1 Python环境与依赖库

无论选哪条路线,Python环境是基础。我推荐用Python 3.9到3.11之间的版本,太新或太老的版本在某些库上容易出兼容性问题。建议先建一个独立的虚拟环境,避免和系统Python、其他项目互相污染。

需要安装的依赖库有以下这些,我按用途列出来:

  • pywin32:提供win32gui、win32com等Windows系统接口,用于窗口查找和Office/WPS调用
  • pyautogui:模拟鼠标键盘操作,截屏方案的核心
  • Pillow:图像裁剪、格式转换、尺寸调整
  • matplotlib、pandas、numpy:数据重绘方案的核心
  • python-docx:直接生成和编辑docx文档,不依赖Word/WPS应用

安装命令很简单,在虚拟环境里执行:

pip install pywin32 pyautogui pillow matplotlib pandas numpy python-docx

这里有个容易忽略的点:pywin32安装后,如果要在命令行下用win32com,有时需要先执行python Scripts/pywin32_postinstall.py -install注册一下,否则某些接口会报错。多花这一步,能省后面很多排查时间。

2.2 PSCAD侧准备与数据导出设置

PSCAD没有公开的Python API,所以自动化的关键是利用它的外部文件接口。先说截屏方案:脚本需要保证波形窗口处于可见且置顶状态。我在实际使用中会在PSCAD里把要导出的波形窗口单独拖到副显示器或屏幕固定位置,这样脚本的截图坐标不会乱。

再说数据重绘方案的关键准备。PSCAD的波形窗口(Curve Window)支持右键菜单中的“Export As”导出数据,常见格式有CSV和文本文件。导出时要注意两点:一是时间轴的列名,PSCAD默认可能是Time或t;二是多个变量导出时会生成多列,列名就是信号路径,比如BRK_Line_Voltage:1这种带前缀的格式,读数据时要想办法把列名清洗掉。实际操作中,我会在PSCAD波形窗口里先只勾选当前关注的曲线,再导出,这样CSV的列结构干净,Python读取时不需要太多复杂的筛选逻辑。

2.3 Word/WPS对象模型基础

如果你之前只接触过手动写Word文档,可能对“对象模型”这个概念比较陌生。简单说,Word和WPS都提供了COM接口,允许外部程序像操作一个对象一样去控制软件本身。Word对应的是Word.Application,WPS文字对应的是Kwps.Application,WPS表格对应的是Ket.Application。这两种接口在基本操作上几乎一致,但细节上存在差异。

我用python-docx比较多,因为这个库直接操作docx文件,不依赖Word或WPS应用是否安装。它的原理是把docx当成一个包含若干XML文件的压缩包,python-docx在内存中解析并修改这些XML,最后重新打包成docx。好处是生成的文档两个Office能顺利打开,坏处是它不能覆盖Word/WPS的所有功能,比如某些高级页面设置和域代码处理就比较费劲。所以在实际项目里,我通常以python-docx为主,碰到它搞不定的高级特性时,再用COM接口通过Word或WPS应用来补一刀。

3. 核心实现:从PSCAD波形到Word文档

3.1 方案A:基于pyautogui的界面截图自动化

先讲最简单、也最容易上手的截屏方案。整体流程分四步:定位波形窗口、激活置顶、区域截图、批量插入Word。窗口定位是第一个难点,PSCAD窗口的标题栏一般包含“Graph”或“Curve”字样,我可以先用win32gui枚举所有可见窗口,找到标题匹配的那一个:

import win32gui def find_window(title_part): result = [] def cb(hwnd, extra): if win32gui.IsWindowVisible(hwnd): text = win32gui.GetWindowText(hwnd) if title_part in text: result.append((hwnd, text)) win32gui.EnumWindows(cb, None) return result # 示例:查找标题带 "Graph" 的窗口 windows = find_window("Graph") for hwnd, text in windows: print(hwnd, text)

找到窗口后,用win32gui.SetForegroundWindow(hwnd)把窗口置前,再调用win32gui.GetWindowRect(hwnd)获取窗口的屏幕坐标范围。窗口置顶后建议等待一两秒,避免界面还没刷新完就截图。截图用pyautogui:

import pyautogui import time rect = (left, top, right, bottom) pyautogui.FAILSAFE = True # 鼠标甩到左上角可紧急停止 time.sleep(0.5) img = pyautogui.screenshot(region=rect) img.save("waveform_raw.png")

实测下来,屏幕缩放比例常常让坐标偏移。Windows如果设了125%或150%缩放,pyautogui拿到的坐标和实际像素对不上。解决办法是在脚本开头加DPI感知,让系统按物理像素返回坐标:

import ctypes try: ctypes.windll.shcore.SetProcessDpiAwareness(1) except Exception: pass

我自己的习惯是:需要截图的波形窗口固定放在主屏幕左侧,并统一窗口尺寸,这样坐标值基本稳定。如果你有副屏,也可以把波形窗口拖到副屏固定位置,但要注意截屏区域不能跨显示器边界。

3.2 共享代码:用PIL统一裁剪与命名

不管哪个方案截完图,后续处理都建议用Pillow统一做一轮裁剪和重命名。波形图的四周通常有空白边框,截图出来如果直接插进Word,视觉上会显得很散。我的做法是先用getbbox()自动去掉四周纯色边缘,再统一设置宽度,并在文件名里加入序号和图号,方便后续插图序。

from PIL import Image im = Image.open("waveform_raw.png") im = im.convert("RGB") # 去掉纯白或纯黑边缘,具体颜色阈值按实际背景调整 bg = im.getpixel((0, 0)) bbox = im.point(lambda p: 255 if p != bg else 0).getbbox() if bbox: im = im.crop(bbox) # 统一宽度为1400像素,高度按比例缩放 width = 1400 ratio = width / im.width im = im.resize((width, int(im.height * ratio)), Image.LANCZOS) im.save("fig_001.png")

3.3 方案B:基于CSV导出的数据重绘

数据重绘方案是我处理正式报告最推荐的做法,它的可复现性比截屏高一个量级。PSCAD导出CSV后,先用pandas读进来,关键是搞清楚列名结构。PSCAD导出的CSV可能是双行头部,第一行是变量描述,第二行是单位或信号名。我一般在读取后做一次列名清洗:

import pandas as pd df = pd.read_csv("case1_line_voltage.csv", skiprows=1) # 去掉列名中的特殊字符和多余空格 df.columns = [c.strip().replace(" ", "_").replace(":", "_") for c in df.columns] print(df.columns.tolist()) print(df.head())

数据清洗完之后,用matplotlib重绘。为了让报告风格统一,我封装了一个画图函数,把字体、线宽、图例位置、坐标轴字号和图片尺寸全部固定下来:

import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = False plt.rcParams["figure.dpi"] = 200 plt.rcParams["savefig.dpi"] = 300 def plot_waveform(time_series, data_series, ylabel, title, output_path): fig, ax = plt.subplots(figsize=(7, 3.2)) ax.plot(time_series, data_series, linewidth=1.0, color="crimson") ax.set_xlabel("时间 (s)", fontsize=9) ax.set_ylabel(ylabel, fontsize=9) ax.set_title(title, fontsize=10) ax.grid(alpha=0.3) ax.tick_params(labelsize=8) fig.tight_layout() fig.savefig(output_path) plt.close(fig)

这里要特别提醒中文字体问题。matplotlib默认字体不包含中文,如果不配置font.sans-serif,图上的“时间”“电压”等文字会变成一个个小方框。上面代码里我已经配了SimHei和微软雅黑,但前提是系统里确实装了这些字体。如果跑在精简版Windows或服务器上,可能需要先把字体文件放到对应目录,再用font_manager.addfont加载。

3.4 批量组装Word文档并统一排版

图片准备好之后,就用python-docx来组装Word。我一般会先生成一个统一风格的报告模板,里面预设好页边距、正文样式和标题样式,Python脚本只需要往模板里追加段落和图片。

插入图片时最需要注意的是图片宽度和对齐方式。python-docx里可以用document.add_picture()直接插入,但默认图片宽度可能很大,所以一定要显式设置width=Cm()。居中对齐要找到图片所在段落,设置alignment为WD_ALIGN_PARAGRAPH.CENTER。另外,Word文档里图片和文字之间最好留一段空白,不然排版会很挤。

from docx import Document from docx.shared import Cm from docx.enum.text import WD_ALIGN_PARAGRAPH doc = Document("report_template.docx") figures = [ ("fig_001.png", "图3-1 并网点电压波形"), ("fig_002.png", "图3-2 并网点电流波形"), ] for img_path, caption in figures: p = doc.add_paragraph() run = p.add_run() run.add_picture(img_path, width=Cm(14)) p.alignment = WD_ALIGN_PARAGRAPH.CENTER cap = doc.add_paragraph() cap.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER cap.paragraph_format.space_after = Cm(0.5) cap.add_run(caption).font.size = Pt(9) doc.save("仿真报告_autogen.docx")

这一段代码看着简单,但在给上百张图片的文档批量插入时非常稳定。唯一要注意的是python-docx往段落里插入图片时,图片是内联在run里的,如果后续还需要在图下面加图注,要新建段落,不要和图片在同一段落里叠加文字。

4. WPS兼容方案详解

4.1 WPS与Word的COM接口差异

很多人的电脑只装了WPS,没有MS Office。python-docx生成的docx文件WPS能正常打开,这一点问题不大。但如果你的脚本里用了win32com来操作Word应用,就需要注意WPS和Word的COM接口名称不一样。

Word的COM入口是Word.Application,WPS文字是Kwps.Application,WPS表格是Ket.Application,WPS演示是Kwpp.Application。我在写工具时,通常会把应用启动封装成一个函数,按顺序尝试多个ProgID:

import win32com.client def create_office_app(): for prog_id in ("Word.Application", "Kwps.Application", "wps.Application"): try: app = win32com.client.DispatchEx(prog_id) app.Visible = True return app, prog_id except Exception: continue raise RuntimeError("未检测到可用的Word或WPS应用")

这里用DispatchEx而不是Dispatch,是因为DispatchEx每次都会启动一个独立的Office进程,脚本结束后可以用app.Quit()干净退出,不容易受之前残留进程影响。

4.2 兼容层封装与自动检测

再进一步,我通常在脚本里加一个“应用类型检测”的环节,拿到ProgID后记录当前是Word还是WPS,后续涉及到两者行为差异的地方,就根据类型走不同的分支。比如设置页面边框、插入页眉页脚这样的操作,WPS和Word在部分参数上会有细微差别。

WPS要支持COM操作,前提是系统里安装了WPS的VBA组件。默认安装WPS时可能不带这个组件,需要在WPS安装目录的“加载项”或“组件”选项里手动勾选“VBA for WPS”。没有这一步的话,Kwps.Application会创建失败。这个点很容易被忽略,我帮同事部署脚本时就卡在这里半小时,最后发现是WPS没装VBA组件。

4.3 样式与排版需要避开的坑

先说图片格式。PSCAD或matplotlib生成的图片,建议统一用PNG格式。Word和WPS对PNG支持都很好,尽量不要用EMF或WMF这些矢量格式,虽然在Word里表现不错,但WPS打开时偶尔出现渲染异常或尺寸错乱。如果你实在需要矢量图,也要把图片以兼容模式插入后再检查一遍。

再说字体。Word里常用的“等线”“微软雅黑”在WPS里一般没问题,但某些特殊的西文字体,比如“Arial Unicode MS”,WPS里可能找不到替代字体,导致排版回退、文字偏移。最稳妥的做法是脚本里只设置中文字体名,并且在中文字体名后接着设置一个通用西文字体,比如SimSun配Times New Roman,这样WPS渲染时不会乱。

还有一个容易被忽略的是纸张大小和页边距。python-docx设置页边距时,用的单位如果没有转成厘米,直接填了数值,很可能被当成Twips或Point处理,导致WPS里页边距异常。我建议统一用Cm()包装。

5. 常见问题与排查实录

5.1 PSCAD窗口定位失败

症状是find_window("Graph")返回空列表。排查方向有三个:一是PSCAD窗口的标题里不是“Graph”,而是具体的波形名称,比如“Line Voltage”或“Case1_Curve”,可以先把所有可见窗口标题打印出来,肉眼确认后再改匹配关键字;二是PSCAD波形窗口最小化到了任务栏,IsWindowVisible会返回False,需要先恢复窗口;三是脚本权限不够,PSCAD以管理员身份运行,而Python终端是普通权限时,某些窗口属性读不到,建议两边都以管理员权限启动。

5.2 坐标偏移与DPI缩放

坐标偏移最典型的表现是截图区域偏左上或偏右下,截出来的图画面不完整。这是系统缩放比例导致的。Windows默认150%缩放时,pyautogui的坐标基准和物理屏幕像素不一致。解决方法是在脚本开头调用SetProcessDpiAwareness(1),让pyautogui按物理像素工作。另外,如果你的PSCAD窗口不是标准Windows标题栏,而是自绘边框,GetWindowRect返回的坐标有时会包含阴影边框,截图时需要多裁剪几个像素。

5.3 matplotlib中文乱码

画图时中文变方框,是因为当前matplotlib配置中没有可用的中文字体。临时解决方法是:

from matplotlib import font_manager font_path = "C:/Windows/Fonts/simhei.ttf" font_manager.fontManager.addfont(font_path) plt.rcParams["font.sans-serif"] = ["SimHei"]

一劳永逸的做法是把常用中文字体路径加入matplotlib的font_manager中,或者干脆在配置文件里把默认字体改成Microsoft YaHei。另外,别忘了设置axes.unicode_minus=False,否则坐标轴负号会变成乱码方块。

5.4 图片清晰度与Word文档体积

有人用matplotlib画图时把dpi设成100,图插入Word后屏幕上看着还行,一打印或者放大就糊了。我的经验是保存时dpi至少设置为200,插入Word时再把图片宽度统一为14厘米左右,这样既能保证打印清晰,又不会因为分辨率过高导致docx文件动辄几十MB。如果确实图片非常多,可以先统一把PNG转成JPG再插入,体积能下降不少,但要注意透明背景会被填成黑色。

5.5 WPS打开docx后样式丢失

最典型的是页边距变了,或者图片位置偏移。这通常是python-docx生成的oxml里某些属性和WPS解析器不兼容。一个有效规避方法:用python-docx生成文档后,不要急着收工,先用脚本里的create_office_app()打开它,再用COM方式“另存为”一次docx,强制WPS/Word把文件重新规范一遍。这个操作并不复杂,但能明显减少不同Office版本间打开跳格式的问题。

5.6 脚本在别人电脑上跑不起来

我遇到过不少场景,脚本在自己电脑上一切正常,拷到同事实机上却各种报错。最常见的三个原因:Python环境没装全、系统没有PSCAD(或PSCAD版本不同导致波形窗口标题变了)、WPS没有VBA组件。我建议把脚本做成交互式配置的形式,首次运行时让用户填PSCAD窗口标题关键字、图片输出目录、Word模板路径,并列出自检清单,这样换电脑时容易排查。

6. 我的一些额外建议

最后说几个我自己实际项目里沉淀出来的小习惯。

一是图片和Word模板一定要用绝对路径。刚开始我用相对路径,脚本和文件放在同一个目录下,自己跑没问题;后来放到Windows任务计划程序里做定时批量出报告,发现经常找不到文件,排查半天才意识到是任务计划程序的默认工作目录不是脚本所在目录。从那以后,所有路径都用pathlib.Path拼接绝对路径,再也没出过这种低级问题。

二是批量处理前先跑一次“预演模式”。我脚本里会加一个dry_run=True的参数,在这种模式下只打印将要处理的文件清单和插入位置,不真正操作PSCAD、不写Word文件。第一次在陌生电脑上跑时,先预演一遍,确认流程和预期一致再全自动执行,能省很多调试时间。

三是把PSCAD数据文件的整理当成一个独立环节。导出的CSV文件,命名规则越规范越好,比如case1_2024_abc_start.csv这种带场景、时间、工况的格式。后面重绘脚本只要按文件名规律批量读取,就能自动生成整套报告配图,这才是数据重绘方案最大的价值——图和报告可以完全分离管理。

这套流程我用了大半年,最大的感受是:把重复劳动交给脚本之后,节省下来的时间可以用来反复检查仿真结果的合理性,而不是把精力浪费在画图排版上。希望这篇分享对你有帮助。如果你也在做电力仿真报告,遇到什么新的坑,欢迎在评论区交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 1:20:44

Excel VBA实现按行拆分生成独立xlsx文件

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 1:20:44

STM32F103开发板入门指南:从工具链选择到点灯与进阶路线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 1:20:29

PDF考题变协议沙盒:用网络试题驱动TCP/BGP/OSPF实操验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 1:19:29

WorkBuddy数字机器人实战:从环境搭建到自动化任务全流程指南

2. 六步走通:New Recruit 从接入到投稿的完整流程既然你已经清楚 New Recruit 能干什么、以及它背后靠什么逻辑在想问题,那么接下来进入到最关键的实操环节。这一节我会把从零开始接入一个“AI 机器人同事”的完整流程拆成六步,每一步都给出可…

作者头像 李华
网站建设 2026/9/29 1:19:16

C# Winform库存系统源码实战:从跑通到部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华