请修改我的代码,保证我不丢失数据的写到execl中,列是需要可扩展的不能固定 import requests
from requests.auth import HTTPBasicAuth
from openpyxl import Workbook
import pandas as pd
import time
import re
import logging
# 抑制 SSL 警告
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
# 配置
USERNAME = "admin"
PASSWORD = "123456"
VERIFY_SSL = False
# 设置日志格式
logging.basicConfig(level=logging.ERROR, format="%(asctime)s - %(levelname)s - %(message)s")
# 安全获取嵌套值
def get_nested(data, *keys, default="未知"):
for key in keys:
if isinstance(data, dict):
data = data.get(key)
elif isinstance(data, list):
if isinstance(key, int) and key < len(data):
data = data[key]
else:
return default
else:
return default
return data if data is not None else default
# 安全转换为整数的函数
def safe_int(value, default=0):
if isinstance(value, int):
return value
elif isinstance(value, str) and value.isdigit():
return int(value)
return default
# =============== 登录并获取 token =============
def login(session, base_url):
login_url = f"{base_url}/redfish/v1/SessionService/Sessions"
headers = {"Content-Type": "application/json"}
body = {"UserName": USERNAME, "Password": PASSWORD}
try:
response = session.post(
login_url,
json=body,
headers=headers,
auth=HTTPBasicAuth(USERNAME, PASSWORD),
verify=VERIFY_SSL
)
response.raise_for_status()
return response.headers.get("X-Auth-Token")
except requests.exceptions.RequestException as e:
return None
# =============== 获取每个 CPU 的详细信息 ================
def get_cpu_info(session, base_url, auth_token):
cpu_url = f"{base_url}/redfish/v1/Systems/1/Processors?x-Auth-Token={auth_token}"
headers = {"X-Auth-Token": auth_token}
try:
time.sleep(1)
response = session.get(cpu_url, headers=headers, verify=VERIFY_SSL)
response.raise_for_status()
cpu_collection = response.json()
cpu_details_list = []
for member in cpu_collection["Members"]:
cpu_member_url = f"{base_url}{member['@odata.id']}?x-Auth-Token={auth_token}"
cpu_response = session.get(cpu_member_url, headers=headers, verify=VERIFY_SSL)
cpu_response.raise_for_status()
cpu_details = cpu_response.json()
cpu_details_list.append(cpu_details)
return cpu_details_list
except requests.exceptions.RequestException as e:
return None
# =============== 写入 Excel 的函数 =============
def write_to_excel(ip, cpu_list, ws):
if not cpu_list:
return
cpu_count = len(cpu_list)
architectures = set(get_nested(cpu, "ProcessorArchitecture") for cpu in cpu_list)
models = set(get_nested(cpu, "Model") for cpu in cpu_list)
total_cores = get_nested(cpu_list[0].get("TotalCores")) if cpu_list else None
total_threads = get_nested(cpu_list[0].get("TotalThreads")) if cpu_list else None
rated_speed_list = [safe_int(get_nested(cpu, "Oem", "Hpe", "RatedSpeedMHz")) for cpu in cpu_list]
rated_speed = max(rated_speed_list) if rated_speed_list else 0
max_speed_list = [safe_int(get_nested(cpu, "MaxSpeedMHz")) for cpu in cpu_list]
max_speed = max(max_speed_list) if max_speed_list else 0
state = get_nested(cpu_list[0], "Status", "State")
caches = get_nested(cpu_list[0], "Oem", "Hpe", "Cache")
cache_values = ["未知"] * 3
if caches and isinstance(caches, list):
for i in range(min(len(caches), 3)):
cache_values[i] = str(caches[i].get('MaximumSizeKB', '未知'))
row = (
ip,
", ".join(models) if models else "未知",
", ".join(architectures) if architectures else "未知",
total_cores,
total_threads,
rated_speed,
max_speed,
state,
cache_values[0],
cache_values[1],
cache_values[2],
cpu_count
)
ws.append(row)
# 处理单个服务器的函数
def process_server(ip, ws):
base_url = f"https://{ip}"
with requests.Session() as session:
auth_token = login(session, base_url)
if not auth_token:
print(f"❌ 无法登录服务器 {ip}")
return
time.sleep(1)
cpu_details = get_cpu_info(session, base_url, auth_token)
if cpu_details:
write_to_excel(ip, cpu_details, ws)
print(f"✅ 成功处理服务器 {ip}")
else:
print(f"❌ 服务器 {ip} 登录成功但未获取到 CPU 信息")
# 获取内存信息
def get_memory_info(session, base_url, auth_token):
memory_url = f"{base_url}/redfish/v1/Systems/1/Memory?x-Auth-Token={auth_token}"
headers = {"X-Auth-Token": auth_token}
try:
time.sleep(1)
response = session.get(memory_url, headers=headers, verify=VERIFY_SSL)
response.raise_for_status()
memory_collection = response.json()
memory_details_list = []
for member in memory_collection.get("Members", []):
memory_member_url = f"{base_url}{member['@odata.id']}?x-Auth-Token={auth_token}"
memory_response = session.get(memory_member_url, headers=headers, verify=VERIFY_SSL)
memory_response.raise_for_status()
memory_details = memory_response.json()
memory_details_list.append(memory_details)
return memory_details_list
except requests.exceptions.RequestException:
return None
# 统计内存信息
def summarize_memory(memory_details_list):
if not memory_details_list:
return {
"total_slots": 0,
"total_enabled": 0,
"total_capacity_gb": 0,
"memory_speed_mhz": "未知",
"memory_type": "未知"
}
enabled_modules = [m for m in memory_details_list if get_nested(m, "Status", "State") == "Enabled"]
total_capacity_gb = round(sum(get_nested(m, "CapacityMiB", default=0) for m in enabled_modules) / 1024, 2)
# 取第一个启用的内存模块用于提取频率和类型
first_module = enabled_modules[0] if enabled_modules else memory_details_list[0]
return {
"total_slots": len(memory_details_list),
"total_enabled": len(enabled_modules),
"total_capacity_gb": total_capacity_gb,
"memory_speed_mhz": get_nested(first_module, "OperatingSpeedMhz", default="未知"),
"memory_type": get_nested(first_module, "MemoryDeviceType", default="未知")
}
# 获取网络设备信息(白名单 + 黑名单过滤)
def get_network_info(session, base_url, auth_token):
chassis_id = "1"
headers = {"X-Auth-Token": auth_token}
pcie_devices_url = f"{base_url}/redfish/v1/Chassis/{chassis_id}/PCIeDevices?x-Auth-Token={auth_token}"
# 黑名单正则表达式
BLACKLISTED_PATTERNS = [
re.compile(r".* FC HBA.*", re.IGNORECASE),
re.compile(r".*Array.*", re.IGNORECASE),
]
# 白名单正则表达式
WHITELISTED_PATTERNS = [
re.compile(r".*Adapter.*", re.IGNORECASE),
re.compile(r".*Ethernet.*", re.IGNORECASE),
re.compile(r".*10Gb.*", re.IGNORECASE),
re.compile(r".*GbE.*", re.IGNORECASE),
re.compile(r"Broadcom", re.IGNORECASE),
re.compile(r"NetXtreme", re.IGNORECASE),
re.compile(r"BCM[0-9]+", re.IGNORECASE),
re.compile(r"HPE", re.IGNORECASE),
re.compile(r"Intel", re.IGNORECASE),
re.compile(r".*Adptr.*", re.IGNORECASE),
re.compile(r".*Embedded.*", re.IGNORECASE),
]
try:
time.sleep(1)
response = session.get(pcie_devices_url, headers=headers, verify=VERIFY_SSL)
response.raise_for_status()
pcie_devices = response.json()
network_adapters = []
for member in pcie_devices.get("Members", []):
device_url = f"{base_url}{member['@odata.id']}?x-Auth-Token={auth_token}"
device_response = session.get(device_url, headers=headers, verify=VERIFY_SSL)
device_response.raise_for_status()
device_info = device_response.json()
name = device_info.get("Name", "未知")
if name == "未知":
continue
# 检查黑名单
if any(pattern.search(name) for pattern in BLACKLISTED_PATTERNS):
continue
# 检查白名单
if is_name_allowed(name, WHITELISTED_PATTERNS):
network_adapters.append({"Name": name})
return network_adapters
except requests.exceptions.RequestException as e:
logging.error(f"❌ 获取网卡信息失败: {e}")
return []
# 获取网络设备数量
def count_network_adapters(network_details):
from collections import Counter
adapter_names = [adapter.get("Name") for adapter in network_details]
return dict(Counter(adapter_names))
# 判断 SKU 是否在白名单中
def is_name_allowed(name, patterns):
for pattern in patterns:
if pattern.search(name):
return True
return False
# 获取服务器序列号(带缓存)
def get_server_serial_number(session, base_url, auth_token):
system_url = f"{base_url}/redfish/v1/Systems/1?x-Auth-Token={auth_token}"
headers = {"X-Auth-Token": auth_token}
try:
time.sleep(1)
response = session.get(system_url, headers=headers, verify=VERIFY_SSL)
response.raise_for_status()
system_info = response.json()
serial_number = get_nested(system_info, 'SerialNumber', default='未知序列号')
return [{"SerialNumber": serial_number}]
except requests.exceptions.RequestException as e:
print(f"❌ 获取服务器基本信息失败: {e}")
return []
# 获取服务器阵列卡(带缓存)
def get_array_controller_info(session, base_url, auth_token):
storage_url = f"{base_url}/redfish/v1/Systems/1/Storage"
headers = {"X-Auth-Token": auth_token}
try:
response = session.get(storage_url, headers=headers, verify=VERIFY_SSL, timeout=10)
response.raise_for_status()
storage_summary = response.json()
array_controllers = []
for member in storage_summary.get("Members", []):
controller_url = f"{base_url}{member['@odata.id']}?x-Auth-Token={auth_token}"
ctrl_response = session.get(controller_url, headers=headers, verify=VERIFY_SSL)
ctrl_response.raise_for_status()
controller_data = ctrl_response.json()
array_controllers.append({
"Id": controller_data.get("Id", "未知ID"),
"Name": controller_data.get("Name", "未知名称"),
})
return array_controllers
except requests.exceptions.RequestException as e:
print(f"❌ 获取 阵列卡 控制器信息失败: {e}")
return []
# 获取 RAID 类型与磁盘列表的映射
def get_raid_disk_mapping(session, base_url, auth_token):
storage_url = f"{base_url}/redfish/v1/Systems/1/Storage"
headers = {"X-Auth-Token": auth_token}
raid_disk_map = {}
try:
response = session.get(storage_url, headers=headers, verify=VERIFY_SSL, timeout=10)
response.raise_for_status()
storage_summary = response.json()
for member in storage_summary.get("Members", []):
controller_url = f"{base_url}{member['@odata.id']}"
ctrl_response = session.get(controller_url, headers=headers, verify=VERIFY_SSL)
ctrl_response.raise_for_status()
controller_data = ctrl_response.json()
# 获取卷信息
volumes_url = f"{base_url}{controller_data['Volumes']['@odata.id']}"
volumes_response = session.get(volumes_url, headers=headers, verify=VERIFY_SSL)
volumes_response.raise_for_status()
volumes_data = volumes_response.json()
for volume_member in volumes_data.get("Members", []):
volume_url = f"{base_url}{volume_member['@odata.id']}"
volume_response = session.get(volume_url, headers=headers, verify=VERIFY_SSL)
volume_response.raise_for_status()
volume_detail = volume_response.json()
raid_type = volume_detail.get("RAIDType", "None")
drives = volume_detail.get("Links", {}).get("Drives", [])
raid_disk_map.setdefault(raid_type, [])
for drive in drives:
drive_url = f"{base_url}{drive['@odata.id']}"
drive_response = session.get(drive_url, headers=headers, verify=VERIFY_SSL, timeout=10)
drive_response.raise_for_status()
drive_data = drive_response.json()
raid_disk_map[raid_type].append({
"SerialNumber": drive_data.get("SerialNumber", "0"),
"CapacityBytes": drive_data.get("CapacityBytes", 0),
"MediaType": drive_data.get("MediaType", "0"),
})
# 获取不属于任何卷的磁盘
drives = controller_data.get("Drives", [])
for drive in drives:
drive_url = f"{base_url}{drive['@odata.id']}"
drive_response = session.get(drive_url, headers=headers, verify=VERIFY_SSL, timeout=10)
drive_response.raise_for_status()
drive_data = drive_response.json()
in_volume = False
for disks in raid_disk_map.values():
for disk in disks:
if disk["SerialNumber"] == drive_data.get("SerialNumber"):
in_volume = True
break
if in_volume:
break
if not in_volume:
raid_disk_map.setdefault("None", []).append({
"SerialNumber": drive_data.get("SerialNumber", "0"),
"CapacityBytes": drive_data.get("CapacityBytes", 0),
"MediaType": drive_data.get("MediaType", "0"),
})
except requests.exceptions.RequestException as e:
print(f"❌ 获取 RAID 与磁盘映射失败: {e}")
return {}
return raid_disk_map
# 获取 iLO 信息(固件版本)
def get_ilo_info(session, base_url, auth_token):
ilo_url = f"{base_url}/redfish/v1/Managers/1/"
headers = {"X-Auth-Token": auth_token}
try:
response = session.get(ilo_url, headers=headers, verify=VERIFY_SSL)
response.raise_for_status()
ilo_data = response.json()
firmware_version = get_nested(ilo_data, "FirmwareVersion")
return {"Firmware Version": firmware_version}
except requests.exceptions.RequestException as e:
logging.error(f"❌ 获取 iLO 信息失败: {e}")
return {"Firmware Version": "未知"}
# 获取 BIOS 版本
def get_bios_info(session, base_url, auth_token):
system_url = f"{base_url}/redfish/v1/Systems/1/"
headers = {"X-Auth-Token": auth_token}
try:
response = session.get(system_url, headers=headers, verify=VERIFY_SSL)
response.raise_for_status()
system_data = response.json()
bios_version = get_nested(system_data, "BiosVersion")
return {"BIOS Version": bios_version}
except requests.exceptions.RequestException as e:
logging.error(f"❌ 获取 BIOS 信息失败: {e}")
return {"BIOS Version": "未知"}
# 获取电源信息(仅保留指定字段)
def get_power_info(session, base_url, auth_token):
power_url = f"{base_url}/redfish/v1/Chassis/1/Power"
headers = {"X-Auth-Token": auth_token}
try:
response = session.get(power_url, headers=headers, verify=VERIFY_SSL)
response.raise_for_status()
power_data = response.json()
power_supplies = power_data.get("PowerSupplies", [])
power_count = len(power_supplies)
power_list = [ps.get("PowerCapacityWatts") for ps in power_supplies if ps.get("PowerCapacityWatts")]
total_power = sum(power_list)
single_power = round(total_power / power_count, 2) if power_count > 0 else "未知"
return {
"电源数量": power_count,
"总功率(W)": total_power,
"单个电源功率(W)": single_power,
}
except requests.exceptions.RequestException as e:
logging.error(f"❌ 获取电源信息失败: {e}")
return {}
# 获取HBA信息(仅保留指定字段)
def get_hba_info(session, base_url, auth_token):
chassis_id = "1"
pcie_devices_url = f"{base_url}/redfish/v1/Chassis/{chassis_id}/PCIeDevices?x-Auth-Token={auth_token}"
headers = {"X-Auth-Token": auth_token}
WHITELISTED_PATTERNS = [
r"Emulex.*",
r"QLogic",
r"SN.*",
]
try:
time.sleep(1)
response = session.get(pcie_devices_url, headers=headers, verify=False)
response.raise_for_status()
pcie_devices = response.json()
fc_adapters = []
for member in pcie_devices.get("Members", []):
device_url = f"{base_url}{member['@odata.id']}?x-Auth-Token={auth_token}"
device_response = session.get(device_url, headers=headers, verify=False)
device_response.raise_for_status()
device_info = device_response.json()
name = get_nested(device_info, "Name")
if not name:
continue
matched = False
for pattern in WHITELISTED_PATTERNS:
regex_pattern = pattern.replace("*", ".*")
if re.search(regex_pattern, name):
matched = True
break
if matched:
fc_adapters.append({"Name": name})
return fc_adapters
except requests.exceptions.RequestException as e:
print(f"❌ 获取FC卡失败: {e}")
return []
# ================== 新增辅助函数:用于生成动态 RAID/HBA/网卡列名 ==================
def get_raid_columns(raid_disk_map):
"""生成 RAID 列名:每组 RAID 类型 + 其下所有磁盘三列连续排列"""
cols = []
sorted_items = sorted(raid_disk_map.items(), key=lambda x: (x[0] != "None", x[0])) # None 排最后
for raid_idx, (raid_type, disks) in enumerate(sorted_items, start=1):
cols.append(f"RAID类型_{raid_idx}")
for disk_idx in range(1, len(disks) + 1):
prefix = f"{raid_idx}_{disk_idx}"
cols.extend([
f"磁盘序列号_{prefix}",
f"磁盘容量_{prefix}(GB)",
f"磁盘类型_{prefix}"
])
return cols
def get_raid_data(raid_disk_map):
"""生成 RAID 数据:每个 RAID 类型后紧跟其磁盘信息"""
data = {}
sorted_items = sorted(raid_disk_map.items(), key=lambda x: (x[0] != "None", x[0]))
for raid_idx, (raid_type, disks) in enumerate(sorted_items, start=1):
data[f"RAID类型_{raid_idx}"] = raid_type
for disk_idx, disk in enumerate(disks, start=1):
prefix = f"{raid_idx}_{disk_idx}"
data[f"磁盘序列号_{prefix}"] = disk.get("SerialNumber", "")
cap_gb = round(disk.get("CapacityBytes", 0) / (1024**3), 2) if disk.get("CapacityBytes") else 0
data[f"磁盘容量_{prefix}(GB)"] = cap_gb
data[f"磁盘类型_{prefix}"] = disk.get("MediaType", "")
return data
def get_hba_columns(hba_list):
return [f"hba{i}卡名" for i in range(1, len(hba_list) + 1)]
def get_hba_data(hba_list):
return {f"hba{i}卡名": adapter.get("Name", "") for i, adapter in enumerate(hba_list, 1)}
def get_nic_columns(nic_list):
cols = [f"网卡型号_{i}" for i in range(1, len(nic_list) + 1)]
cols.append("网卡数量") # 数量放在网卡列表之后
return cols
def get_nic_data(nic_list):
data = {f"网卡型号_{i}": adapter.get("Name", "") for i, adapter in enumerate(nic_list, 1)}
data["网卡数量"] = len(nic_list) # ✅ 放在网卡字段后面
return data
# 主函数:读取文件并处理所有服务器
def main():
wb = Workbook()
ws = wb.active
ws.title = "CPU 信息汇总"
# 表头
ws.append([
"IP 地址", "型号", "CPU架构", "CPU核心数", "线程数", "CPU基本频率(MHz)", "CPU最大频率(MHz)", "CPU状态",
"CPU缓存1(KB)", "CPU缓存2(KB)", "CPU缓存3(KB)", "CPU数量"
])
try:
with open("server.txt", "r") as f:
ips = [line.strip() for line in f if line.strip()]
except FileNotFoundError:
print("❌ 文件 server.txt 不存在,请确保文件存在并包含 IP 地址。")
return
for ip in ips:
print(f"\n🔄 正在处理服务器:{ip}")
process_server(ip, ws)
wb.save("HPE_DL380_Gen10_PLUS_info.xlsx")
print("✅ 所有服务器的 CPU 信息已成功写入 HPE_DL380_Gen10_PLUS_info.xlsx")
# ========== 第一步:收集所有数据,并构建全局列集合 ==========
all_server_data = []
all_columns_set = set()
# 固定基础列(必须存在)
base_columns = [
"IP 地址", "型号", "CPU架构", "CPU核心数", "线程数",
"CPU基本频率(MHz)", "CPU最大频率(MHz)", "CPU状态",
"CPU缓存1(KB)", "CPU缓存2(KB)", "CPU缓存3(KB)", "CPU数量",
"总内存插槽数", "已启用内存数", "可用内存插槽数", "内存总容量(GB)",
"内存频率(MHz)", "内存类型",
"序列号", "阵列卡id", "阵列卡名称",
"BIOS Version", "Firmware Version",
"电源数量", "总功率(W)", "单个电源功率(W)"
]
all_columns_set.update(base_columns)
try:
# ====== 🔴 添加这一段:重新读取 ips ======
try:
with open("server.txt", "r") as f:
ips = [line.strip() for line in f if line.strip()]
except FileNotFoundError:
print("❌ 文件 server.txt 不存在")
sys.exit(1) # 终止程序
with requests.Session() as session:
for ip in ips:
print(f"\n🔄 正在处理服务器:{ip}")
base_url = f"https://{ip}"
auth_token = login(session, base_url)
if not auth_token:
print(f"❌ 登录失败,跳过 {ip}")
continue
time.sleep(1)
# ========== 采集所有信息 ==========
cpu_details = get_cpu_info(session, base_url, auth_token)
memory_details = get_memory_info(session, base_url, auth_token)
network_details = get_network_info(session, base_url, auth_token) or [] # 防空
server_info = get_server_serial_number(session, base_url, auth_token) or []
array_info = get_array_controller_info(session, base_url, auth_token) or []
raid_disk_map = get_raid_disk_mapping(session, base_url, auth_token) or {}
ilo_details = get_ilo_info(session, base_url, auth_token) or {}
bios_details = get_bios_info(session, base_url, auth_token) or {}
power_details = get_power_info(session, base_url, auth_token) or {}
fc_details = get_hba_info(session, base_url, auth_token) or []
# ========== 汇总内存 ==========
memory_summary = summarize_memory(memory_details) if memory_details else {}
# ========== CPU 处理 ==========
if not cpu_details:
model = "未知"
arch = "未知"
cores = threads = rated_speed = max_speed = 0
state = "未知"
cache_values = ["未知"] * 3
cpu_count = 0
else:
models = {get_nested(cpu, "Model") for cpu in cpu_details}
archs = {get_nested(cpu, "ProcessorArchitecture") for cpu in cpu_details}
cores = get_nested(cpu_details[0], "TotalCores") or 0
threads = get_nested(cpu_details[0], "TotalThreads") or 0
rated_speed = max((safe_int(get_nested(cpu, "Oem", "Hpe", "RatedSpeedMHz")) for cpu in cpu_details), default=0)
max_speed = max((safe_int(get_nested(cpu, "MaxSpeedMHz")) for cpu in cpu_details), default=0)
state = get_nested(cpu_details[0], "Status", "State") or "未知"
caches = get_nested(cpu_details[0], "Oem", "Hpe", "Cache") or []
cache_values = ["未知"] * 3
for i in range(min(len(caches), 3)):
cache_values[i] = str(caches[i].get('MaximumSizeKB', '未知'))
cpu_count = len(cpu_details)
model = ", ".join(models) if models else "未知"
arch = ", ".join(archs) if archs else "未知"
# ========== 初始化当前行 ==========
row = {
"IP 地址": ip,
"型号": model,
"CPU架构": arch,
"CPU核心数": cores,
"线程数": threads,
"CPU基本频率(MHz)": rated_speed,
"CPU最大频率(MHz)": max_speed,
"CPU状态": state,
"CPU缓存1(KB)": cache_values[0],
"CPU缓存2(KB)": cache_values[1],
"CPU缓存3(KB)": cache_values[2],
"CPU数量": cpu_count,
"总内存插槽数": memory_summary.get("total_slots", 0),
"已启用内存数": memory_summary.get("total_enabled", 0),
"可用内存插槽数": memory_summary.get("total_slots", 0) - memory_summary.get("total_enabled", 0),
"内存总容量(GB)": memory_summary.get("total_capacity_gb", 0),
"内存频率(MHz)": memory_summary.get("memory_speed_mhz", "未知"),
"内存类型": memory_summary.get("memory_type", "未知"),
"序列号": (server_info[0]["SerialNumber"] if server_info else "未知"),
"阵列卡id": (array_info[0]["Id"] if array_info else "未知"),
"阵列卡名称": (array_info[0]["Name"] if array_info else "未知"),
"BIOS Version": bios_details.get("BIOS Version", "未知"),
"Firmware Version": ilo_details.get("Firmware Version", "未知"),
"电源数量": power_details.get("电源数量", "未知"),
"总功率(W)": power_details.get("总功率(W)", "未知"),
"单个电源功率(W)": power_details.get("单个电源功率(W)", "未知"),
}
# ========== 动态添加 RAID ==========
raid_data = get_raid_data(raid_disk_map)
row.update(raid_data)
all_columns_set.update(get_raid_columns(raid_disk_map))
# ========== 动态添加 HBA ==========
hba_data = get_hba_data(fc_details)
row.update(hba_data)
all_columns_set.update(get_hba_columns(fc_details))
# ========== 动态添加 网卡(包含数量)=========
nic_data = get_nic_data(network_details)
row.update(nic_data)
all_columns_set.update(get_nic_columns(network_details))
all_server_data.append(row)
# ========== 第二步:构建最终列顺序 ==========
final_columns = list(base_columns)
# 添加 RAID 组(每组:类型 + 所有磁盘三列连续)
raid_cols = [c for c in sorted(all_columns_set) if c.startswith("RAID类型_")]
for col in raid_cols:
raid_idx = col.split("_")[1]
final_columns.append(col)
# 添加该 RAID 组下的所有磁盘列(按编号升序)
disk_cols = [f"磁盘序列号_{raid_idx}_", f"磁盘容量_{raid_idx}_", f"磁盘类型_{raid_idx}_"]
matching = [c for c in sorted(all_columns_set) if any(c.startswith(prefix) and f"_{raid_idx}_" in c for prefix in ["磁盘序列号_", "磁盘容量_", "磁盘类型_"])]
final_columns.extend(matching)
# 移除已添加的 RAID 列
all_columns_set -= set(raid_cols)
for c in [col for col in all_columns_set if col.startswith("磁盘")]:
if any(f"_{raid_idx}_" in c for raid_idx in map(str, range(1, 10))):
all_columns_set.discard(c)
# 添加 HBA
hba_cols = sorted([c for c in all_columns_set if c.startswith("hba")])
final_columns.extend(hba_cols)
all_columns_set -= set(hba_cols)
# 添加 网卡(先型号,再数量)
nic_cols = sorted([c for c in all_columns_set if c.startswith("网卡型号_")])
nic_count_col = "网卡数量" if "网卡数量" in all_columns_set else None
final_columns.extend(nic_cols)
if nic_count_col:
final_columns.append(nic_count_col)
all_columns_set.discard(nic_count_col)
all_columns_set -= set(nic_cols)
# 其他未分类字段(极少)
final_columns.extend(sorted(all_columns_set))
# ========== 第三步:构造 DataFrame 并补全缺失列 ==========
df = pd.DataFrame(all_server_data, columns=final_columns).fillna("")
# ========== 第四步:保存到 Excel ==========
try:
df.to_excel("HPE_DL380_Gen10_PLUS_info.xlsx", index=False, engine="openpyxl")
print(f"✅ 成功写入 {len(df)} 台服务器信息到 Excel")
print(f"📊 总计列数: {len(final_columns)}")
except Exception as e:
print(f"❌ 写入 Excel 失败: {e}")
except Exception as e:
print(f"❌ 主流程异常: {e}")
if __name__ == "__main__":
main()