chore: 批量新增各类工具脚本与配置文件
1. 新增音频录制、下载、上传相关脚本 2. 新增数据库操作、API调用工具 3. 新增Excel数据处理脚本 4. 新增弱密码检测脚本
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
from requests import get
|
||||
import time
|
||||
import os
|
||||
import pandas as pd
|
||||
import tqdm
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
teamId = 19
|
||||
ip = "http://band.hxzhxy.cn"
|
||||
param = {
|
||||
"username": "xtl",
|
||||
"password": "xia123456",
|
||||
"client_id": "client",
|
||||
"grant_type": "password",
|
||||
"client_secret": "123456",
|
||||
}
|
||||
|
||||
data = get(url=f"{ip}/oauth/token", params=param).json()
|
||||
token = data["access_token"]
|
||||
header = {
|
||||
"content-type": "application/json",
|
||||
"authorization": f"Bearer {token}",
|
||||
}
|
||||
|
||||
url = f"{ip}/pay/trads/findPayTtadsNews"
|
||||
output_dir = r"e:\project\python\工具\智慧校园\本部\消费数据\24-26消费信息"
|
||||
|
||||
FIELD_MAP = {
|
||||
"gradeName": "年级名称",
|
||||
"termName": "交易地点",
|
||||
"teamId": "团队ID",
|
||||
"stuId": "学生ID",
|
||||
"areaName": "设备区域名称",
|
||||
"bagName": "交易账户",
|
||||
"userNumb": "学号/工号",
|
||||
"dealCount": "交易流水号/笔次",
|
||||
"className": "班级名称",
|
||||
"remark": "备注/充值类型",
|
||||
"cardCode": "物理卡号",
|
||||
"dealType": "消费方式",
|
||||
"cardValue": "余额",
|
||||
"userXm": "姓名",
|
||||
"cardNo": "卡号",
|
||||
"createDate": "入库时间",
|
||||
"dealValue": "交易金额",
|
||||
"recordType": "记录类型",
|
||||
"id": "记录ID",
|
||||
"recordId": "终端原始记录ID",
|
||||
"userId": "用户唯一ID",
|
||||
"dealTime": "交易时间"
|
||||
}
|
||||
|
||||
OUTPUT_COLUMNS = list(FIELD_MAP.values())
|
||||
|
||||
|
||||
def day_range(start_date, end_date):
|
||||
current = start_date
|
||||
while current <= end_date:
|
||||
yield current
|
||||
current += timedelta(days=1)
|
||||
|
||||
|
||||
def month_range(start_date, end_date):
|
||||
current = datetime(start_date.year, start_date.month, 1)
|
||||
end_month = datetime(end_date.year, end_date.month, 1)
|
||||
while current <= end_month:
|
||||
yield current
|
||||
if current.month == 12:
|
||||
current = datetime(current.year + 1, 1, 1)
|
||||
else:
|
||||
current = datetime(current.year, current.month + 1, 1)
|
||||
|
||||
|
||||
def to_timestamp_ms(dt):
|
||||
return int(dt.timestamp() * 1000)
|
||||
|
||||
|
||||
def fetch_day_records(day_dt):
|
||||
day_start = datetime(day_dt.year, day_dt.month, day_dt.day, 0, 0, 0)
|
||||
day_end = datetime(day_dt.year, day_dt.month, day_dt.day, 23, 59, 59)
|
||||
|
||||
begin_time = to_timestamp_ms(day_start)
|
||||
end_time = to_timestamp_ms(day_end)
|
||||
|
||||
page_number = 0
|
||||
page_size = 100
|
||||
day_records = []
|
||||
|
||||
while True:
|
||||
params = {
|
||||
"teamId": teamId,
|
||||
"pageNumber": page_number,
|
||||
"pageSize": page_size,
|
||||
"screen": 1,
|
||||
"beginTime": begin_time,
|
||||
"endTime": end_time,
|
||||
"name": "",
|
||||
"recordType": 1,
|
||||
"termName": "",
|
||||
"type": 1,
|
||||
"classId": "",
|
||||
"gradeId": ""
|
||||
}
|
||||
|
||||
resp = get(url=url, params=params, headers=header, timeout=60)
|
||||
resp.raise_for_status()
|
||||
result = resp.json()
|
||||
|
||||
if not result.get("success"):
|
||||
raise Exception(f"接口返回失败: {result}")
|
||||
|
||||
data = result.get("data") or {}
|
||||
content = data.get("content") or []
|
||||
total_pages = data.get("totalPages", 0)
|
||||
total_elements = data.get("totalElements", 0)
|
||||
|
||||
day_records.extend(content)
|
||||
|
||||
print(
|
||||
f"{day_dt.strftime('%Y-%m-%d')} 第 {page_number + 1}/{max(total_pages, 1)} 页,"
|
||||
f"当前页 {len(content)} 条,当天累计 {len(day_records)} 条,总计 {total_elements} 条"
|
||||
)
|
||||
|
||||
page_number += 1
|
||||
if page_number >= total_pages or total_pages == 0:
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
return day_records
|
||||
|
||||
|
||||
def parse_date(date_str):
|
||||
return datetime.strptime(date_str, "%Y-%m-%d")
|
||||
|
||||
|
||||
def convert_records_to_df(records):
|
||||
rows = []
|
||||
for record in records:
|
||||
row = {}
|
||||
for en_key, cn_key in FIELD_MAP.items():
|
||||
row[cn_key] = record.get(en_key)
|
||||
rows.append(row)
|
||||
if not rows:
|
||||
return pd.DataFrame(columns=OUTPUT_COLUMNS)
|
||||
return pd.DataFrame(rows, columns=OUTPUT_COLUMNS)
|
||||
|
||||
|
||||
def get_month_start_end(month_dt, start_date, end_date):
|
||||
month_start = datetime(month_dt.year, month_dt.month, 1)
|
||||
if month_dt.month == 12:
|
||||
next_month = datetime(month_dt.year + 1, 1, 1)
|
||||
else:
|
||||
next_month = datetime(month_dt.year, month_dt.month + 1, 1)
|
||||
month_end = next_month - timedelta(days=1)
|
||||
|
||||
if month_start < start_date:
|
||||
month_start = start_date
|
||||
if month_end > end_date:
|
||||
month_end = end_date
|
||||
|
||||
return month_start, month_end
|
||||
|
||||
|
||||
def save_month_excel(month_dt, records):
|
||||
df = convert_records_to_df(records)
|
||||
file_name = f"{month_dt.strftime('%Y%m')}_学生消费记录.xlsx"
|
||||
file_path = os.path.join(output_dir, file_name)
|
||||
df.to_excel(file_path, index=False)
|
||||
return file_path, len(df)
|
||||
|
||||
|
||||
def main():
|
||||
print("开始拉取数据...")
|
||||
start_date_str = os.getenv("START_DATE", "2024-01-01")
|
||||
end_date_str = os.getenv("END_DATE", "2026-05-19")
|
||||
|
||||
start_date = parse_date(start_date_str)
|
||||
end_date = parse_date(end_date_str)
|
||||
|
||||
all_month_list = list(month_range(start_date, end_date))
|
||||
total_records = 0
|
||||
success_months = 0
|
||||
|
||||
with tqdm.tqdm(all_month_list, desc="按月拉取消费记录") as month_pbar:
|
||||
for month_dt in month_pbar:
|
||||
try:
|
||||
month_start, month_end = get_month_start_end(month_dt, start_date, end_date)
|
||||
month_records = []
|
||||
month_days = list(day_range(month_start, month_end))
|
||||
|
||||
for day_dt in month_days:
|
||||
day_records = fetch_day_records(day_dt)
|
||||
month_records.extend(day_records)
|
||||
time.sleep(0.2)
|
||||
|
||||
file_path, count = save_month_excel(month_dt, month_records)
|
||||
total_records += count
|
||||
success_months += 1
|
||||
month_pbar.set_postfix({
|
||||
"month": month_dt.strftime("%Y-%m"),
|
||||
"count": count,
|
||||
"total": total_records
|
||||
})
|
||||
print(f"已输出: {file_path}")
|
||||
except Exception as e:
|
||||
print(f"{month_dt.strftime('%Y-%m')} 拉取失败: {e}")
|
||||
|
||||
print("\n完成")
|
||||
print(f"成功月份: {success_months}")
|
||||
print(f"总记录数: {total_records}")
|
||||
print(f"输出目录: {output_dir}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,89 @@
|
||||
import pandas as pd
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 读取Excel文件
|
||||
file_path = r'e:\project\python\工具\智慧校园\本部\消费数据\2024学生.xlsx'
|
||||
df = pd.read_excel(file_path)
|
||||
|
||||
print("=" * 80)
|
||||
print("学生消费数据提取")
|
||||
print("=" * 80)
|
||||
|
||||
# 1. 提取年级ID和班级ID(供用户查表)
|
||||
grade_ids = sorted(df['gradeId'].unique().tolist())
|
||||
class_ids = sorted(df['classId'].unique().tolist())
|
||||
|
||||
print(f"\n【年级ID列表】(共{len(grade_ids)}个):")
|
||||
print(grade_ids)
|
||||
|
||||
print(f"\n【班级ID列表】(共{len(class_ids)}个):")
|
||||
print(class_ids)
|
||||
|
||||
# 2. 提取学生消费信息
|
||||
student_records = []
|
||||
|
||||
for _, row in df.iterrows():
|
||||
record = {
|
||||
'学号': str(row['stuNo']),
|
||||
'姓名': row['stuName'],
|
||||
'年级ID': row['gradeId'],
|
||||
'班级ID': row['classId'],
|
||||
'支付金额(元)': row['totalFee'] / 100, # 转换为元
|
||||
'支付状态': '支付成功' if row['payState'] == 1 else '其他',
|
||||
'创建时间': row['createTime'].strftime('%Y-%m-%d %H:%M:%S') if pd.notna(row['createTime']) else '',
|
||||
'支付时间': row['payTime'].strftime('%Y-%m-%d %H:%M:%S') if pd.notna(row['payTime']) else '',
|
||||
'物理卡号': str(row['serialNo']) if pd.notna(row['serialNo']) else '',
|
||||
'teamId': row['teamId']
|
||||
}
|
||||
student_records.append(record)
|
||||
|
||||
# 3. 统计信息
|
||||
print(f"\n【数据统计】")
|
||||
print(f"总记录数: {len(student_records)}")
|
||||
print(f"唯一学生数: {df['stuNo'].nunique()}")
|
||||
print(f"总金额: {df['totalFee'].sum() / 100:.2f} 元")
|
||||
|
||||
# 4. 按年级统计
|
||||
print(f"\n【按年级统计】")
|
||||
grade_stats = df.groupby('gradeId').agg({
|
||||
'stuNo': 'nunique',
|
||||
'totalFee': 'sum'
|
||||
}).reset_index()
|
||||
grade_stats.columns = ['年级ID', '学生人数', '总金额(分)']
|
||||
grade_stats['总金额(元)'] = grade_stats['总金额(分)'] / 100
|
||||
print(grade_stats.to_string(index=False))
|
||||
|
||||
# 5. 按班级统计
|
||||
print(f"\n【按班级统计】")
|
||||
class_stats = df.groupby('classId').agg({
|
||||
'stuNo': 'nunique',
|
||||
'totalFee': 'sum'
|
||||
}).reset_index()
|
||||
class_stats.columns = ['班级ID', '学生人数', '总金额(分)']
|
||||
class_stats['总金额(元)'] = class_stats['总金额(分)'] / 100
|
||||
print(class_stats.to_string(index=False))
|
||||
|
||||
# 6. 保存详细记录到JSON
|
||||
output_file = r'e:\project\python\工具\智慧校园\本部\消费数据\student_records.json'
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(student_records, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n【输出文件】")
|
||||
print(f"详细记录已保存到: {output_file}")
|
||||
|
||||
# 7. 保存ID映射表(等待用户补充年级和班级名称)
|
||||
id_mapping = {
|
||||
'grade_ids': grade_ids,
|
||||
'class_ids': class_ids,
|
||||
'note': '请查询数据库补充年级名称和班级名称'
|
||||
}
|
||||
|
||||
id_mapping_file = r'e:\project\python\工具\智慧校园\本部\消费数据\id_mapping.json'
|
||||
with open(id_mapping_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(id_mapping, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"ID映射表已保存到: {id_mapping_file}")
|
||||
print("\n请查询数据库,告诉我以下ID对应的名称:")
|
||||
print(f"年级ID: {grade_ids}")
|
||||
print(f"班级ID: {class_ids}")
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "72fd36ef",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.12.4"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import pandas as pd
|
||||
import json
|
||||
|
||||
# 读取Excel文件
|
||||
df = pd.read_excel('e:/project/python/工具/智慧校园/本部/消费数据/2024学生.xlsx')
|
||||
|
||||
print("=== Excel文件基本信息 ===")
|
||||
print(f"数据形状: {df.shape}")
|
||||
print(f"\n列名: {df.columns.tolist()}")
|
||||
|
||||
print("\n=== 前10行数据 ===")
|
||||
print(df.head(10))
|
||||
|
||||
print("\n=== 数据类型 ===")
|
||||
print(df.dtypes)
|
||||
|
||||
# 提取唯一值
|
||||
print("\n=== 唯一值统计 ===")
|
||||
for col in df.columns:
|
||||
unique_count = df[col].nunique()
|
||||
print(f"{col}: {unique_count} 个唯一值")
|
||||
if unique_count <= 20:
|
||||
print(f" 值: {df[col].unique().tolist()}")
|
||||
|
||||
# 检查是否有年级和班级相关字段
|
||||
grade_class_cols = [col for col in df.columns if any(keyword in col.lower() for keyword in ['年级', '班级', 'grade', 'class'])]
|
||||
print(f"\n=== 年级/班级相关字段: {grade_class_cols} ===")
|
||||
|
||||
# 保存列名信息供后续使用
|
||||
with open('e:/project/python/工具/智慧校园/本部/消费数据/excel_info.json', 'w', encoding='utf-8') as f:
|
||||
info = {
|
||||
'columns': df.columns.tolist(),
|
||||
'shape': df.shape,
|
||||
'sample_data': df.head(5).to_dict('records')
|
||||
}
|
||||
json.dump(info, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print("\n=== 信息已保存到 excel_info.json ===")
|
||||
@@ -0,0 +1,24 @@
|
||||
import pandas as pd
|
||||
import sys
|
||||
|
||||
# 读取Excel文件
|
||||
file_path = r'e:\project\python\工具\智慧校园\本部\消费数据\2024学生.xlsx'
|
||||
df = pd.read_excel(file_path)
|
||||
|
||||
print("数据形状:", df.shape)
|
||||
print("\n列名:")
|
||||
for i, col in enumerate(df.columns):
|
||||
print(f"{i+1}. {col}")
|
||||
|
||||
print("\n前5行数据:")
|
||||
print(df.head().to_string())
|
||||
|
||||
# 检查年级和班级相关字段
|
||||
print("\n查找年级/班级相关字段...")
|
||||
for col in df.columns:
|
||||
if any(keyword in str(col) for keyword in ['年级', '班级', 'grade', 'class', 'Grade', 'Class']):
|
||||
print(f"找到字段: {col}")
|
||||
print(f"唯一值: {df[col].unique()[:10]}") # 只显示前10个唯一值
|
||||
|
||||
print("\n数据类型:")
|
||||
print(df.dtypes)
|
||||
@@ -0,0 +1,17 @@
|
||||
import pandas as pd
|
||||
|
||||
# 读取年级表和班级表
|
||||
grade_df = pd.read_excel(r'e:\project\python\工具\智慧校园\本部\消费数据\年级表.xlsx')
|
||||
class_df = pd.read_excel(r'e:\project\python\工具\智慧校园\本部\消费数据\班级表.xlsx')
|
||||
|
||||
print("=== 年级表 ===")
|
||||
print(f"形状: {grade_df.shape}")
|
||||
print(f"列名: {grade_df.columns.tolist()}")
|
||||
print("\n前10行:")
|
||||
print(grade_df.head(10))
|
||||
|
||||
print("\n=== 班级表 ===")
|
||||
print(f"形状: {class_df.shape}")
|
||||
print(f"列名: {class_df.columns.tolist()}")
|
||||
print("\n前10行:")
|
||||
print(class_df.head(10))
|
||||
@@ -0,0 +1,140 @@
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
|
||||
# 读取所有数据
|
||||
print("正在读取数据...")
|
||||
consumption_df = pd.read_excel(r'e:\project\python\工具\智慧校园\本部\消费数据\2024学生.xlsx')
|
||||
grade_df = pd.read_excel(r'e:\project\python\工具\智慧校园\本部\消费数据\年级表.xlsx')
|
||||
class_df = pd.read_excel(r'e:\project\python\工具\智慧校园\本部\消费数据\班级表.xlsx')
|
||||
|
||||
print(f"消费数据: {consumption_df.shape[0]} 条记录")
|
||||
print(f"年级表: {grade_df.shape[0]} 个年级")
|
||||
print(f"班级表: {class_df.shape[0]} 个班级")
|
||||
|
||||
# 创建年级ID到名称的映射
|
||||
grade_map = dict(zip(grade_df['id'], grade_df['grade_name']))
|
||||
|
||||
# 创建班级ID到名称的映射
|
||||
class_map = dict(zip(class_df['id'], class_df['class_name']))
|
||||
|
||||
# 创建班级ID到年级ID的映射(用于验证)
|
||||
class_to_grade_map = dict(zip(class_df['id'], class_df['grade_id']))
|
||||
|
||||
print("\n正在处理消费数据...")
|
||||
|
||||
# 处理消费数据
|
||||
processed_data = []
|
||||
|
||||
for _, row in consumption_df.iterrows():
|
||||
grade_id = row['gradeId']
|
||||
class_id = row['classId']
|
||||
|
||||
# 获取年级和班级名称
|
||||
grade_name = grade_map.get(grade_id, f'未知年级({grade_id})')
|
||||
class_name = class_map.get(class_id, f'未知班级({class_id})')
|
||||
|
||||
# 处理时间格式
|
||||
create_time = row['createTime']
|
||||
pay_time = row['payTime']
|
||||
|
||||
if pd.notna(create_time):
|
||||
if isinstance(create_time, str):
|
||||
create_time_str = create_time
|
||||
else:
|
||||
create_time_str = create_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
create_time_str = ''
|
||||
|
||||
if pd.notna(pay_time):
|
||||
if isinstance(pay_time, str):
|
||||
pay_time_str = pay_time
|
||||
else:
|
||||
pay_time_str = pay_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
pay_time_str = ''
|
||||
|
||||
# 处理支付状态
|
||||
pay_state_map = {0: '确认中', 1: '支付成功', 2: '已取消', 3: '退款'}
|
||||
pay_state = pay_state_map.get(row['payState'], f'未知状态({row["payState"]})')
|
||||
|
||||
# 处理充值状态
|
||||
change_state_map = {0: '充值中', 1: '已取消', 2: '充值成功', 3: '充值失败'}
|
||||
change_state = change_state_map.get(row['changeState'], f'未知状态({row["changeState"]})')
|
||||
|
||||
record = {
|
||||
'学号': str(row['stuNo']),
|
||||
'姓名': row['stuName'],
|
||||
'年级ID': grade_id,
|
||||
'年级名称': grade_name,
|
||||
'班级ID': class_id,
|
||||
'班级名称': class_name,
|
||||
'支付金额(元)': row['totalFee'] / 100,
|
||||
'支付状态': pay_state,
|
||||
'充值状态': change_state,
|
||||
'创建时间': create_time_str,
|
||||
'支付时间': pay_time_str,
|
||||
'物理卡号': str(int(row['serialNo'])) if pd.notna(row['serialNo']) else '',
|
||||
'订单编号': row['outTradeNo'],
|
||||
'微信订单号': row['transactionId'] if pd.notna(row['transactionId']) else '',
|
||||
'支付手机号': str(row['wxPhone']),
|
||||
'teamId': row['teamId']
|
||||
}
|
||||
processed_data.append(record)
|
||||
|
||||
# 创建DataFrame
|
||||
result_df = pd.DataFrame(processed_data)
|
||||
|
||||
# 生成输出文件名
|
||||
output_file = r'e:\project\python\工具\智慧校园\本部\消费数据\学生消费记录整理.xlsx'
|
||||
|
||||
# 写入Excel,使用多个sheet
|
||||
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
|
||||
# Sheet 1: 详细记录
|
||||
result_df.to_excel(writer, sheet_name='详细记录', index=False)
|
||||
|
||||
# Sheet 2: 按年级汇总
|
||||
grade_summary = result_df.groupby(['年级ID', '年级名称']).agg({
|
||||
'学号': 'nunique',
|
||||
'支付金额(元)': 'sum',
|
||||
'姓名': 'count'
|
||||
}).reset_index()
|
||||
grade_summary.columns = ['年级ID', '年级名称', '学生人数', '总金额(元)', '消费笔数']
|
||||
grade_summary = grade_summary.sort_values('年级ID')
|
||||
grade_summary.to_excel(writer, sheet_name='按年级汇总', index=False)
|
||||
|
||||
# Sheet 3: 按班级汇总
|
||||
class_summary = result_df.groupby(['班级ID', '班级名称', '年级名称']).agg({
|
||||
'学号': 'nunique',
|
||||
'支付金额(元)': 'sum',
|
||||
'姓名': 'count'
|
||||
}).reset_index()
|
||||
class_summary.columns = ['班级ID', '班级名称', '年级名称', '学生人数', '总金额(元)', '消费笔数']
|
||||
class_summary = class_summary.sort_values(['年级名称', '班级ID'])
|
||||
class_summary.to_excel(writer, sheet_name='按班级汇总', index=False)
|
||||
|
||||
# Sheet 4: 按学生汇总
|
||||
student_summary = result_df.groupby(['学号', '姓名', '年级名称', '班级名称']).agg({
|
||||
'支付金额(元)': 'sum',
|
||||
'创建时间': 'count'
|
||||
}).reset_index()
|
||||
student_summary.columns = ['学号', '姓名', '年级名称', '班级名称', '总金额(元)', '消费笔数']
|
||||
student_summary = student_summary.sort_values(['年级名称', '班级名称', '学号'])
|
||||
student_summary.to_excel(writer, sheet_name='按学生汇总', index=False)
|
||||
|
||||
print(f"\n✅ 数据整理完成!")
|
||||
print(f"📁 输出文件: {output_file}")
|
||||
print(f"\n📊 统计信息:")
|
||||
print(f" - 总记录数: {len(result_df)}")
|
||||
print(f" - 唯一学生数: {result_df['学号'].nunique()}")
|
||||
print(f" - 总金额: {result_df['支付金额(元)'].sum():.2f} 元")
|
||||
print(f"\n📋 Excel包含以下工作表:")
|
||||
print(f" 1. 详细记录 - 所有消费明细")
|
||||
print(f" 2. 按年级汇总 - 各年级消费统计")
|
||||
print(f" 3. 按班级汇总 - 各班级消费统计")
|
||||
print(f" 4. 按学生汇总 - 各学生消费统计")
|
||||
|
||||
# 显示年级分布
|
||||
print(f"\n🏫 年级分布:")
|
||||
grade_dist = result_df.groupby('年级名称')['学号'].nunique().sort_values(ascending=False)
|
||||
for grade, count in grade_dist.items():
|
||||
print(f" {grade}: {count} 人")
|
||||
Reference in New Issue
Block a user