增加web_api

This commit is contained in:
2026-02-05 15:13:54 +08:00
parent 443ec09c5c
commit d5edbc0723
43 changed files with 7036 additions and 2640 deletions

View File

@ -29,13 +29,14 @@ from datetime import datetime
import sys
import os
from collections import Counter
import yaml
try:
from tqdm import tqdm
HAS_TQDM = True
except ImportError:
HAS_TQDM = False
print("⚠️ 未安装tqdm库,将不显示进度条。如需进度条,请运行: pip install tqdm")
print("WARNING: 未安装tqdm库,将不显示进度条。如需进度条,请运行: pip install tqdm")
def create_height_bins(heights, bin_size=2.0):
@ -82,7 +83,7 @@ def create_height_bins(heights, bin_size=2.0):
# 导入qiya模块
try:
from .qiya import get_pressure_at_location
print("✅ 成功导入qiya模块")
except ImportError as e:
print(f"❌ 导入qiya模块失败: {e}")
print("请确保GasFlux包结构完整")
@ -104,13 +105,13 @@ def load_excel_data(file_path):
# 读取Excel文件
df = pd.read_excel(file_path)
print(f"✅ 成功读取数据:{len(df)} 行,{len(df.columns)} 列")
print(f"SUCCESS: Successfully loaded data: {len(df)} rows, {len(df.columns)} columns")
print(f"列名:{list(df.columns)}")
return df
except Exception as e:
print(f"❌ 读取文件失败: {e}")
print(f"ERROR: Failed to read file: {e}")
sys.exit(1)
@ -132,11 +133,11 @@ def remove_columns(df, columns_to_remove):
missing_columns = [col for col in columns_to_remove if col not in df.columns]
if missing_columns:
print(f"⚠️ 以下列不存在(跳过): {missing_columns}")
print(f"以下列不存在(跳过): {missing_columns}")
if existing_columns:
df = df.drop(columns=existing_columns)
print(f"✅ 已删除 {len(existing_columns)} 列")
print(f"已删除 {len(existing_columns)} 列")
return df
@ -158,7 +159,7 @@ def extract_hour_from_filename(filename):
if match:
return match.group(1)
else:
print(f"⚠️ 无法从文件名 '{filename}' 中提取小时信息,使用默认值 '00'")
print(f"无法从文件名 '{filename}' 中提取小时信息,使用默认值 '00'")
return "00"
@ -181,7 +182,7 @@ def fix_time_column(df, filename):
# 检查时间列是否存在
if '时间' not in df.columns:
print("❌ 未找到 '时间' 列")
print("未找到 '时间' 列")
return df
# 修正时间格式
@ -231,18 +232,18 @@ def convert_coordinates(df):
"""
print("转换经纬度坐标...")
if '经度' in df.columns:
original_lon = df['经度'].head(3).tolist()
df['经度'] = df['经度'] / 1e7
converted_lon = df['经度'].head(3).tolist()
if 'stGPSPositionX' in df.columns:
original_lon = df['stGPSPositionX'].head(3).tolist()
df['stGPSPositionX'] = df['stGPSPositionX'] / 1e7
converted_lon = df['stGPSPositionX'].head(3).tolist()
print("经度转换示例:")
for orig, conv in zip(original_lon, converted_lon):
print(".6f")
if '纬度' in df.columns:
original_lat = df['纬度'].head(3).tolist()
df['纬度'] = df['纬度'] / 1e7
converted_lat = df['纬度'].head(3).tolist()
if 'stGPSPositionY' in df.columns:
original_lat = df['stGPSPositionY'].head(3).tolist()
df['stGPSPositionY'] = df['stGPSPositionY'] / 1e7
converted_lat = df['stGPSPositionY'].head(3).tolist()
print("纬度转换示例:")
for orig, conv in zip(original_lat, converted_lat):
print(".6f")
@ -265,42 +266,54 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
"""
print("计算气压数据...")
# 检查必要列是否存在
required_cols = ['日期', '时间', '经度', '纬度', '融合高程']
# 检查必要列是否存在(支持原始列名和新列名)
# 原始数据中的列名
original_cols = ['qStrDate', 'qStrTime', 'stGPSPositionX', 'stGPSPositionY', 'fAltitudeFused']
# 坐标转换后的列名(经纬度已被除以1e7)
converted_cols = ['qStrDate', 'qStrTime', 'stGPSPositionX', 'stGPSPositionY', 'fAltitudeFused']
# 优先使用转换后的列名(如果存在),否则使用原始列名
date_col = 'qStrDate' if 'qStrDate' in df.columns else '日期'
time_col = 'qStrTime' if 'qStrTime' in df.columns else '时间'
lon_col = 'stGPSPositionX' if 'stGPSPositionX' in df.columns else '经度'
lat_col = 'stGPSPositionY' if 'stGPSPositionY' in df.columns else '纬度'
height_col = 'fAltitudeFused' if 'fAltitudeFused' in df.columns else '融合高程'
required_cols = [date_col, time_col, lon_col, lat_col, height_col]
missing_cols = [col for col in required_cols if col not in df.columns]
if missing_cols:
print(f"❌ 缺少必要列: {missing_cols}")
print(f"缺少必要列: {missing_cols}")
return df
# 检查高度变化范围
height_min = df['融合高程'].min()
height_max = df['融合高程'].max()
height_min = df[height_col].min()
height_max = df[height_col].max()
height_range = height_max - height_min
print(f"🏔️ 高度范围: {height_min:.1f} - {height_max:.1f} 米 (变化: {height_range:.1f} 米)")
print(f"高度范围: {height_min:.1f} - {height_max:.1f} 米 (变化: {height_range:.1f} 米)")
# 创建高度分档
height_bins = create_height_bins(df['融合高程'], height_bin_size)
print(f"📏 高度分档: {len(height_bins)} 个档位 (间隔: {height_bin_size:.1f} 米)")
height_bins = create_height_bins(df[height_col], height_bin_size)
print(f"高度分档: {len(height_bins)} 个档位 (间隔: {height_bin_size:.1f} 米)")
for i, (bin_min, bin_max, bin_center, count) in enumerate(height_bins):
print(f" 档位{i+1}: {bin_min:.1f}-{bin_max:.1f}m (中心: {bin_center:.1f}m, 数据: {count}行)")
# 决定计算策略
if height_range <= height_tolerance:
# 高度变化小,只计算一次气压
print("🎯 高度变化小,将使用平均高度计算一次气压")
print("高度变化小,将使用平均高度计算一次气压")
use_single_calculation = True
mean_height = df['融合高程'].mean()
print(f"📍 使用平均高度: {mean_height:.1f} 米")
mean_height = df[height_col].mean()
print(f"使用平均高度: {mean_height:.1f} 米")
elif len(height_bins) == 1:
# 只有一个高度档位,使用档位中心高度
print("📦 只有一个高度档位,使用档位中心高度")
print("只有一个高度档位,使用档位中心高度")
use_single_calculation = True
mean_height = height_bins[0][2] # bin_center
print(f"📍 使用档位中心高度: {mean_height:.1f} 米")
print(f"使用档位中心高度: {mean_height:.1f} 米")
else:
# 高度变化大,使用分档计算
print("🏗️ 使用高度分档策略,减少API调用")
print("使用高度分档策略,减少API调用")
use_single_calculation = False
# 确定要处理的行数
@ -309,10 +322,10 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
sample_df = df.copy()
actual_samples = len(df)
if not use_single_calculation:
print(f"📊 将计算所有 {len(df)} 行的气压数据")
print(f"将计算所有 {len(df)} 行的气压数据")
else:
# 限制采样数量
print(f"⚠️ 数据量较大 ({len(df)} 行),只对前 {max_samples} 行计算气压")
print(f"数据量较大 ({len(df)} 行),只对前 {max_samples} 行计算气压")
sample_df = df.head(max_samples).copy()
actual_samples = max_samples
@ -325,11 +338,11 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
first_row = sample_df.iloc[0]
# 转换日期格式 - 只提取日期部分,移除任何时间信息
date_str = str(first_row['日期'])
date_str = str(first_row[date_col])
if ' ' in date_str:
date_str = date_str.split(' ')[0]
date_str = date_str.split(' ')[0] # 处理 "2026-01-15 00:00:00" 格式
elif 'T' in date_str:
date_str = date_str.split('T')[0]
date_str = date_str.split('T')[0] # 处理ISO格式
if '/' in date_str:
date_str = date_str.replace('/', '-')
@ -343,8 +356,15 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
# 使用数据的代表性时间(整点小时,众数)
time_strings = []
for time_val in sample_df['时间']:
for time_val in sample_df[time_col]:
time_str = str(time_val).strip()
# 处理时间字符串,提取正确的部分
# 如果时间字符串包含多个时间部分(如 "00:00:00 08:37:12"),取最后一个
if ' ' in time_str:
time_parts = time_str.split()
time_str = time_parts[-1] # 取最后一个有效的时间部分
if ':' in time_str:
# 确保是有效的 HH:MM 格式,然后取整点小时
parts = time_str.split(':')
@ -368,8 +388,8 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
print("正在计算平均气压...")
pressure = get_pressure_at_location(
lat=sample_df['纬度'].mean(),
lon=sample_df['经度'].mean(),
lat=sample_df[lat_col].mean(),
lon=sample_df[lon_col].mean(),
altitude=mean_height,
date=formatted_date,
time=formatted_time
@ -377,18 +397,18 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
if pressure is not None:
pressures = [pressure] * len(sample_df)
print("✅ 平均气压计算成功,将应用到所有行")
print("平均气压计算成功,将应用到所有行")
else:
print("❌ 平均气压计算失败")
print("平均气压计算失败")
pressures = [None] * len(sample_df)
except Exception as e:
print(f"❌ 平均气压计算失败: {e}")
print(f"平均气压计算失败: {e}")
pressures = [None] * len(sample_df)
else:
# 使用高度分档策略
print("🏗️ 开始分档计算气压...")
print("开始分档计算气压...")
# 为每个高度档位计算气压
bin_pressures = {} # bin_center -> pressure
@ -402,19 +422,19 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
try:
# 使用第一行数据作为代表来获取日期和时间
# 找到这个档位中的一行数据
bin_rows = sample_df[(sample_df['融合高程'] >= bin_min) &
(sample_df['融合高程'] <= bin_max)]
bin_rows = sample_df[(sample_df[height_col] >= bin_min) &
(sample_df[height_col] <= bin_max)]
if len(bin_rows) == 0:
continue
first_row = bin_rows.iloc[0]
# 转换日期格式 - 只提取日期部分,移除任何时间信息
date_str = str(first_row['日期'])
date_str = str(first_row[date_col])
if ' ' in date_str:
date_str = date_str.split(' ')[0]
date_str = date_str.split(' ')[0] # 处理 "2026-01-15 00:00:00" 格式
elif 'T' in date_str:
date_str = date_str.split('T')[0]
date_str = date_str.split('T')[0] # 处理ISO格式
if '/' in date_str:
date_str = date_str.replace('/', '-')
@ -424,14 +444,21 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
year, month, day = date_parts
formatted_date = f"{year}-{month.zfill(2)}-{day.zfill(2)}"
else:
print(f"⚠️ 档位高度 {bin_center:.1f}m 日期格式异常: {date_str}")
print(f"档位高度 {bin_center:.1f}m 日期格式异常: {date_str}")
bin_pressures[bin_center] = None
continue
# 使用该档位数据的代表性时间(整点小时)
time_strings = []
for time_val in bin_rows['时间']:
for time_val in bin_rows[time_col]:
time_str = str(time_val).strip()
# 处理时间字符串,提取正确的部分
# 如果时间字符串包含多个时间部分(如 "00:00:00 08:37:12"),取最后一个
if ' ' in time_str:
time_parts = time_str.split()
time_str = time_parts[-1] # 取最后一个有效的时间部分
if ':' in time_str:
# 确保是有效的 HH:MM 格式,然后取整点小时
parts = time_str.split(':')
@ -456,8 +483,8 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
print(" 无有效时间数据,使用默认中午12:00")
# 计算这个档位的气压(使用平均位置和档位中心高度)
avg_lat = bin_rows['纬度'].mean()
avg_lon = bin_rows['经度'].mean()
avg_lat = bin_rows[lat_col].mean()
avg_lon = bin_rows[lon_col].mean()
pressure = get_pressure_at_location(
lat=avg_lat,
@ -475,14 +502,14 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
iterator.set_description(f"计算档位 (成功: {success_count}/{len(bin_pressures)})")
except Exception as e:
print(f"❌ 计算高度档位 {bin_center:.1f}m 气压失败: {e}")
print(f"计算高度档位 {bin_center:.1f}m 气压失败: {e}")
bin_pressures[bin_center] = None
# 为每一行分配对应档位的气压
pressures = []
for idx, row in sample_df.iterrows():
# 找到这个高度对应的档位
height = row['融合高程']
height = row[height_col]
assigned_pressure = None
for bin_min, bin_max, bin_center, count in height_bins:
@ -492,7 +519,7 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
pressures.append(assigned_pressure)
print(f"✅ 完成分档气压计算,共 {len(bin_pressures)} 个档位,{len([p for p in bin_pressures.values() if p is not None])} 个成功")
print(f"完成分档气压计算,共 {len(bin_pressures)} 个档位,{len([p for p in bin_pressures.values() if p is not None])} 个成功")
# 添加气压列
df['pressure'] = None # 初始化
@ -513,7 +540,7 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s
avg_pressure = sum(valid_pressures) / len(valid_pressures)
print(f"成功计算 {len(valid_pressures)}/{actual_samples} 个气压值,平均值: {avg_pressure:.1f} hPa")
else:
print("⚠️ 未能计算出任何气压值")
print("未能计算出任何气压值")
return df
@ -529,13 +556,13 @@ def adjust_altitude(df):
"""
print("调整融合高程...")
if '融合高程' in df.columns:
min_altitude = df['融合高程'].min()
if 'fAltitudeFused' in df.columns:
min_altitude = df['fAltitudeFused'].min()
print(".2f")
original_alt = df['融合高程'].head(3).tolist()
df['融合高程'] = df['融合高程'] - min_altitude
adjusted_alt = df['融合高程'].head(3).tolist()
original_alt = df['fAltitudeFused'].head(3).tolist()
df['fAltitudeFused'] = df['fAltitudeFused'] - min_altitude
adjusted_alt = df['fAltitudeFused'].head(3).tolist()
print("高度调整示例:")
for orig, adj in zip(original_alt, adjusted_alt):
@ -546,7 +573,7 @@ def adjust_altitude(df):
def merge_timestamp(df):
"""
融合日期和时间列为时间戳
融合日期和时间列为时间戳(修正时间格式)
Args:
df: 输入DataFrame
@ -554,57 +581,48 @@ def merge_timestamp(df):
Returns:
pd.DataFrame: 融合后的DataFrame
"""
print("融合日期和时间...")
print("融合日期和时间(修正时间格式)...")
if '日期' in df.columns and '时间' in df.columns:
if 'qStrDate' in df.columns and 'qStrTime' in df.columns:
timestamps = []
for idx, row in df.iterrows():
try:
date_str = str(row['日期'])
time_str = str(row['时间'])
date_str = str(row['qStrDate']).strip()
time_str = str(row['qStrTime']).strip()
# 清理日期字符串 - 移除任何时间部分
date_str = date_str.strip()
# 处理日期字符串,提取纯日期部分
# 如果日期字符串包含时间部分(如 "2026-01-15 00:00:00"),取日期部分
if ' ' in date_str:
date_str = date_str.split(' ')[0] # 只取日期部分
if 'T' in date_str:
date_str = date_str.split('T')[0] # 处理ISO格式
date_parts = date_str.split()
date_str = date_parts[0] # 取第一个部分作为日期
# 标准化日期格式
if '/' in date_str:
date_str = date_str.replace('/', '-')
# 处理时间字符串,提取正确的部分
# 如果时间字符串包含多个时间部分(如 "00:00:00 08:37:12"),取最后一个
if ' ' in time_str:
time_parts = time_str.split()
# 取最后一个有效的时间部分
time_str = time_parts[-1]
# 确保日期格式正确
date_parts = date_str.split('-')
if len(date_parts) == 3:
year, month, day = date_parts
date_formatted = f"{year.zfill(4)}-{month.zfill(2)}-{day.zfill(2)}"
else:
print(f"⚠️ 日期格式异常: '{date_str}',使用当前日期")
date_formatted = datetime.now().strftime("%Y-%m-%d")
# 时间字符串已经是修正后的格式(如 "08:34:01"),直接使用
time_str = time_str.strip()
# 确保时间格式正确
if ':' in time_str and len(time_str.split(':')) >= 2:
time_formatted = time_str
# 组合日期和修正后的时间
timestamp = f"{date_str} {time_str}"
else:
print(f"⚠️ 时间格式异常: '{time_str}',使用默认时间")
time_formatted = "12:00:00"
timestamp = f"{date_str} 12:00:00" # 默认中午时间
print(f" 时间格式异常 '{row['qStrTime']}',使用默认时间")
# 组合时间戳 - 直接连接日期和时间
timestamp = f"{date_formatted} {time_formatted}"
timestamps.append(timestamp)
except Exception as e:
print(f"❌ 处理第 {idx+1} 行时间戳失败: {e}")
print(f"处理第 {idx+1} 行时间戳失败: {e}")
timestamps.append(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
df['timestamp'] = timestamps
print("时间戳融合示例:")
for i in range(min(3, len(timestamps))):
print(f" {df.loc[i, '日期']} + {df.loc[i, '时间']} → {timestamps[i]}")
print(f" {df.loc[i, 'qStrDate']} + {df.loc[i, 'qStrTime']} → {timestamps[i]}")
return df
@ -623,83 +641,104 @@ def rename_columns(df):
# 定义字段映射
column_mapping = {
'timestamp': 'timestamp', # 时间戳(已创建)
'经度': 'longitude', # 经度 → latitude
'纬度': 'latitude', # 纬度 → longitude
'融合高程': 'height_ato', # 融合高程 → height_ato
'修正风向': 'winddir', # 修正风向 → winddir
'修正风速': 'windspeed', # 修正风速 → windspeed
'风温': 'temperature', # 风温 → temperature
'pressure': 'pressure', # 气压(已计算)
'CH4': 'ch4', # CH4保持不变
'pitch': 'course_elevation', # pitch → course_elevation
'yaw': 'course_azimuth' # yaw → course_azimuth
'timestamp': 'timestamp', # 时间戳(已创建)
'stGPSPositionX': 'longitude', # 经度 → longitude
'stGPSPositionY': 'latitude', # 纬度 → latitude
'fAltitudeFused': 'height_ato', # 融合高程 → height_ato
'fFixedWindDirection': 'winddir', # 修正风向 → winddir
'fFixedWindSpeed': 'windspeed', # 修正风速 → windspeed
'fWindTemperature': 'temperature', # 风温 → temperature
'CO2': 'CO2', # CO2浓度 → co2
'pitch': 'course_elevation', # pitch → course_elevation
'yaw': 'course_azimuth' # yaw → course_azimuth
}
# 重命名存在的列
columns_to_rename = {}
# 先复制字段,再对复制的字段重命名
columns_renamed = []
for old_name, new_name in column_mapping.items():
if old_name in df.columns:
columns_to_rename[old_name] = new_name
# 复制字段到新名称
df[new_name] = df[old_name].copy()
columns_renamed.append((old_name, new_name))
print(f" 复制并重命名: {old_name} → {new_name}")
if columns_to_rename:
df = df.rename(columns=columns_to_rename)
print("字段重命名:")
for old, new in columns_to_rename.items():
print(f" {old} → {new}")
# 只保留GasFlux需要的列
required_columns = ['timestamp', 'latitude', 'longitude', 'height_ato', 'windspeed', 'winddir', 'temperature', 'pressure', 'ch4', 'course_elevation', 'course_azimuth']
existing_required_columns = [col for col in required_columns if col in df.columns]
if len(existing_required_columns) != len(required_columns):
missing = [col for col in required_columns if col not in df.columns]
print(f"⚠️ 缺少必需列: {missing}")
# 移除不需要的列,只保留必需的列
df = df[existing_required_columns]
print(f"最终保留列: {existing_required_columns}")
if columns_renamed:
print(f"共处理了 {len(columns_renamed)} 个字段")
return df
def process_excel_file(file_path):
def ensure_float64_types(df, config=None):
"""
确保数值字段为float64类型,以满足GasFlux处理要求
Args:
df: 输入DataFrame
config: 配置字典,包含gases字段
Returns:
pd.DataFrame: 数据类型转换后的DataFrame
"""
print("确保数值字段类型为float64...")
# 定义基础需要转换为float64的字段
float64_columns = [
'longitude', 'latitude', 'height_ato', # 位置和高度
'winddir', 'windspeed', 'temperature', # 风和温度
'pressure', # 气压
'course_elevation', 'course_azimuth' # 姿态角
]
# 如果提供了配置,添加gases中的气体列
if config and 'gases' in config:
gas_columns = list(config['gases'].keys())
float64_columns.extend(gas_columns)
print(f"从配置中添加气体列: {gas_columns}")
converted_count = 0
for col in float64_columns:
if col in df.columns:
try:
original_dtype = df[col].dtype
df[col] = df[col].astype('float64')
new_dtype = df[col].dtype
if original_dtype != new_dtype:
print(f" 转换: {col} ({original_dtype} → {new_dtype})")
converted_count += 1
except Exception as e:
print(f" 转换失败: {col} - {e}")
if converted_count > 0:
print(f"共转换了 {converted_count} 个字段的数据类型为float64")
else:
print("所有数值字段已经是float64类型")
return df
def process_excel_file(file_path, config_path=None):
"""
处理单个Excel文件的主函数
Args:
file_path: Excel文件路径
config_path: 配置文件路径,用于读取gases配置
"""
print(f"=== 开始处理文件: {file_path} ===\n")
# 获取文件名(用于时间修正)
filename = Path(file_path).name
# 1. 读取数据
df = load_excel_data(file_path)
# 2. 删除不需要的列
columns_to_remove = [
'高程', '速度x', '速度y', '速度z',
'四元数_q0', '四元数_q1', '四元数_q2', '四元数_q3',
'roll', 'H2O', # 保留pitch和yaw,将重命名为course_elevation和course_azimuth
'原始风向', '原始风速'
]
df = remove_columns(df, columns_to_remove)
# 3. 修正时间格式
df = fix_time_column(df, filename)
# 4. 坐标转换
# 2. 坐标转换
df = convert_coordinates(df)
# 5. 计算气压
# 4. 计算气压数据
df = calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_size=2.0) # 计算所有行,高度容差10米,分档2米
# 6. 高度调整
# 5. 高度调整
df = adjust_altitude(df)
# 7. 时间戳融合
# 6. 时间戳融合(保持原有时间格式)
df = merge_timestamp(df)
# 调试:检查当前列
@ -707,28 +746,41 @@ def process_excel_file(file_path):
if 'timestamp' in df.columns:
print(f"timestamp列示例: {df['timestamp'].head(3).tolist()}")
# 8. 字段重命名
# 7. 字段重命名
df = rename_columns(df)
# 8. 确保数值字段类型为float64
# 读取配置以获取gases字段
config = None
if config_path:
try:
with open(config_path, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
except Exception as e:
print(f"读取配置文件失败: {e}")
df = ensure_float64_types(df, config)
# 保存处理结果
output_path = Path(file_path).with_suffix('.processed.csv')
df.to_csv(output_path, index=False)
df.to_csv(output_path, index=False, encoding='utf-8-sig')
print(f"\n✅ 处理完成!")
print(f"📁 输出文件: {output_path}")
print(f"📊 最终数据形状: {df.shape[0]} 行 × {df.shape[1]} 列")
print(f"📋 最终列名: {list(df.columns)}")
print(f"\n处理完成!")
print(f"输出文件: {output_path}")
print(f"最终数据形状: {df.shape[0]} 行 × {df.shape[1]} 列")
print(f"最终列名: {list(df.columns)}")
return df
def process_file(input_file, output_file=None):
def process_file(input_file, output_file=None, config_file=None):
"""
直接处理Excel文件的函数(不使用命令行参数)
Args:
input_file: 输入Excel文件路径(字符串或Path对象)
output_file: 输出CSV文件路径(可选,字符串或Path对象)
config_file: 配置文件路径(可选,用于读取gases配置)
Returns:
pd.DataFrame: 处理后的DataFrame
@ -744,13 +796,13 @@ def process_file(input_file, output_file=None):
raise ValueError(f"输入文件必须是Excel格式 (.xlsx 或 .xls),当前文件: {input_path}")
# 处理文件
df = process_excel_file(str(input_path))
df = process_excel_file(str(input_path), config_file)
# 如果指定了输出路径,额外保存一份
if output_file:
output_path = Path(output_file)
df.to_csv(output_path, index=False)
print(f"📁 额外保存到: {output_path}")
df.to_csv(output_path, index=False, encoding='utf-8-sig')
print(f"额外保存到: {output_path}")
return df
@ -769,17 +821,17 @@ def interactive_input():
while True:
input_file = input("请输入Excel文件路径 (例如: data.xlsx): ").strip()
if not input_file:
print("❌ 文件路径不能为空,请重新输入")
print("文件路径不能为空,请重新输入")
continue
input_path = Path(input_file)
if not input_path.exists():
print(f"❌ 文件不存在: {input_path}")
print(f"文件不存在: {input_path}")
print("提示: 请确保文件路径正确,或者将文件放在当前目录下")
continue
if input_path.suffix.lower() not in ['.xlsx', '.xls']:
print(f"❌ 文件格式错误: {input_path.suffix}")
print(f"文件格式错误: {input_path.suffix}")
print("只支持 .xlsx 和 .xls 格式的Excel文件")
continue
@ -791,13 +843,13 @@ def interactive_input():
output_file = None
print("使用默认输出文件名")
print(f"\n✅ 输入确认:")
print(f"\n输入确认:")
print(f" 输入文件: {input_file}")
print(f" 输出文件: {output_file or '自动生成'}")
confirm = input("\n确认开始处理? (y/N): ").strip().lower()
if confirm not in ['y', 'yes', '是', '确认']:
print("❌ 用户取消操作")
print("用户取消操作")
return None, None
return input_file, output_file
@ -823,7 +875,7 @@ def main(input_file=None, output_file=None, interactive=False):
try:
return process_file(input_file, output_file)
except Exception as e:
print(f"❌ 处理失败: {e}")
print(f"处理失败: {e}")
raise
# 否则使用命令行参数
@ -881,7 +933,7 @@ def main(input_file=None, output_file=None, interactive=False):
if not input_file:
parser.print_help()
print("\n" + "="*60)
print("📖 使用示例:")
print("使用示例:")
print("="*60)
print("1. 命令行模式:")
print(" python data_processor.py your_file.xlsx")
@ -900,11 +952,11 @@ def main(input_file=None, output_file=None, interactive=False):
# 检查输入文件
input_path = Path(input_file)
if not input_path.exists():
print(f"❌ 错误:输入文件不存在: {input_path}")
print(f"错误:输入文件不存在: {input_path}")
sys.exit(1)
if input_path.suffix.lower() not in ['.xlsx', '.xls']:
print(f"❌ 错误:输入文件必须是Excel格式 (.xlsx 或 .xls)")
print(f"错误:输入文件必须是Excel格式 (.xlsx 或 .xls)")
sys.exit(1)
# 处理文件
@ -914,16 +966,16 @@ def main(input_file=None, output_file=None, interactive=False):
# 如果指定了输出路径,额外保存一份
if output_file:
output_path = Path(output_file)
df.to_csv(output_path, index=False)
print(f"📁 额外保存到: {output_path}")
df.to_csv(output_path, index=False, encoding='utf-8-sig')
print(f"额外保存到: {output_path}")
return df
except KeyboardInterrupt:
print("\n⚠️ 用户中断处理")
print("\n用户中断处理")
sys.exit(1)
except Exception as e:
print(f"\n❌ 处理失败: {e}")
print(f"\n处理失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)