做工业组态这些年跟 WinCC 打交道最多的需求里“把历史数据弄到 Excel 里做报表”绝对排得上前三。操作员每天班次结束要导数据工程师要看趋势分析管理层要生产日报Excel 几乎是绕不开的终点站。但问题在于WinCC 自带的导出功能要么格式固定、要么需要手动操作数据量一大查询起来还特别慢。这篇就分享一下我在现场落地的一套全脚本方案用 VBS 脚本在 WinCC 和 Excel 之间完成数据导出、自动查询和定时调度重点说清楚怎么把查询速度从“肉眼可见的等”优化到“基本无感”。这套方案不需要额外装数据库、不需要买授权只需要 WinCC 自带的脚本环境就能跑起来。适合三种人看一是现场做设备维护、天天要导报表的工程师二是做产线数字化改造、想快速实现数据自动查询的项目实施人员三是刚接触 WinCC 脚本、想搞明白 WinCC 和外部程序怎么打交道的学习者。下面我会把需求拆解、方案选型、脚本实现、速度优化和踩坑实录全部写出来代码可以直接抄但更重要的是把每一步为什么这么做讲透。1. 场景与需求拆解工控报表为什么这么难做1.1 现场真实需求从手动导数据到自动查询先说一个我接触过的典型场景。某制造厂有一条灌装产线PLC 采集温度、压力、流量、转速这些过程参数全部进 WinCC 做画面监控和归档。以前操作员做班报的流程是下班前一小时打开 WinCC 归档控件手动选择时间范围拖出几十个变量等数据加载完再导出成 CSV最后打开 Excel 手动整理。遇上设备报警多的班次光整理一条报警记录就要反复筛选。这个流程最大的问题不是慢而是每一步都可能出错。时间范围选错了要重来变量漏选了要重来CSV 打开后编码不对要重新转换好不容易整理完Excel 里用 VLOOKUP 匹配设备编号的时候几万行数据能把整个表格卡住。更麻烦的是这种操作高度依赖操作员的个人习惯换个新人过来半天都导不出一份合格的报表。所以现场真正的需求不是“导出数据”而是“自动查询”。操作员打开 WinCC 画面点击一个按钮数据自动从归档里取出来按照预设条件过滤、匹配、计算最终生成一份可以直接使用的 Excel 报表。查询过程中哪怕数据量有几万行也要在几十秒乃至几秒内完成不能让人守着电脑干等。这就是这套全脚本方案要解决的核心问题。1.2 三条技术路线怎么选DDE/OPC、数据库中间表、全脚本WinCC 和 Excel 交互行业内常见的技术路线有三条我做个对比你就知道为什么最终选了全脚本方案。技术路线实现方式优点缺点适合场景DDE/OPC 通信WinCC 作为 OPC ServerExcel 通过 DDE 或 OPC 客户端读取实时值实时性好点位较少时稳定配置繁琐历史数据查询能力弱大批量数据传输几乎不可用少量点位实时显示在 Excel 表格中数据库中间表WinCC 归档数据定时同步到 SQL Server 或 MySQLExcel 通过 ODBC/ADO 查询数据量大时依然高效支持复杂 SQL可扩展性强需要额外部署数据库涉及账号权限、网络配置、数据同步任务实施成本高企业级数据平台、跨系统数据共享、海量历史数据全脚本方案WinCC VBS 脚本直接创建 Excel 对象或通过 ADO 把 Excel 当作数据库查询零额外部署实施快灵活度高查询速度可优化到秒级数据量超过几十万行后性能下降不适合并发访问单机或小规模产线的自动报表与条件查询我最终选全脚本方案核心原因只有一个在需求不明确、IT 环境不复杂的现场多引入一个数据库组件就意味着多一个故障点。全脚本方案完全跑在 WinCC 所在的工控机上部署脚本、添加按钮、配置触发器就能上线。而且对于绝大多数需要“查询数据做报表”的场景数据量都在几万行的量级全脚本方案经过优化后完全可以胜任。2. 方案设计与脚本链路选型2.1 WinCC 脚本环境的能力边界VBS 与 C 脚本怎么分工WinCC 提供两套脚本体系VBS 脚本和 C 脚本。很多刚接触的人容易混淆我简单讲清楚。C 脚本语法类似 C 语言运行效率高适合做画面对象联动、数值计算、直接操作 WinCC 内部接口这类底层工作。VBS 脚本基于 Visual Basic 的语法最大的优势是能通过 COM 接口和外部 Windows 程序交互尤其是 Office 组件。所以当目标明确是“和 Excel 打交道”时VBS 是更顺手的工具。它可以用 CreateObject(Excel.Application) 直接拉起一个 Excel 进程也可以用 CreateObject(ADODB.Connection) 把 Excel 当作数据源来查询。这两条路 C 脚本也能走但字符串处理、对象调用远没有 VBS 来得直观。我的建议是画面动作、数值逻辑用 C 脚本数据交互、文件操作、Excel 处理统一用 VBS。WinCC 的 VBS 环境里有两个常用入口HMIRuntime.Tags(标签名) 用于读写 WinCC 变量ScreenItems(对象名) 用于读写画面上的 IO 域、文本对象等。这两个接口在按钮事件脚本里可以直接使用非常方便。后面所有脚本都会围绕这两个入口展开。2.2 Excel 交互的两条路对象模型与 ADO 查询VBS 操作 Excel有两种底层实现方式很多项目里混着用却没想明白区别我在这里把边界划清楚。第一种是 Excel 对象模型也就是代码里创建 Excel.Application 对象通过 Workbooks.Open 打开文件再通过 Cells、Range、Worksheets 等对象逐单元格读写数据。这种方式的优势是功能全面可以设置单元格格式、合并单元格、插入公式、绘制图表几乎能实现人手在 Excel 里能做的一切操作。缺点是逐单元格读写时脚本和 Excel 之间要反复进行 COM 跨进程通信数据量一大速度就会急剧下降。第二种是 ADO 查询把 Excel 文件当作一个轻量级数据库用 SQL 语句直接查询。连接字符串指向 Excel 文件路径表名就是工作表名加 $ 符号例如 Sheet1$。SQL 里的 WHERE 条件可以在数据源端完成过滤脚本拿到的已经是筛选后的结果集。这种方式的读取速度远快于逐单元格循环尤其适合“按照条件查一批数据”的场景。我在这套方案里的策略是“查询用 ADO报表用对象”。需要按条件抓数据的时候走 ADO秒级返回需要生成带格式的报表时再用对象模型把查询结果直接写入并排版。两者各有分工才能兼顾速度和效果。2.3 提高查询速度的三个底层原则既然标题里写了“提高数据查询速度”我提前把优化思路的核心讲明白后面第四部分的具体代码都是围绕这三条展开的。第一条减少进程间通信次数。每访问一次 Excel 的 Cells 对象脚本和 Excel 之间就发生一次跨进程调用。几万行数据逐行读写就是几万次甚至几十万次调用能不慢吗优化方式是复用 Excel 进程、用数组批量读写、把所有操作集中在一个脚本里完成。第二条把过滤动作下推到数据源。能写进 SQL WHERE 子句的条件绝不要在脚本里用循环判断。数据库引擎这里指 Excel 的查询引擎在数据源端过滤脚本拿到的是已经筛干净的结果集处理量可能只有原来的百分之一。第三条为高频匹配字段建立内存索引。VBS 里可以用 Scripting.Dictionary 对象把“关键字段 → 行号/记录”一次性加载到内存。之后每次查询都是 O(1) 的字典查找比在 Excel 单元格区域里来回扫描快几个数量级。3. 脚本实现细节三个核心模块完整拆解3.1 工程准备按钮、变量与脚本挂接开始写脚本之前先规划好 WinCC 侧的界面和变量。以自动查询报表为例我通常在画面里放几个 IO 域用于输入查询条件比如批次号、设备编号、开始时间、结束时间再放几个 IO 域用于显示查询结果比如合格数、不合格数、平均温度最后放一个按钮按钮的“鼠标点击”事件里调用 VBS 脚本。按钮事件的挂接方式很简单在 WinCC 图形编辑器中选中按钮打开“事件”页签找到“鼠标按下”或“鼠标左键单击”事件选择 VBS 动作然后在编辑器中粘贴脚本。这里要注意按钮事件里的脚本可以直接使用 ScreenItems(名称) 和 HMIRuntime.Tags(名称)不需要额外的变量声明。如果要做定时自动执行就不用按钮了改在 WinCC 的“全局脚本”里创建动作。全局脚本动作可以设置触发器比如“每天 1630 执行”或者“每 5 分钟执行一次”。全局脚本里访问变量都用 HMIRuntime.Tags不需要依赖画面对象。3.2 模块一读取 WinCC 数据写入 Excel 并建立索引第一个模块负责把 WinCC 里的数据写入 Excel。我这里给出一段完整的可参考脚本它读取若干 WinCC 变量的值加上当前时间戳写入指定 Excel 文件的新工作表中。注意脚本里的变量名和文件路径需要按实际项目修改。Option Explicit Dim objExcel, objWorkbook, objSheet Dim oTagTemp, oTagPressure, oTagSpeed Dim strFilePath Dim dblTemp, dblPressure, dblSpeed Dim strTime strFilePath D:\PlantData\DailyReport.xlsx --- 读取 WinCC 变量 --- Set oTagTemp HMIRuntime.Tags(AI_Temperature) Set oTagPressure HMIRuntime.Tags(AI_Pressure) Set oTagSpeed HMIRuntime.Tags(AI_Speed) dblTemp CDbl(oTagTemp.Read) dblPressure CDbl(oTagPressure.Read) dblSpeed CDbl(oTagSpeed.Read) strTime Now --- 创建 Excel 对象 --- Set objExcel CreateObject(Excel.Application) objExcel.Visible False objExcel.DisplayAlerts False Set objWorkbook objExcel.Workbooks.Open(strFilePath) Set objSheet objWorkbook.Worksheets(Record) --- 追加一行数据 --- Dim iNextRow iNextRow objSheet.UsedRange.Rows.Count 1 objSheet.Cells(iNextRow, 1).Value strTime objSheet.Cells(iNextRow, 2).Value dblTemp objSheet.Cells(iNextRow, 3).Value dblPressure objSheet.Cells(iNextRow, 4).Value dblSpeed --- 保存并释放 --- objWorkbook.Save objWorkbook.Close False objExcel.Quit Set objSheet Nothing Set objWorkbook Nothing Set objExcel Nothing --- 清理标签对象 --- Set oTagTemp Nothing Set oTagPressure Nothing Set oTagSpeed Nothing这段脚本说明几个关键点。其一CDbl 显式转换很重要WinCC 变量读出来是变体类型直接参与运算可能出错。其二UsedRange.Rows.Count 取的是已经使用过的行数但如果工作表里曾经有过格式残留这个值可能偏大更稳妥的做法是维护一个“最后写入行号”变量或者用已知表头结构时直接固定起始行。其三Excel 进程一定要在脚本末尾彻底释放否则后台会残留 EXCEL.EXE多跑几次工控机内存就满了。如果遇到历史归档数据需要从 WinCC 归档数据库读取可以在脚本里改用 ADODB 连接 WinCC 的 OLEDB 接口。连接字符串类似 ProviderWinCCOLEDBProvider;CatalogCC_计算机名_年份;Data Source项目路径;然后执行 SQL 查询把结果集写入 Excel。这部分需要按现场的具体项目数据库配置调整我就不直接照搬了。3.3 模块二ADO 条件查询 Excel 并回填 WinCC第二个模块是查询模块。用 ADO 把 Excel 当作数据库在 SQL 里完成条件过滤然后把查询结果回填到 WinCC 变量或画面对象。先看代码Option Explicit Dim objConn, objRS, strSQL, strConn Dim strBatchNo, dblResult Dim strFilePath, strQty strFilePath D:\PlantData\DailyReport.xlsx strBatchNo ScreenItems(IO_BatchNo).OutputValue 拼连接字符串注意 xlsx 与 xls 的区别 strConn ProviderMicrosoft.ACE.OLEDB.12.0;Data Source strFilePath _ ;Extended PropertiesExcel 12.0 Xml;HDRYES;IMEX1;; Set objConn CreateObject(ADODB.Connection) objConn.Open strConn SQL 查询统计某批次的合格数量 strSQL SELECT SUM(合格数量) AS TotalQty FROM [Record$] _ WHERE 批次号 strBatchNo Set objRS objConn.Execute(strSQL) If Not objRS.EOF Then strQty CStr(objRS.Fields(TotalQty).Value) Else strQty 0 End If objRS.Close objConn.Close Set objRS Nothing Set objConn Nothing 回填到画面 IO 域 ScreenItems(IO_ResultQty).OutputValue strQty这段脚本的核心是 SQL 语句。把查询条件拼进 WHERE 子句后Excel 的查询引擎只返回符合条件的汇总结果脚本端几乎不需要做任何计算速度自然快。SQL 里的字段名对应 Excel 表头注意工作表名称要用方括号包起来末尾加 $ 符号。IMEX1 表示混合类型的列统一按文本读取能避免某些单元格数据被误判成日期或数字。如果你要在 WinCC 画面里回填的是变量而不是 IO 域可以用 HMIRuntime.Tags(ResultQty).Write strQty。另外ADO 连接 Excel 只适合“读”操作不适合“写”操作时做复杂格式控制所以我在这个模块里只用它处理查询和统计回报表还是交给对象模型。3.4 模块三定时触发与无人值守调度人工点击按钮只是第一步真正体现“自动化”的是定时执行。WinCC 里做定时任务有两种常见方式。第一种是 WinCC 全局脚本触发器。在 WinCC 项目管理器的“全局脚本”编辑器中创建一个 VBS 动作把查询和写入 Excel 的代码放进去然后编辑动作的触发器属性设置按周期执行或按日历执行。比如每到整点执行一次数据归档或者每天 1630 生成当日统计报表。这种方式的优点是脚本跑在 WinCC 进程内访问变量和画面对象都方便。第二种是 Windows 任务计划程序。把 VBS 脚本保存成 .vbs 文件在 Windows 任务计划里配置触发时间和执行程序。这种方式的好处是脚本可以脱离 WinCC 画面独立运行适合做纯粹的、无界面的后台数据任务。但要注意脱离 WinCC 运行环境后HMIRuntime 接口不可用脚本里如果访问了 WinCC 变量就会报错所以这种方式更适合只做文件级的数据处理。定时脚本还有一个实战难点防止重复执行。如果上次运行还没结束触发器又触发了两个脚本同时读写同一个 Excel 文件可能导致文件锁冲突。我的经验是加一个“运行锁”脚本启动时检查 WinCC 里的一个开关变量为 1 表示正在运行直接退出本次运行结束后将该变量复位为 0。这样即使触发器周期很短也不会出现并发冲突。4. 查询速度优化实战从“能跑”到“快跑”4.1 瓶颈在哪里COM 调用成本与逐行循环脚本写出来能跑和真正能在生产环境里让人用得舒服中间差着一个优化环节。我从实测经验出发把最常见的性能瓶颈指出来。第一个瓶颈是 COM 跨进程调用的开销。VBS 脚本操作 Excel 时脚本进程和 Excel 进程是独立的每一次访问 Cells、Range本质都是一次跨进程调用。假设你有 5 万行数据要写入 Excel最直观的写法是 For 循环逐行写那就意味着 5 万次跨进程调用。每一次调用虽然只要几毫秒累加起来就是几百秒的等待。第二个瓶颈是重复打开和关闭 Excel。如果在一次查询过程中生成了多个 Excel 对象或者脚本被重复触发后每次都重新创建 Excel 进程光启动进程就要花两到三秒反复几次用户体感就会变得非常差。第三个瓶颈是循环内做条件判断。很多人拿到一个大结果集之后喜欢在 VBS 里写 If Then 逐行判断过滤条件。这种做法把本该由数据源完成的过滤动作搬到了脚本里白白增加了处理量。正确的思路是让 SQL 先过滤脚本只处理结果集。4.2 三个立竿见影的提速技巧附代码对比技巧一用数组批量写入代替逐单元格写入。与其一行一行地写 Cells不如在内存里构造一个二维数组然后用区间一次性赋值。示例代码如下Dim arrData(10000 To 59999, 1 To 4) 填充数组后一次性写入 objSheet.Range(A2).Resize(50000, 4).Value arrData这种写法的本质是把几万次跨进程调用压缩成一次区间赋值实测速度差距可以达到几十倍。对于 10 万行以内的数据这是最有效的提速手段。技巧二查询条件下沉到 SQL。把 WHERE 子句写好让 Excel 查询引擎先把数据过滤完脚本只接收结果集。我在前面 3.3 已经给出示例这里要补充的是拼接 SQL 时注意字段数据类型要和 Excel 列类型一致避免隐式转换导致查询变慢。技巧三用 Dictionary 做内存索引。如果查询场景里有大量“根据批次号找记录”的操作不要每次都去扫描 Excel 范围。脚本启动时读一次数据把批次号作为 Key、行号作为 Value 存入 Scripting.Dictionary后续所有查询都变成内存查找。示例Dim dictIndex Set dictIndex CreateObject(Scripting.Dictionary) Dim iRow, strKey For iRow 2 To iLastRow strKey CStr(objSheet.Cells(iRow, 1).Value) If Not dictIndex.Exists(strKey) Then dictIndex.Add strKey, iRow End If Next 查询时直接取 If dictIndex.Exists(B20250115) Then iFoundRow dictIndex(B20250115) End If用这个技巧查 10 万行数据中的任意批次时间基本在 1 秒以内而且不会随着数据量增加而明显变慢。这是我在现场最常用的提速方案。4.3 数据量接近 Excel 极限时的降级方案全脚本方案不是万能的Excel 本身有行数上限和性能拐点。以我的经验单次查询超过 5 万行时Excel 的排序、筛选、公式联动都会开始明显变慢超过 20 万行时打开文件本身就要等半天。这时候不要硬扛建议直接降级方案。降级方案一是 CSV 分块导出。如果数据只是给下游系统做分析没必要生成 xlsx 格式。脚本直接拼接字符串写出 CSV 文件读写速度远快于 Excel文件体积也更小。WinCC 自带的数据导出能力本身就是 CSV这一步成本很低。降级方案二是引入数据库中间表。当查询数据量经常达到几十万行或者多个客户端需要同时访问数据时全脚本方案就不再合适了。此时把 WinCC 归档数据定时同步到 SQL Server让 Excel 通过 ODBC 连接查询性能和并发能力都会上一个台阶。我在项目里通常把这个转变点设置在“单表数据超过 50 万行”或“并发查询超过 3 个客户端”。判断标准可以很简单如果查询报表需要等超过 10 秒先做索引和批量读写优化优化后仍超过 10 秒直接切数据库。不要在 Excel 上死磕。5. 常见问题与排查技巧实录5.1 Excel 进程残留释放顺序不到位运行脚本后打开任务管理器发现 EXCEL.EXE 好多个占着内存不放这是 VBS 操作 Excel 最常见的坑。根源在于 COM 对象释放顺序不对。正确顺序是先关闭工作簿再退出 Excel 应用最后将对象引用逐一置为 Nothing。很多脚本只写了 objExcel.Quit没有关闭工作簿或者对象引用还没释放就退出Excel 进程就会残留。另外如果脚本中途发生异常On Error 处理不当后面的释放代码根本没执行也会残留。我的做法是写一个统一的清理片段放脚本末尾On Error Resume Next objWorkbook.Close False objExcel.Quit Set objSheet Nothing Set objWorkbook Nothing Set objExcel Nothing On Error GoTo 0即使前面对象创建失败这段代码也能安全执行。脚本上线前我会在工控机上连续运行十次每次运行后检查任务管理器里没有 EXCEL.EXE 残留才敢交付给现场。5.2 “外部表不是预期的格式”连接串与文件不匹配ADO 查询 Excel 时报“外部表不是预期的格式”大多数原因是连接字符串和文件版本不匹配。xls 和 xlsx 使用不同的 Provider 和扩展属性。 如果是 xls 文件Excel 97-2003 strConn ProviderMicrosoft.Jet.OLEDB.4.0;Data Source strFilePath _ ;Extended PropertiesExcel 8.0;HDRYES;IMEX1;; 如果是 xlsx 文件Excel 2007 及以上 strConn ProviderMicrosoft.ACE.OLEDB.12.0;Data Source strFilePath _ ;Extended PropertiesExcel 12.0 Xml;HDRYES;IMEX1;;还有两个容易被忽略的原因一是 Excel 文件正被另一个进程打开导致 ADO 无法独占读取二是文件路径或者工作表名称写错了。排查时先关掉所有 Excel 窗口确认文件存在再检查表名最后看连接字符串。我建议写代码时就把扩展名判断逻辑加上根据文件名的后缀动态选择 Provider以后维护时就不用反复改了。5.3 读取 WinCC 变量变体类型引发的计算错误WinCC 的 HMIRuntime.Tags(标签名).Read 返回的是变体类型变量直接参与运算时可能出现类型不匹配或者精度丢失。比如温度变量读出来是字符串和数值做比较时结果完全错误。解决方法是显式转换。数值变量统一用 CDbl字符串变量统一用 CStr布尔变量用 CBool。我写过一个小的辅助函数专门做带缺省值的类型转换Function ReadTagValue(tagName, defaultValue) On Error Resume Next Dim oTag, varValue Set oTag HMIRuntime.Tags(tagName) varValue oTag.Read If IsNumeric(varValue) Then ReadTagValue CDbl(varValue) Else ReadTagValue defaultValue End If Set oTag Nothing End Function这个函数有两点好处一是变量还没有值的时候不会报错二是参与计算前已经做了类型统一。我在所有涉及 WinCC 变量读取的脚本里都套用这个函数省去了大量重复的调试时间。5.4 脚本卡死与 WinCC 画面无响应的处理WinCC 的 VBS 脚本跑在运行系统进程中如果脚本里出现死循环或者长时间等待会导致画面卡住不动严重时影响监控。这是最需要提前预防的问题。我的经验有三条。第一脚本中避免大循环里不加延时。如果确实需要延迟用 Application.Wait 或者 WScript.Sleep而不是空转循环。第二分批处理大数据。例如写入 Excel 时不要一次性把 20 万行数据全部塞进内存再写可以按 1 万行分批每批完成后释放内存这样即使运行慢一些也不会把系统拖死。第三上线前一定要在 WinCC 运行系统的测试模式下跑通全流程。排查卡死问题时先用任务管理器看 CPU 和内存占用如果 EXCEL.EXE 占用很高基本可以确定是 Excel 交互部分的问题。如果 WinCC 进程占用高要重点检查脚本里的循环和 SQL 查询。实在定位不到可以在脚本里插入 HMIRuntime.Trace 输出调试信息跟踪执行到哪一步开始卡住。这个输出会写到 WinCC 的诊断日志里不用打断运行就能看。最后再分享一点个人体会。这套全脚本方案我陆陆续续用了几年最大的感受是它非常适合“中小规模、快速落地”的自动化查询场景改造成本低现场操作员培训成本也低。但它毕竟跑在工控机上脚本本身的健壮性、异常处理、日志记录都要做足不能拿单机测试的脚本直接扔到生产环境。后续如果要扩展到多产线数据汇总、跨部门报表共享还是要把数据库中间层搭起来把查询能力和数据容量彻底解放。数据量不大时这套脚本就是性价比最高的方案数据量大了它留给你的优化经验也能无缝迁移到数据库方案里。
阅读完成 · 觉得有帮助?