from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import datetime
import time
import sys
import os
import glob
import re
# 导入操作配置模块
# import menu_operations
def load_all_operations(module):
"""
自动扫描模块中所有名为 OPERATIONS_ 开头的字典变量,合并为一个大字典
若有重复 key,后加载的覆盖前面的(可加入警告提示)
"""
combined = {}
for attr_name in dir(module):
if attr_name.startswith("OPERATIONS_"):
obj = getattr(module, attr_name)
if isinstance(obj, dict):
duplicate_keys = set(obj.keys()) & set(combined.keys())
if duplicate_keys:
print(f"⚠️ 警告: '{attr_name}' 包含重复任务名: {duplicate_keys} → 将被覆盖")
combined.update(obj)
print(f"✅ 加载操作集: {attr_name} ({len(obj)} 项)")
else:
print(f"❌ 跳过非字典成员: {attr_name}")
return combined
# ========================================
# 能源系统登录类
# ========================================
class EnergySystemLogin:
def __init__(self):
self.login_url = "http://10.11.20.117:7001/energy4/#/login"
self.credentials = {'username': 'E915285', 'password': '123456'}
self.driver = None
self.wait_timeout = 30
def initialize_driver(self):
"""初始化Chrome浏览器(无头模式 + 支持文件下载)"""
try:
options = webdriver.ChromeOptions()
# --- 反检测设置 ---
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--start-maximized")
options.add_argument("--headless=new") # 新版无头模式
options.add_argument("--disable-gpu")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--window-size=1920,1080")
options.add_argument("--log-level=3")
# 关闭自动化控制提示
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
# 下载目录配置
download_dir = r"D:\Class\能管比对\导出文件"
if not os.path.exists(download_dir):
os.makedirs(download_dir)
prefs = {
"download.default_directory": download_dir,
"download.prompt_for_download": False,
"download.directory_upgrade": True,
"safebrowsing.enabled": True,
"profile.default_content_settings.popups": 0,
"plugins.always_open_pdf_externally": False,
}
options.add_experimental_option("prefs", prefs)
self.driver = webdriver.Chrome(options=options)
self.driver.implicitly_wait(10)
# 防止被识别为机器人
self.driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => false});")
self.log_action(f"浏览器初始化完成,下载路径: {download_dir}")
return True
except Exception as e:
self.log_error(f"浏览器初始化失败: {str(e)}")
return False
def log_action(self, message):
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(f"[{timestamp}] {message}")
def log_error(self, message):
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
self.log_action(f"错误: {message}")
if self.driver:
try:
self.driver.save_screenshot(f"error_{timestamp}.png")
except Exception as e:
print(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 截图失败: {str(e)}")
def locate_and_input(self, by, value, text, description):
try:
element = WebDriverWait(self.driver, self.wait_timeout).until(
EC.visibility_of_element_located((by, value))
)
element.clear()
element.send_keys(text)
self.log_action(f"{description} 输入完成")
return True
except Exception as e:
self.log_error(f"{description} 输入失败: {str(e)}")
return False
def perform_login(self):
try:
self.log_action("访问登录页面")
self.driver.get(self.login_url)
# 等待页面加载完成
WebDriverWait(self.driver, self.wait_timeout).until(
lambda d: d.execute_script("return document.readyState") == "complete"
)
# 输入用户名
if not self.locate_and_input(
By.CSS_SELECTOR,
"input.el-input__inner[type='text']",
self.credentials['username'],
"用户名"
):
return False
# 输入密码
if not self.locate_and_input(
By.CSS_SELECTOR,
"input.el-input__inner[type='password']",
self.credentials['password'],
"密码"
):
return False
# 点击登录按钮
try:
login_btn = WebDriverWait(self.driver, self.wait_timeout).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "button.loginBtn"))
)
login_btn.click()
self.log_action("点击登录按钮")
except Exception as e:
self.log_error(f"点击登录按钮失败: {str(e)}")
return False
# 等待登录成功跳转
try:
WebDriverWait(self.driver, 20).until(
EC.url_contains("/energy4/#/")
)
WebDriverWait(self.driver, 10).until_not(
EC.presence_of_element_located((By.CLASS_NAME, "login-container"))
)
self.log_action("登录成功")
self.driver.save_screenshot("login_success.png")
return True
except TimeoutException:
self.log_error("登录验证超时,可能未成功登录")
return False
except Exception as e:
self.log_error(f"登录过程发生异常: {str(e)}")
return False
def get_driver(self):
return self.driver
def cleanup(self):
if self.driver:
try:
self.driver.quit()
except Exception as e:
print(f"清理浏览器资源时出错: {str(e)}")
# ========================================
# 菜单导航与文件处理类
# ========================================
class MenuNavigator:
def __init__(self, driver, operations_dict, download_dir=r"D:\Class\能管比对\导出文件"):
self.driver = driver
self.wait_timeout = 10
self.click_delay = 3
self.download_dir = download_dir
self.OPERATIONS = operations_dict # 接收外部传入的所有操作流程
def get_timestamp(self):
return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
def log_action(self, message):
print(f"[{self.get_timestamp()}] 操作: {message}")
def log_error(self, message):
print(f"[{self.get_timestamp()}] 错误: {message}")
try:
self.driver.save_screenshot(f"error_{int(time.time())}.png")
except:
pass
def click_element(self, xpath, description):
try:
element = WebDriverWait(self.driver, self.wait_timeout).until(
EC.visibility_of_element_located((By.XPATH, xpath))
)
element.click()
self.log_action(f"点击成功: {description}")
time.sleep(self.click_delay) # 固定等待页面响应
return True
except Exception as e:
self.log_error(f"点击失败 ({description}): {str(e)}")
return False
def get_latest_downloaded_file(self, timeout=30):
"""等待并返回最新稳定的 .xlsx 文件"""
start_time = time.time()
while time.time() - start_time < timeout:
list_of_files = [
f for f in glob.glob(os.path.join(self.download_dir, "*.xls*"))
if not os.path.basename(f).startswith("~$")
]
if list_of_files:
latest_file = max(list_of_files, key=os.path.getmtime)
size1 = os.path.getsize(latest_file)
time.sleep(1)
size2 = os.path.getsize(latest_file)
if size1 == size2: # 大小稳定表示写入完成
return latest_file
time.sleep(1)
raise Exception("等待下载超时,未检测到新文件")
def extract_filename(self, task_key):
filename = f"{task_key}.xlsx"
return filename
def rename_latest_file(self, new_name):
"""将最新下载的文件重命名为指定名称,避免覆盖"""
try:
old_file = self.get_latest_downloaded_file()
new_file_path = os.path.join(self.download_dir, new_name)
counter = 1
base_name, ext = os.path.splitext(new_name)
while os.path.exists(new_file_path):
new_file_path = os.path.join(self.download_dir, f"{base_name}_{counter}{ext}")
counter += 1
os.rename(old_file, new_file_path)
self.log_action(f"文件已重命名: {os.path.basename(old_file)} → {os.path.basename(new_file_path)}")
return True
except Exception as e:
self.log_error(f"文件重命名失败: {str(e)}")
return False
def navigate(self, task_key):
"""执行单个任务流程"""
if task_key not in self.OPERATIONS:
self.log_error(f"未找到名为 '{task_key}' 的操作流程")
return False
operations = self.OPERATIONS[task_key]
self.log_action(f"开始执行任务: {task_key}")
for op in operations:
if not self.click_element(op["xpath"], op["desc"]):
self.log_error(f"操作中断于: {op['desc']}")
return False
# 🔁 判断是否为预操作:不需要导出文件
if task_key == "0.预操作":
self.log_action("✅ 任务 '0.预操作' 执行完成(无导出动作)")
return True # 直接成功返回,不等待文件
# 其他任务继续走导出逻辑
auto_filename = self.extract_filename(task_key)
self.log_action("正在等待导出文件生成...")
if self.rename_latest_file(auto_filename):
self.log_action(f"✅ 任务 '{task_key}' 成功,文件已保存为: {auto_filename}")
return True
else:
self.log_error(f"❌ 任务 '{task_key}' 失败:无法获取或重命名导出文件")
return False
if __name__ == "__main__":
login = None
try:
login = EnergySystemLogin()
# ✅ 第一步:初始化浏览器
if not login.initialize_driver():
print("❌ 浏览器初始化失败,程序退出")
sys.exit(1)
# ✅ 第二步:执行登录
if not login.perform_login():
print("❌ 登录失败,程序退出")
sys.exit(1)
# ✅ 第三步:获取已登录的 driver
driver = login.get_driver()
# 🔍 支持加载多个 operations 模块
module_names = [
"menu_operations0",
"menu_operations1",
"menu_operations2",
# "menu_operations3",
]
ALL_OPERATIONS = {}
for mod_name in module_names:
try:
module = __import__(mod_name)
import importlib
importlib.reload(module)
combined_in_mod = load_all_operations(module)
duplicate_keys = set(combined_in_mod.keys()) & set(ALL_OPERATIONS.keys())
if duplicate_keys:
print(f"⚠️ 跨模块重复任务名: {duplicate_keys} → 将被后加载的覆盖")
ALL_OPERATIONS.update(combined_in_mod)
print(f"📁 已从模块 '{mod_name}' 加载 {len(combined_in_mod)} 项任务\n")
except ImportError as e:
print(f"❌ 无法导入模块 '{mod_name}': {e}")
except Exception as e:
print(f"❌ 加载模块 '{mod_name}' 时发生异常: {str(e)}")
navigator = MenuNavigator(driver, operations_dict=ALL_OPERATIONS)
tasks_to_run = list(ALL_OPERATIONS.keys())
print(f"\n📋 共发现 {len(tasks_to_run)} 个待执行任务:")
for i, task in enumerate(tasks_to_run, 1):
print(f" {i}. {task}")
success_count = 0
for task in tasks_to_run:
print(f"\n🔁 正在执行任务: {task}")
if navigator.navigate(task):
success_count += 1
else:
print(f"❌ 任务 '{task}' 执行失败")
print(f"\n🎉 总结:{success_count}/{len(tasks_to_run)} 个任务执行成功")
except KeyboardInterrupt:
print("\n\n🛑 用户手动中断程序")
except Exception as e:
print(f"🚨 程序发生未预期异常: {str(e)}")
finally:
if login:
login.cleanup()
print("🔚 程序结束")
——
"""
能源系统自动化脚本的操作路径配置文件
支持多个 OPERATIONS_* 字典,main.py 会自动扫描并合并所有任务
"""
OPERATIONS_PART1 = {
"1.生产用水": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='水处理系统(旧)']]", "desc": "水处理系统(旧)"},
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='生产用水']]", "desc": "生产用水"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"2.全厂耗水分析": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='全厂耗水分析']]", "desc": "全厂耗水分析"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"3.City Water Consumption": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='纯水曲线看板']]","desc": "纯水曲线看板"},
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='City Water Consumption']]","desc": "City Water Consumption"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"4.UPW Consumption": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='UPW Consumption']]","desc": "UPW Consumption"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"5.Other Water Consumption": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='Other Water Consumption']]","desc": "Other Water Consumption"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"6.全期DIR&LSR回收水量": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='DIR&LSR回收水量']]","desc": "DIR&LSR回收水量"},
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='全期DIR&LSR回收水量']]","desc": "全期DIR&LSR回收水量"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"7.全期 DIR系统产水": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='全期 DIR系统产水']]","desc": "全期 DIR系统产水"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"8.全期 LSR系统产水": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='全期 LSR系统产水']]","desc": "全期 LSR系统产水"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
# "9.DIR回收率": [
# {"xpath": "//div[@class='el-tree-node__content'][.//span[@title='DIR回收率']]","desc": "DIR回收率"},
# {"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
# {"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
# ],
"10.WWT各系统水量": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='废水曲线看板']]", "desc": "废水曲线看板"},
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='WWT各系统水量']]", "desc": "WWT各系统水量"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"11.WWT放流水_pH": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='WWT放流水_pH']]", "desc": "WWT放流水_pH"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"12.WWT放流水质_F&NH3_N": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='WWT放流水质_F&NH3_N']]","desc": "WWT放流水质_F&NH3_N"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"13.WWT放流水质_COD&SS": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='WWT放流水质_COD&SS']]","desc": "WWT放流水质_COD&SS"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"14.WWT放流水质_Cu": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='WWT放流水质_Cu']]","desc": "WWT放流水质_Cu"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"15.废水产量": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='废水产量']]","desc": "废水产量"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"16.超纯水耗电量": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='超纯水耗电量']]","desc": "超纯水耗电量"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"17.UPW_ECF_参考": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='UPW_ECF_参考']]", "desc": "UPW_ECF_参考"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"18.纯水电量": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='纯水电量']]", "desc": "纯水电量"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
"19.纯水电盘电量": [
{"xpath": "//div[@class='el-tree-node__content'][.//span[@title='纯水电盘电量']]", "desc": "纯水电盘电量"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
{"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
],
}
#
# 示例:第一组操作(冷热水系统)
# OPERATIONS_PART1 = {
# "1.排气系统风量": [
# {"xpath": "//span[@slot='title'][text()='能耗统计']", "desc": "能耗统计"},
# {"xpath": "//span[text()='分类统计' and contains(@data-v-11708afe,'')]", "desc": "分类统计"},
# {"xpath": "//div[@role='button' and contains(@class,'el-collapse-item__header')][normalize-space()='系统分组']", "desc": "系统分组"},
# {"xpath": "//span[@data-v-4fe475b6 and @title='空调系统' and normalize-space()='空调系统']", "desc": "空调系统"},
# {"xpath": "//div[@class='el-tree-node__content' and contains(@style,'padding-left: 18px;')]//span[@title='排气系统']", "desc": "排气系统"},
# {"xpath": "//div[contains(@class, 'el-tree-node__content') and contains(., '排气系统风量')]","desc": "排气系统风量"},
# {"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
# {"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
# ],
#
# "40.西侧2#外气焓值": [
# {"xpath": "//div[contains(@class, 'el-tree-node__content') and contains(., '西侧2#外气焓值')]","desc": "西侧2#外气焓值"},
# {"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
# {"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
# ],
# "41.一般空调耗电量": [
# {"xpath": "//div[@class='el-tree-node__content' and contains(@style,'padding-left: 18px;')]//span[@title='一般空调耗电量']","desc": "一般空调耗电量"},
# {"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '查 询')]", "desc": "查询"},
# {"xpath": "//button[contains(@class, 'el-button') and contains(span/text(), '导出Excel')]", "desc": "导出"},
# ],
# }
——这里头有个问题导致我最后导出文件少了一个——🔁 正在执行任务: 2.全厂耗水分析
[2025-11-03 15:56:58] 操作: 开始执行任务: 2.全厂耗水分析
[2025-11-03 15:56:59] 操作: 点击成功: 全厂耗水分析
[2025-11-03 15:57:02] 操作: 点击成功: 查询
[2025-11-03 15:57:07] 操作: 点击成功: 导出
[2025-11-03 15:57:10] 操作: 正在等待导出文件生成...
[2025-11-03 15:57:11] 操作: 文件已重命名: 列表.xlsx → 2.全厂耗水分析.xlsx
[2025-11-03 15:57:11] 操作: ✅ 任务 '2.全厂耗水分析' 成功,文件已保存为: 2.全厂耗水分析.xlsx,🔁 正在执行任务: 3.City Water Consumption
[2025-11-03 15:57:11] 操作: 开始执行任务: 3.City Water Consumption
[2025-11-03 15:57:11] 操作: 点击成功: 纯水曲线看板
[2025-11-03 15:57:16] 操作: 点击成功: City Water Consumption
[2025-11-03 15:57:20] 操作: 点击成功: 查询
[2025-11-03 15:57:23] 操作: 点击成功: 导出
[2025-11-03 15:57:26] 操作: 正在等待导出文件生成...
[2025-11-03 15:57:27] 操作: 文件已重命名: 2.全厂耗水分析.xlsx → 3.City Water Consumption.xlsx
[2025-11-03 15:57:27] 操作: ✅ 任务 '3.City Water Consumption' 成功,文件已保存为: 3.City Water Consumption.xlsx,为什么把全场耗水分析更名为了下一个任务的名字,导致少了一个导出文件
最新发布