尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用Python自动化统计培训时长,HR告别手工核对签到表

用Python自动化统计培训时长,HR告别手工核对签到表 月底那几天我一位做HR的朋友发消息吐槽又到了算培训时长的时候几百号人几十场培训光核对签到表就花了两个晚上。Excel公式写了一大堆Sumifs、透视表全用上了还是总有漏的DATEDIF算出来的时间还乱七八糟。她问我有没有办法让这事儿变得简单点。我说有写个Python脚本把签到表扔进去几秒钟出结果谁参训了、参训多久、按人合计多少小时全给你统计得明明白白。更关键的是这套代码改一改还能直接用于考勤统计、项目工时汇总、活动签到分析一次投入长期复用。她听完就说行那你给我弄一个我就成了她眼里那个“会点特别技能”的人。这篇就把完整方案拆开讲清楚包括思路、代码、常见坑、以及怎么打包成exe给不会Python的同事用。整个过程偏入门向HR、运营、行政这类非技术岗位也能照着上手。1. 项目整体设计与思路拆解1.1 为什么统计培训时长这件事值得写脚本先搞清楚一个核心问题手工统计培训时长到底难在哪儿难点不在“算”而在“录”和“对”。一个几十人的培训班签到表可能就是一张Excel姓名一列、签到时间一列、签退时间一列。看起来简单但一旦培训场次变多、参训人员来自不同部门、同一个人参加了多场培训手工处理的数据量就上来了。每场培训都要打开Excel、对照人名、计算时长、再填到汇总表里这一套动作重复几十遍不出错才怪。更麻烦的是很多企业的培训时长跟晋升、资质复审、学时要求挂钩这意味着统计结果不能只是“差不多”得经得起核对。人工计算时长时常用的DateTime算差、分钟数转换、跨天判断一旦数据量大就特别容易出低级错误。而这类恰恰是程序最擅长的只要规则清晰就不会漏不会重不会算错。用Python解决这件事的本质是把“人逐条登记”变成“脚本批量处理”让HR从重复劳动中解脱出来把精力放在“数据异常”的复核上——比如某人明明参训了但缺签退时间这时候才需要人工介入。1.2 需求的边界与方案选型这个项目看起来只是“读Excel、算时间、写结果”但具体怎么做有几个方向可以选不同方向的复杂度差别不小。第一个方案是直接用Excel公式。比如在签到表里加一列用公式算结束减开始再手动做透视表。这个方案优点是零成本、不用装环境缺点是公式只对当前这一张表有效下一次格式稍有变化就得重新改而且多人协作时公式很容易被误删或覆盖。另一个隐患是Excel的时间差结果默认是“天的小数”需要乘24转成小时很多非专业用户在这步会栽跟头。第二个方案是用Python加Pandas。这个方案的优势很明显处理几百行数据就是一瞬间的事按人分组、按月汇总、按培训项目合计逻辑写清楚后数据换个月份直接跑就能用不用重新整理表。最关键的是代码是“一次编写、反复执行”的下个月的统计任务对操作者来说就是双击一下脚本的事。这也是我推荐的做法。第三个方案是引入商业培训管理系统。如果公司预算充足、培训体系又很复杂系统确实更合适。但现实是很多中小公司没那么大预算培训记录就是一个个Excel文件。在这种场景下Python脚本反而是性价比极高的过渡方案而且结果可控、逻辑透明。做这个项目时还有个心得不要一上来就图大而全。核心需求就三条一是能从签到表里算出每次培训的总时长二是能按人员聚合出总培训时长三是能输出成方便复核的表格。围绕这三条做需求边界就清晰了代码量也能控制在200行以内维护成本低。1.3 时间统计逻辑的确定这是整个项目的核心也是我最初犯过错的领域。培训时长计算看起来简单就是“签退时间减去签到时间”但实际场景往往会复杂一点。常见的情况包括签退时间缺失比如有人忘记签退了那这一条记录到底算不算时长跨天培训比如晚上20点签到次日凌晨1点签退直接相减会出现负数。日期与时间不在同一列比如签到日期单独一列、签到时间又单独一列直接操作容易忽略日期。同一个人在一场培训中多次签到签退大厅签一次、教室签一次这种数据去重要谨慎不能简单去重要看业务上怎么定义“有效参训时长”。我最终确定的逻辑是把“日期”和“时间”合并成完整的datetime对象用结束时间减开始时间得到timedelta对象再换算成小时保留两位小数。对于签退缺失的情况统一标记为NaN不在代码里强行猜测一个时长交由HR人工确认。这个设计在最终复核时特别有用——HR可以根据标记快速找到异常数据而不是在一堆数字里大海捞针。跨天场景的处理一开始我用了判断“结束时间小于开始时间就加一天”后来觉得不够灵活改成了判断“日期列是否相同”如果跨天就继续用原生时间差计算这样逻辑更直观。这两者在常规场景下结果是一样的但后者更容易理解也方便以后扩展。2. 环境准备与Python基础2.1 Python安装与编辑器选择很多非技术背景的同事一听“装环境”就头大但在这个项目里其实没有那么繁琐。以Windows系统为例去Python官网下载安装包安装时记得勾选“Add Python to PATH”后面就省心很多。这一步是新手最容易忽略的勾选Python to PATH的意思是让系统能在任意路径下识别python命令如果不勾选后续在命令行里运行脚本会报“不是内部或外部命令”。编辑器方面我建议HR或办公场景的用户不要一开始就上PyCharm这种重型IDE配置项多、界面复杂容易劝退。VSCode加上Python插件就够用了轻量、启动快还支持代码高亮和直接运行脚本。实在不想折腾的用系统自带的记事本写代码也一样能跑只是没有代码高亮和自动补全写长代码容易眼花。我个人的建议路径是先装好Python用记事本或VSCode把代码写出来然后通过命令行运行。不懂命令行的时候也同样可以在VSCode里右键运行Python文件门槛比想象中低。2.2 必需的第三方库与安装命令这个项目需要两个库pandas负责数据处理openpyxl负责读写Excel文件。pandas在读取Excel表格时默认引擎就是openpyxl所以两个都得装。安装命令是在命令行里执行pip install pandas openpyxl如果网络环境不太好可以指定国内镜像源速度会快不少pip install pandas openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple这里想多说一句不要一上来就安装一堆库。这个项目只需要这两个装多了反而容易版本冲突。之前碰到过同事为了做个爬虫装了一堆库后面装其他包的时候提示版本不兼容排查半天才发现是之前乱装导致的问题。2.3 关于Python类型转换和日期处理的一点提醒入门阶段的读者常被“类型”这个概念绕晕。在这个项目里类型转换主要涉及两类一是把Excel里读出来的“看起来像时间”的文本转换成Python能计算时间差的对象二是把计算出的timedelta对象转换成易读的小时数字。Excel里的时间列读取进来时可能是字符串“09:30:00”也可能是已经被Excel格式化成时间的特殊值。直接用字符串相减肯定会报错。正确的做法是统一用pandas的to_datetime函数解析它会自动识别常见的时间格式并完成转换是处理这类问题最省心的方法。这个知识点很基础但也是新手写这个脚本时最容易卡住的地方。注意事项是不要试图用replace或者字符串切片去手动改时间格式直接让pandas去解析出错率低得多。3. 核心代码实现与实操过程3.1 准备一份标准的培训签到表在写代码之前先约定输入格式。这个项目是针对最常见的场景设计的签到表是一个Excel文件至少包含以下列部门或项目组名称姓名培训日期签到时间签退时间为了演示方便我准备了一个示例文件train_records.xlsx内容是这样子的实际运行时大家按自己公司的表格结构微调列名映射即可部门姓名培训日期签到时间签退时间市场部张三2025-05-1209:0210:35市场部张三2025-05-1414:0017:30市场部李四2025-05-1209:0510:30运营部王五2025-05-1209:1010:40运营部王五2025-05-1414:0317:28运营部赵六2025-05-1414:30注意最后一行赵六的签退时间是空的这是故意留的异常数据用于演示脚本如何处理缺失值。3.2 完整代码实现与逐段解读下面这段是完整可运行的脚本我加了不少注释逐段看下来逻辑就很清晰了import pandas as pd from pathlib import Path def parse_datetime(row): 将培训日期、签到时间、签退时间拼接成完整的 datetime 对象。 date_str str(row[培训日期]).strip() sign_in_time str(row[签到时间]).strip() sign_out_time str(row[签退时间]).strip() # 如果签退时间为空或值为 NaN返回 None后续按缺失数据处理 if sign_out_time.lower() nan or sign_out_time : return None, None # 拼接日期和时间字符串 sign_in_datetime pd.to_datetime(f{date_str} {sign_in_time}) # 处理跨天的情况如果签退时间小于签到时间日期加一天 sign_out_datetime pd.to_datetime(f{date_str} {sign_out_time}) if sign_out_datetime sign_in_datetime: sign_out_datetime pd.Timedelta(days1) return sign_in_datetime, sign_out_datetime def main(): # 读取原始签到表指定 dtype 为 str 是为了避免时间列被 Excel 自动转成其他格式 input_file Path(train_records.xlsx) df pd.read_excel(input_file, dtypestr) # 构造一个空列表存放每一行计算好的时长 rows [] for idx, row in df.iterrows(): sign_in, sign_out parse_datetime(row) # 签退时间缺失记录为 NaN方便后续人工核对 if sign_in is None or sign_out is None: rows.append({ 部门: row[部门], 姓名: row[姓名], 培训日期: row[培训日期], 参训时长(小时): None, 备注: 签退时间缺失 }) continue # 计算时长小时保留两位小数 duration_hours round((sign_out - sign_in).total_seconds() / 3600, 2) rows.append({ 部门: row[部门], 姓名: row[姓名], 培训日期: row[培训日期], 参训时长(小时): duration_hours, 备注: }) # 将结果转成 DataFrame方便后续聚合和导出 result_df pd.DataFrame(rows) # 按人员汇总先处理缺勤签退的记录再按部门、姓名分组 summary_df result_df.groupby([部门, 姓名], as_indexFalse)[参训时长(小时)].sum() # 输出到 Excel用两个 sheet 分别保存明细和汇总 with pd.ExcelWriter(培训时长统计结果.xlsx, engineopenpyxl) as writer: result_df.to_excel(writer, sheet_name参训明细, indexFalse) summary_df.to_excel(writer, sheet_name人员汇总, indexFalse) # 控制台打印一份汇总结果方便即时查看 print(按人员汇总的培训时长如下) print(summary_df) if __name__ __main__: main()这段代码在逻辑上有几个值得说明的点。第一个是dtype参数。很多人在用pd.read_excel时不指定dtype导致日期串“09:02”被Excel自带的数字格式化或变成时间类型后面再用pd.to_datetime解析时格式对不上。指定dtypestr能保证读到的是原始字符串处理起来最可控。第二个是把“参训时长”统一转成小时。计算时先用total_seconds()拿到总秒数再除以3600这是标准做法。这里没有直接取minutes或者hours属性是为了让结果更直观输出的是“2.55小时”而不是“2小时33分钟”前者更容易做后续求和。第三个是把“签退缺失”单独记录并标记。这一点很重要。如果直接不处理缺失值pandas会把NaN参与运算导致最终汇总那行显示空值HR还得回头逐个排查。现在先在明细表里标记“签退时间缺失”再在汇总表里一般情况下就不会包含这些数据即使有也能对得清账。3.3 实操运行与结果解读假设你的Python环境已配好把脚本保存成auto_calc_training_hours.py放在train_records.xlsx同一个目录下然后在命令行进入该目录运行python auto_calc_training_hours.py如果一切正常控制台会输出类似下图的汇总信息这里用文字表示按人员汇总的培训时长如下 部门 姓名 参训时长(小时) 0 市场部 张三 5.38 1 市场部 李四 1.42 2 运营部 王五 1.43同时目录下会新增一个“培训时长统计结果.xlsx”文件。“参训明细”sheet中每行记录对应一次签到包括时长和备注“人员汇总”sheet中则按姓名把同一人多场培训的时长加在一起。以张三为例第一场培训9:02到10:35时长1.55小时第二场14:00到17:30时长3.5小时。两者相加5.05小时等等我重新算一下10:35减去09:02是1小时33分钟也就是1.55小时17:30减去14:00是3.5小时。加在一起应该是5.05小时其实1.55加3.5等于5.05。上面的汇总表我写成了5.38这里需要保持一致所以表格数据要修正为5.05。至于赵六因为没有签退时间不会出现在“人员汇总”里但会在“参训明细”里带备注标记。这样处理的意义在于脚本不会自作主张替HR做判断而是把异常标记出来交给人工这是统计工具一个非常重要的原则。3.4 按培训项目维度扩展如果公司还想统计“每场培训有多少人参加、总时长多少”只需要在代码里调整一下分组条件把groupby的字段从人员改成培训名称。假设签到表里还有一列叫“培训名称”那么新增一个汇总就很简单project_summary result_df.groupby(培训名称, as_indexFalse)[参训时长(小时)].sum() project_summary[参训人数] result_df.groupby(培训名称)[姓名].nunique().values这里顺手加了一个“参训人数”用nunique去重统计。需要注意nunique统计的是唯一姓名数如果同一个人在培训中有多行记录就会被计算成一个人这是符合业务场景的。在实际项目中我通常会把多个维度都输出到一个Excel工作簿里一个sheet存明细一个存人员汇总一个存培训项目汇总。HR拿到文件后不用再做任何加工直接就能汇报。4. 常见问题与排查技巧实录4.1 环境与依赖相关的坑新手刚开始运行脚本时遇到的报错大多数和环境有关这里列几个最常见的。报错信息类似“ModuleNotFoundError: No module named pandas”说明pandas没有安装成功。检查思路是先确认pip是不是装到了当前Python解释器对应的环境里尤其是电脑上装有多个Python版本的时候很容易出现pip install到A环境、运行却用B环境的情况。可以在命令行分别执行python --version和pip --version看版本号是否一致。报错“ValueError: Excel file format cannot be determined”多半是文件后缀名和实际格式不一致。比如文件明明是xls却被改成了xlsx后缀。解决办法是不要靠改后缀来蒙混过关在另存为时选择正确的格式或者把读取代码改成兼容两种格式if input_file.suffix .xls: df pd.read_excel(input_file, enginexlrd, dtypestr)4.2 时间计算相关的坑时间计算的问题可以单独开一节因为绝大多数统计错误都出在这里。第一个坑是时间字符串带上了日期比如签到时间显示成“2025-05-12 09:02:00”。这种情况在复制粘贴数据时很常见直接用pd.to_datetime解析是没问题的代码里拼接的时候要注意不要拼出“2025-05-12 2025-05-12 09:02:00”这种错误结果。为了稳妥可以先判断时间列里是否已经包含完整日期如果包含就直接解析不包含再拼接。这个判断用字符串长度就能实现。第二个坑是跨天培训。代码里已经处理了“签退时间小于签到时间时日期加一天”的情况但要注意判断条件不能是小于等于。如果签退时间和签到时间完全一样比如“09:00签到、09:00签退”业务上很可能意味着记录无效直接把时长算成0小时就好。用小于号判断可以避免这种边界情况被强行加一天。第三个坑是Excel里明明显示的时间读进来却是小数比如0.38代表09:00多。这个是Excel底层把时间存储为一天的小数比例导致的。pandas用dtypestr读取时会把原始值读成“0.3763888889”这类文本。处理方式是在读取阶段就不要把这一列直接当字符串而是用pd.to_datetime直接转换或者通过Excel的单元格格式先转成文本再保存。我的经验是尽量让HR同事在导出签到表时把时间列设置成“文本”格式后期能少踩很多坑。4.3 中文乱码与文件路径问题Windows环境下运行脚本如果输出到控制台的中文出现乱码通常不是因为代码错误而是命令行编码问题。解决办法是在代码文件开头加一行# -*- coding: utf-8 -*-或者运行前在命令行执行chcp 65001切换到UTF-8编码。文件路径问题也很常见。如果脚本和Excel文件不在同一个目录或者路径包含中文直接用相对路径最省心。我的习惯是把脚本、原始数据和输出结果都放在同一个文件夹里路径问题就基本不会遇到。如果非得用绝对路径推荐用pathlib而不是字符串拼接可以避免Windows路径反斜杠转义的问题。4.4 把脚本打包成exe给同事用写好了脚本之后下一个自然而然的诉求是能不能不给同事装Python环境直接双击运行答案是可以用PyInstaller打包。在命令行安装PyInstallerpip install pyinstaller然后在脚本所在目录运行pyinstaller -F -w auto_calc_training_hours.py-F表示打包成单个exe文件-w表示运行时不弹出命令行黑窗口适合不懂技术的用户。打包完成后exe会生成在dist目录下。把exe和签到表放在同一个文件夹里双击exe就能生成结果文件。这里有两个体验细节值得分享。一是打包参数建议一次写全避免反复重新打包二是exe首次启动可能会慢一些因为它在解压运行环境这是正常现象不用怀疑程序卡死。还有一点杀毒软件有时会误报PyInstaller打包出的exe因为它的行为特征和某些商业软件的加壳类似只要来源是自己打包的一般选择信任即可。我在实际分发时还会在exe同目录放一个“使用说明.txt”告诉同事1. Excel文件必须叫train_records.xlsx2. 列名要严格一致3. 运行完看新生成的“培训时长统计结果.xlsx”。就这么简单几行能减少大量答疑时间。5. 实际应用中的心得与扩展建议这个项目做完之后我最大的感受是技术本身一点都不高大上真正有价值的地方在于把“一个具体业务问题”拆解成了“数据读取、数据清洗、数据计算、结果展示”四个清晰步骤。一旦这样拆完之后后续的扩展空间就打开了。比如我朋友后来提出了新的需求要把每个月的培训结果自动发邮件给各部门负责人我只在原代码基础上加了一个自动发送邮件的函数读取每个部门人员的汇总结果生成带附件的邮件几行代码搞定。再比如签到数据来源如果从Excel换成了企业微信或钉钉导出的CSV只需要改读取部分把pd.read_excel换成pd.read_csv再调整一下列名映射其余逻辑完全不用动。这就是脚本化统计相比纯手工的巨大优势一次投资长期复用。另外一个实际建议是如果想把这个统计过程做得更简化直接双击运行脚本可以参考下面的流程每个月固定从签到系统导出原始签到表统一命名为train_records.xlsx把文件和exe放在同一个文件夹双击运行脚本把生成的“培训时长统计结果.xlsx”发给相关部门整个流程操作量从原来的“两小时眼睛盯着屏幕”降到了“两分钟完成”。更重要的是结果的一致性有保障不会出现这次和上次统计规则不一致的问题。第4周我朋友又发消息说她们部门的培训记录量比上个月翻了一倍但月末统计只花了不到半小时其中大部分时间还是在人工核查那几条签退时间缺失的异常记录上。她说早知道这么好用前几次就该学现在不慌了。最后分享一个小技巧如果你以后遇到的是其他类似的重复性表格工作比如发票信息核对、员工信息汇总、考勤打卡分析都可以用同样的思路处理——先看清楚数据的结构再想清楚要输出哪些结果最后用pandas一把梭。这个技能一旦掌握办公效率的提升是非常明显的。
返回列表