在企业日常管理中,考勤统计是HR和行政人员的核心工作之一——核对员工打卡记录、计算工作时长、统计加班情况、生成分析报告,这些流程往往繁琐且耗时,手动处理不仅效率低下,还容易出现漏算、错算的问题。尤其是面对多人、多周期的考勤数据时,重复的计算工作会占用大量精力。

基于此,本文将分享一款实战性极强的Python考勤数据自动分析程序,可实现考勤数据读取、工作时长计算、加班识别、异常考勤(迟到/早退)判断,以及可视化分析报告生成,全程自动化执行,无需手动干预。无论是HR、行政人员,还是需要处理考勤数据的开发者,都能直接复用代码,大幅提升工作效率。

本文将从环境准备、核心功能实现、代码解析、使用教程、可视化报告生成五个方面,详细讲解整个项目的开发过程,附完整可运行源码,新手也能轻松上手。

一、项目背景与核心需求

日常考勤统计中,我们通常会面临以下痛点:

  • 手动核对Excel考勤数据,逐行计算工作时长、判断是否加班,耗时费力;
  • 难以快速统计员工月度/周期内的出勤、加班、迟到、早退等关键指标;
  • 需要手动整理数据、绘制图表,生成可视化分析报告,步骤繁琐;
  • 数据量大时,容易出现计算错误,后续核对成本高。

针对以上痛点,我们开发的Python程序实现了以下核心功能:

  • 支持读取Excel格式的考勤数据(适配常见考勤导出格式);
  • 自动解析上班、下班时间,计算每日工作时长和加班时长;
  • 识别迟到、早退异常,统计异常次数和时长;
  • 自动汇总员工周期内的核心考勤指标(出勤率、加班天数、总加班时长等);
  • 生成多维度可视化HTML报告(包含表格、折线图、柱状图、饼图);
  • 支持批量处理多员工数据,可灵活替换数据源,重复使用。

二、环境准备

本项目基于Python开发,依赖以下第三方库,核心用于数据处理、时间计算和可视化,安装步骤简单,新手可直接复制命令执行。

2.1 依赖库清单

  • pandas:用于读取Excel考勤数据、数据清洗和统计分析,是数据处理的核心库;
  • datetime:Python内置库,用于时间解析和时长计算,无需额外安装;
  • openpyxl:用于读取和写入Excel文件,支持.xlsx格式,适配大多数考勤导出文件;
  • matplotlib/seaborn:用于绘制可视化图表(可选,本项目最终生成HTML交互式图表)。

2.2 环境安装命令

打开终端(Windows CMD/Mac终端),输入以下命令,一键安装所有依赖:

python
pip install pandas openpyxl matplotlib seaborn

安装完成后,即可开始编写代码,全程复制粘贴即可运行,无需复杂配置。

三、核心功能代码实现(附详细注释)

本项目代码分为3个核心模块:数据读取与预处理、考勤指标计算、可视化报告生成。以下是完整源码,每一行都添加了详细注释,新手可轻松理解每一步的作用,也可直接复制使用,只需修改数据源路径即可。

3.1 完整源码(可直接运行)

python
import pandas as pd
from datetime import datetime
import os

# -------------------------- 1. 配置参数(可根据实际需求修改)--------------------------
# 考勤规则配置(适配大多数企业考勤制度,可灵活调整)
WORK_START = "09:00:00"  # 标准上班时间
WORK_END = "17:00:00"    # 标准下班时间
OVERTIME_THRESHOLD = 8.0  # 标准工作时长(小时),超过部分算加班
LATE_THRESHOLD = "09:05:00"  # 迟到阈值,超过该时间算迟到
EARLY_LEAVE_THRESHOLD = "16:55:00"  # 早退阈值,早于该时间算早退

# 数据源配置(替换为你的Excel文件路径)
EXCEL_PATH = "员工考勤数据.xlsx"  # 考勤数据文件
SHEET_NAME = "考勤记录"  # Excel工作表名称

# 输出配置
OUTPUT_HTML = "员工考勤加班分析报告.html"  # 可视化报告输出路径

# -------------------------- 2. 数据读取与预处理 --------------------------
def load_and_preprocess_data(file_path, sheet_name):
    """
    读取Excel考勤数据,并进行预处理(处理时间格式、删除无效数据)
    :param file_path: Excel文件路径
    :param sheet_name: 工作表名称
    :return: 预处理后的DataFrame
    """
    # 读取Excel文件
    df = pd.read_excel(file_path, sheet_name=sheet_name, engine="openpyxl")
    
    # 查看数据结构,确保列名正确(需与你的Excel列名对应,可根据实际修改)
    required_columns = ["姓名", "人员编号", "日期", "上班时间", "下班时间"]
    if not all(col in df.columns for col in required_columns):
        raise ValueError(f"Excel文件缺少必要列,需包含:{required_columns}")
    
    # 转换时间格式(将字符串时间转为datetime格式,便于计算)
    df["上班时间"] = pd.to_datetime(df["上班时间"], format="%H:%M:%S", errors="coerce")
    df["下班时间"] = pd.to_datetime(df["下班时间"], format="%H:%M:%S", errors="coerce")
    
    # 删除无效数据(时间为空、日期异常的记录)
    df = df.dropna(subset=["上班时间", "下班时间", "日期"])
    df = df[df["日期"].notna()]
    
    # 提取日期中的年、月、日,便于后续统计
    df["日期"] = pd.to_datetime(df["日期"])
    df["年份"] = df["日期"].dt.year
    df["月份"] = df["日期"].dt.month
    
    print(f"数据预处理完成,有效考勤记录共 {len(df)} 条")
    return df

# -------------------------- 3. 考勤指标计算(核心模块)--------------------------
def calculate_attendance_indicators(df):
    """
    计算核心考勤指标:工作时长、加班时长、是否迟到/早退、出勤率等
    :param df: 预处理后的考勤数据
    :return: 包含所有指标的DataFrame、统计摘要
    """
    # 转换标准时间为datetime格式,用于对比
    work_start = datetime.strptime(WORK_START, "%H:%M:%S").time()
    work_end = datetime.strptime(WORK_END, "%H:%M:%S").time()
    late_threshold = datetime.strptime(LATE_THRESHOLD, "%H:%M:%S").time()
    early_leave_threshold = datetime.strptime(EARLY_LEAVE_THRESHOLD, "%H:%M:%S").time()
    
    # 计算每日工作时长(小时):下班时间 - 上班时间,保留2位小数
    df["工作时长(小时)"] = (df["下班时间"] - df["上班时间"]).dt.total_seconds() / 3600
    df["工作时长(小时)"] = df["工作时长(小时)"].round(2)
    
    # 计算加班时长(小时):超过标准工作时长的部分,不足0则按0计算
    df["加班时长(小时)"] = df["工作时长(小时)"].apply(lambda x: max(x - OVERTIME_THRESHOLD, 0))
    df["加班时长(小时)"] = df["加班时长(小时)"].round(2)
    
    # 判断是否迟到:上班时间超过迟到阈值
    df["是否迟到"] = df["上班时间"].dt.time.apply(lambda x: "是" if x > late_threshold else "否")
    # 计算迟到分钟数
    df["迟到分钟数"] = df.apply(
        lambda row: round((row["上班时间"].dt.time() - late_threshold).total_seconds() / 60)
        if row["是否迟到"] == "是" else 0, axis=1
    )
    
    # 判断是否早退:下班时间早于早退阈值
    df["是否早退"] = df["下班时间"].dt.time.apply(lambda x: "是" if x < early_leave_threshold else "否")
    # 计算早退分钟数
    df["早退分钟数"] = df.apply(
        lambda row: round((early_leave_threshold - row["下班时间"].dt.time()).total_seconds() / 60)
        if row["是否早退"] == "是" else 0, axis=1
    )
    
    # 判断是否加班:加班时长大于0
    df["是否加班"] = df["加班时长(小时)"].apply(lambda x: "是" if x > 0 else "否")
    
    # 统计摘要(按员工分组,汇总核心指标)
    summary = df.groupby(["姓名", "人员编号"]).agg({
        "日期": "count",  # 统计天数
        "是否迟到": lambda x: (x == "是").sum(),  # 迟到天数
        "是否早退": lambda x: (x == "是").sum(),  # 早退天数
        "是否加班": lambda x: (x == "是").sum(),  # 加班天数
        "加班时长(小时)": "sum",  # 总加班时长
        "工作时长(小时)": "mean",  # 平均工作时长
        "上班时间": "min",  # 最早上班时间
        "下班时间": "max"   # 最晚下班时间
    }).reset_index()
    
    # 重命名列名,使其更直观
    summary.columns = [
        "员工姓名", "员工编号", "统计天数", "迟到天数", "早退天数",
        "加班天数", "总加班时长(小时)", "平均工作时长(小时)", "最早上班时间", "最晚下班时间"
    ]
    
    # 计算出勤率(假设统计天数均为工作日,可根据实际调整)
    summary["出勤天数"] = summary["统计天数"] - (summary["迟到天数"] + summary["早退天数"])
    summary["出勤率"] = (summary["出勤天数"] / summary["统计天数"] * 100).round(1)
    # 计算加班天数占比
    summary["加班天数占比"] = (summary["加班天数"] / summary["统计天数"] * 100).round(1)
    # 计算平均每天加班时长
    summary["平均每天加班时长(小时)"] = (summary["总加班时长(小时)"] / summary["加班天数"]).round(2)
    # 处理除数为0的情况(无加班时,平均加班时长设为0)
    summary["平均每天加班时长(小时)"] = summary["平均每天加班时长(小时)"].fillna(0)
    
    # 格式化时间字段,便于展示
    summary["最早上班时间"] = summary["最早上班时间"].dt.strftime("%H:%M:%S")
    summary["最晚下班时间"] = summary["最晚下班时间"].dt.strftime("%H:%M:%S")
    
    print("考勤指标计算完成,生成统计摘要")
    return df, summary

# -------------------------- 4. 生成可视化HTML报告 --------------------------
def generate_html_report(df, summary, output_path):
    """
    生成交互式可视化HTML报告,包含数据表格、多维度图表
    :param df: 包含所有指标的考勤数据
    :param summary: 考勤统计摘要
    :param output_path: HTML报告输出路径
    """
    # 取第一个员工的信息(可扩展为多员工切换,此处以单员工为例)
    employee_info = summary.iloc[0]
    employee_name = employee_info["员工姓名"]
    employee_id = employee_info["员工编号"]
    
    # 提取样例数据(前10条,用于报告展示)
    sample_data = df.head(10).to_dict("records")
    
    # 提取图表所需数据
    # 加班时长趋势数据(按日期排序)
    overtime_trend = df.sort_values("日期")[["日期", "加班时长(小时)"]]
    overtime_chart_data = {
        "labels": [d.strftime("%Y-%m-%d") for d in overtime_trend["日期"]],
        "data": overtime_trend["加班时长(小时)"].tolist()
    }
    
    # 工作时长趋势数据
    work_hours_trend = df.sort_values("日期")[["日期", "工作时长(小时)"]]
    work_hours_chart_data = {
        "labels": [d.strftime("%Y-%m-%d") for d in work_hours_trend["日期"]],
        "data": work_hours_trend["工作时长(小时)"].tolist()
    }
    
    # 考勤状态分布数据
    status_data = {
        "labels": ["正常出勤", "迟到", "早退", "加班"],
        "data": [
            employee_info["出勤天数"],
            employee_info["迟到天数"],
            employee_info["早退天数"],
            employee_info["加班天数"]
        ]
    }
    
    # HTML模板(集成Tailwind CSS和Chart.js,实现响应式可视化)
    html_template = f"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{employee_name}考勤加班分析报告</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
    <script>
        // 样例数据
        const sampleData = {sample_data};
        // 统计摘要
        const summary = {employee_info.to_dict()};
        // 加班时长图表数据
        const overtimeChartData = {overtime_chart_data};
        // 工作时长图表数据
        const workHoursChartData = {work_hours_chart_data};
        // 考勤状态分布数据
        const statusChartData = {status_data};
    </script>
    <style>
        .fade-in {{animation: fadeIn 0.8s ease-in;}}
        @keyframes fadeIn {{from {{opacity: 0; transform: translateY(20px);}} to {{opacity: 1; transform: translateY(0);}}}}
        .card-hover {{transition: all 0.3s ease;}}
        .card-hover:hover {{transform: translateY(-2px); box-shadow: 0 8px 25px rgba(0,0,0,0.15);}}
    </style>
</head>
<body class="bg-gray-50 min-h-screen">
    <header class="bg-gradient-to-r from-blue-600 to-purple-600 text-white py-8">
        <div class="container mx-auto px-6 text-center">
            <h1 class="text-4xl font-bold mb-2"><i class="fas fa-clock mr-3"></i>员工考勤加班分析报告</h1>
            <p class="text-blue-100 text-lg">基于{df["日期"].min().strftime("%Y-%m-%d")}至{df["日期"].max().strftime("%Y-%m-%d")}考勤数据</p>
            <div class="mt-4 flex justify-center items-center space-x-8 text-sm">
                <span><i class="fas fa-user mr-2"></i>员工: {employee_name}</span>
                <span><i class="fas fa-id-card mr-2"></i>编号: {employee_id}</span>
            </div>
        </div>
    </header>

    <section class="py-12">
        <div class="container mx-auto px-6">
            <h2 class="text-3xl font-bold text-gray-800 mb-8 text-center"><i class="fas fa-chart-line mr-3 text-blue-600"></i>关键指标概览</h2>
            <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
                <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-green-500 card-hover fade-in">
                    <div class="flex items-center justify-between">
                        <div><p class="text-gray-600 text-sm font-medium">出勤率</p><p class="text-3xl font-bold text-green-600">{employee_info["出勤率"]}%</p></div>
                        <div class="bg-green-100 p-3 rounded-full"><i class="fas fa-check-circle text-green-600 text-xl"></i></div>
                    </div>
                </div>
                <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-blue-500 card-hover fade-in">
                    <div class="flex items-center justify-between">
                        <div><p class="text-gray-600 text-sm font-medium">加班天数</p><p class="text-3xl font-bold text-blue-600">{employee_info["加班天数"]}天</p></div>
                        <div class="bg-blue-100 p-3 rounded-full"><i class="fas fa-clock text-blue-600 text-xl"></i></div>
                    </div>
                </div>
                <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-purple-500 card-hover fade-in">
                    <div class="flex items-center justify-between">
                        <div><p class="text-gray-600 text-sm font-medium">总加班时长</p><p class="text-3xl font-bold text-purple-600">{employee_info["总加班时长(小时)"]}小时</p></div>
                        <div class="bg-purple-100 p-3 rounded-full"><i class="fas fa-hourglass-half text-purple-600 text-xl"></i></div>
                    </div>
                </div>
                <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-orange-500 card-hover fade-in">
                    <div class="flex items-center justify-between">
                        <div><p class="text-gray-600 text-sm font-medium">平均工作时长</p><p class="text-3xl font-bold text-orange-600">{employee_info["平均工作时长(小时)"]}小时</p></div>
                        <div class="bg-orange-100 p-3 rounded-full"><i class="fas fa-briefcase text-orange-600 text-xl"></i></div>
                    </div>
                </div>
            </div>
        </div>
    </section>

    <section class="py-12 bg-white">
        <div class="container mx-auto px-6">
            <h2 class="text-3xl font-bold text-gray-800 mb-8 text-center"><i class="fas fa-table mr-3 text-blue-600"></i>样例数据表格</h2>
            <div class="bg-white rounded-xl shadow-lg overflow-hidden">
                <div class="overflow-x-auto">
                    <table class="w-full">
                        <thead class="bg-gray-50">
                            <tr>
                                <th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">日期</th>
                                <th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">上班时间</th>
                                <th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">下班时间</th>
                                <th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">工作时长</th>
                                <th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">加班时长</th>
                                <th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">考勤状态</th>
                            </tr>
                        </thead>
                        <tbody id="sampleDataTable" class="bg-white divide-y divide-gray-200"></tbody>
                    </table>
                </div>
            </div>
        </div>
    </section>

    <section class="py-12">
        <div class="container mx-auto px-6">
            <h2 class="text-3xl font-bold text-gray-800 mb-8 text-center"><i class="fas fa-chart-bar mr-3 text-blue-600"></i>数据可视化分析</h2>
            <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
                <div class="bg-white rounded-xl shadow-lg p-6">
                    <h3 class="text-xl font-bold text-gray-800 mb-4">每日加班时长</h3>
                    <div class="h-80"><canvas id="overtimeChart"></canvas></div>
                </div>
                <div class="bg-white rounded-xl shadow-lg p-6">
                    <h3 class="text-xl font-bold text-gray-800 mb-4">每日工作时长趋势</h3>
                    <div class="h-80"><canvas id="workHoursChart"></canvas></div>
                </div>
            </div>
            <div class="bg-white rounded-xl shadow-lg p-6">
                <h3 class="text-xl font-bold text-gray-800 mb-4">考勤状态分布</h3>
                <div class="flex justify-center"><div class="h-96 w-96"><canvas id="statusChart"></canvas></div></div>
            </div>
        </div>
    </section>

    <section class="py-12 bg-gradient-to-r from-gray-800 to-gray-900 text-white">
        <div class="container mx-auto px-6">
            <h2 class="text-3xl font-bold mb-8 text-center"><i class="fas fa-lightbulb mr-3 text-yellow-400"></i>数据分析总结</h2>
            <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
                <div class="bg-white bg-opacity-10 rounded-xl p-6 backdrop-blur-sm">
                    <h3 class="text-xl font-bold mb-4 text-yellow-400">优势表现</h3>
                    <ul class="space-y-3 text-gray-200">
                        <li class="flex items-start"><i class="fas fa-check-circle text-green-400 mt-1 mr-3"></i><span>出勤率较高:{employee_info["统计天数"]}个工作日中出勤{employee_info["出勤天数"]}天,出勤率达{employee_info["出勤率"]}%</span></li>
                        <li class="flex items-start"><i class="fas fa-check-circle text-green-400 mt-1 mr-3"></i><span>工作投入度高:{employee_info["加班天数占比"]}%的工作日存在加班,表现出较强的责任心</span></li>
                        <li class="flex items-start"><i class="fas fa-check-circle text-green-400 mt-1 mr-3"></i><span>加班时长稳定:平均每天加班{employee_info["平均每天加班时长(小时)"]}小时,加班强度适中</span></li>
                    </ul>
                </div>
                <div class="bg-white bg-opacity-10 rounded-xl p-6 backdrop-blur-sm">
                    <h3 class="text-xl font-bold mb-4 text-yellow-400">改进建议</h3>
                    <ul class="space-y-3 text-gray-200">
                        <li class="flex items-start"><i class="fas fa-arrow-right text-blue-400 mt-1 mr-3"></i><span>关注工作效率:建议分析加班原因,优化工作流程提高效率,减少无效加班</span></li>
                        <li class="flex items-start"><i class="fas fa-arrow-right text-blue-400 mt-1 mr-3"></i><span>注意劳逸结合:{employee_info["加班天数占比"]}%的加班率较高,建议合理安排工作时间</span></li>
                        <li class="flex items-start"><i class="fas fa-arrow-right text-blue-400 mt-1 mr-3"></i><span>完善考勤管理:对迟到、早退情况进行跟踪,建立异常预警机制</span></li>
                    </ul>
                </div>
            </div>
        </section>

    <footer class="bg-gray-800 text-white py-6">
        <div class="container mx-auto px-6 text-center">
            <p class="text-gray-400"><i class="fas fa-robot mr-2"></i>考勤分析报告 | 生成时间: <span id="currentTime"></span></p>
        </div>
    </footer>

    <script>
        document.addEventListener('DOMContentLoaded', function() {{
            // 填充样例数据表格
            const tableBody = document.getElementById('sampleDataTable');
            sampleData.forEach(record => {{
                const row = document.createElement('tr');
                row.className = 'hover:bg-gray-50 transition-colors duration-200';
                let status = '正常';
                let statusClass = 'bg-green-100 text-green-800';
                if (record.是否迟到 === '是') {{status = '迟到'; statusClass = 'bg-red-100 text-red-800';}}
                else if (record.是否早退 === '是') {{status = '早退'; statusClass = 'bg-yellow-100 text-yellow-800';}}
                else if (record.加班时长_小时_ > 0) {{status = '加班'; statusClass = 'bg-blue-100 text-blue-800';}}
                row.innerHTML = `
                    <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${{record.日期.slice(0,10)}}</td>
                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${{record.上班时间.slice(11,19)}}</td>
                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${{record.下班时间.slice(11,19)}}</td>
                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${{record.工作时长_小时_}}小时</td>
                    <td class="px-6 py-4 whitespace-nowrap text-sm ${{record.加班时长_小时_ > 0 ? 'text-blue-600 font-medium' : 'text-gray-500'}}">${{record.加班时长_小时_}}小时</td>
                    <td class="px-6 py-4 whitespace-nowrap"><span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${{statusClass}}">${{status}}</span></td>
                `;
                tableBody.appendChild(row);
            }});

            // 创建加班时长图表
            new Chart(document.getElementById('overtimeChart'), {{
                type: 'bar', data: {{labels: overtimeChartData.labels, datasets: [{label: '加班时长(小时)', data: overtimeChartData.data, backgroundColor: 'rgba(59, 130, 246, 0.8)', borderColor: 'rgba(59, 130, 246, 1)', borderRadius: 4}]}},
                options: {{responsive: true, maintainAspectRatio: false, scales: {{y: {beginAtZero: true, title: {display: true, text: '时长(小时)'}}, x: {title: {display: true, text: '日期'}}}}
            }});

            // 创建工作时长图表
            new Chart(document.getElementById('workHoursChart'), {{
                type: 'line', data: {{labels: workHoursChartData.labels, datasets: [{label: '工作时长(小时)', data: workHoursChartData.data, borderColor: 'rgba(16, 185, 129, 1)', backgroundColor: 'rgba(16, 185, 129, 0.1)', borderWidth: 2, fill: true, tension: 0.4}]}},
                options: {{responsive: true, maintainAspectRatio: false, scales: {{y: {beginAtZero: true, title: {display: true, text: '时长(小时)'}}, x: {title: {display: true, text: '日期'}}}}
            }});

            // 创建考勤状态饼图
            new Chart(document.getElementById('statusChart'), {{
                type: 'doughnut', data: {{labels: statusChartData.labels, datasets: [{data: statusChartData.data, backgroundColor: ['rgba(16, 185, 129, 0.8)', 'rgba(239, 68, 68, 0.8)', 'rgba(245, 158, 11, 0.8)', 'rgba(59, 130, 246, 0.8)'], borderWidth: 2}]}},
                options: {{responsive: true, maintainAspectRatio: false, plugins: {{legend: {position: 'bottom'}}}}
            }});

            // 更新当前时间
            document.getElementById('currentTime').textContent = new Date().toLocaleString('zh-CN');
        }});
    </script>
</body>
</html>
    """
    
    # 写入HTML文件
    with open(output_path, "w", encoding="utf-8") as f:
        f.write(html_template)
    
    print(f"可视化HTML报告已生成,路径:{os.path.abspath(output_path)}")

# -------------------------- 5. 主函数(程序入口)--------------------------
def main():
    """主函数,串联所有模块,执行完整流程"""
    try:
        # 1. 读取并预处理数据
        df = load_and_preprocess_data(EXCEL_PATH, SHEET_NAME)
        
        # 2. 计算考勤指标
        df, summary = calculate_attendance_indicators(df)
        
        # 3. 生成可视化报告
        generate_html_report(df, summary, OUTPUT_HTML)
        
        print("="*50)
        print("考勤数据自动分析完成!")
        print(f"核心统计:共处理 {len(df)} 条考勤记录,生成 {len(summary)} 名员工的分析报告")
        print(f"报告路径:{os.path.abspath(OUTPUT_HTML)}")
        print("="*50)
        
    except Exception as e:
        print(f"程序执行出错:{str(e)}")
        print("请检查以下内容:1. Excel文件路径是否正确;2. 列名是否与代码中一致;3. 时间格式是否正确")

# 执行主函数
if __name__ == "__main__":
    main()

3.2 代码核心模块解析

为了方便大家理解和修改,这里重点解析3个核心模块的作用,新手可根据自身需求灵活调整:

(1)数据读取与预处理模块

该模块的核心作用是读取Excel考勤数据,处理时间格式(将字符串时间转为可计算的datetime格式),删除无效数据(如时间为空、日期异常的记录)。关键注意点:

  • 需确保Excel文件的列名与代码中“required_columns”一致(姓名、人员编号、日期、上班时间、下班时间);
  • 时间格式需为“HH:MM:SS”,若你的考勤数据格式不同,可修改pd.to_datetime中的format参数;
  • 自动提取日期中的年、月信息,便于后续按周期统计。

(2)考勤指标计算模块(核心)

该模块是整个程序的核心,实现了所有考勤指标的自动计算,包括:

  • 工作时长:下班时间 - 上班时间,转为小时并保留2位小数;
  • 加班时长:超过标准工作时长(可配置)的部分,不足0则按0计算;
  • 迟到/早退判断:根据配置的阈值,自动识别迟到、早退,并计算具体时长;
  • 统计摘要:按员工分组,汇总周期内的核心指标(出勤率、加班天数、总加班时长等)。

其中,考勤规则(上班时间、下班时间、迟到/早退阈值)可在代码开头的“配置参数”部分灵活修改,适配不同企业的考勤制度。

(3)可视化报告生成模块

该模块生成交互式HTML报告,集成了Tailwind CSS和Chart.js,无需额外安装插件,打开即可查看。报告包含:

  • 关键指标概览:出勤率、加班天数、总加班时长等核心数据,直观展示;
  • 样例数据表格:展示前10条考勤记录,包含详细的时间和指标信息;
  • 多维度图表:每日加班时长柱状图、工作时长趋势折线图、考勤状态分布饼图;
  • 数据分析总结:自动生成优势表现和改进建议,辅助管理决策。

四、使用教程(新手必看)

按照以下步骤操作,无需修改核心代码,即可快速实现考勤数据自动分析:

4.1 步骤1:准备考勤数据

1. 整理你的考勤数据,保存为Excel文件(.xlsx格式);

2. 确保Excel文件包含以下列(列名需完全一致):姓名、人员编号、日期、上班时间、下班时间;

3. 日期格式建议为“YYYY-MM-DD”,上班/下班时间格式为“HH:MM:SS”(如:2026-04-01、08:56:14)。

4.2 步骤2:修改配置参数

打开Python代码,修改开头“配置参数”部分的3个关键信息:

python
# 1. 考勤规则(根据你的企业制度修改)
WORK_START = "09:00:00"  # 你的标准上班时间
WORK_END = "17:00:00"    # 你的标准下班时间
OVERTIME_THRESHOLD = 8.0  # 标准工作时长(小时)

# 2. 数据源路径(替换为你的Excel文件路径)
EXCEL_PATH = "员工考勤数据.xlsx"  # 例如:"D:\\考勤数据\\4月考勤.xlsx"
SHEET_NAME = "考勤记录"  # 你的Excel工作表名称

# 3. 报告输出路径(可默认,也可自定义)
OUTPUT_HTML = "员工考勤加班分析报告.html"

4.3 步骤3:运行代码

1. 将Excel文件和Python代码放在同一个文件夹中(或填写完整的Excel文件路径);

2. 打开终端,进入代码所在文件夹,输入命令:python 文件名.py(如:python attendance_analysis.py);

3. 等待程序执行,执行完成后,会在文件夹中生成HTML报告文件。

4.4 步骤4:查看报告

双击生成的HTML文件,用浏览器打开即可查看完整的可视化分析报告,支持响应式布局,手机、电脑均可正常查看。

五、常见问题解决

新手在运行代码时,可能会遇到以下问题,这里整理了常见问题及解决方案:

问题1:Excel文件读取失败

解决方案:

  • 检查Excel文件路径是否正确,避免中文路径或特殊字符;
  • 确保Excel文件格式为.xlsx(.xls格式需修改engine为“xlrd”,并安装xlrd库);
  • 检查工作表名称是否与代码中SHEET_NAME一致。

问题2:时间格式错误

解决方案:

  • 确保上班/下班时间格式为“HH:MM:SS”,若格式不同,修改pd.to_datetime中的format参数(如:format="%H:%M");
  • 删除Excel中时间为空或格式异常的记录。

问题3:缺少依赖库

解决方案:重新执行环境安装命令,确保所有依赖库安装成功,若安装失败,可尝试更换镜像源(如:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 库名)。

六、项目扩展与优化方向

本项目已实现核心功能,可根据实际需求进行扩展,推荐以下优化方向:

  • 多员工批量处理:支持同时分析多个员工的考勤数据,在报告中实现员工切换功能;
  • 请假/旷工识别:添加请假、旷工数据列,完善考勤状态判断;
  • 数据导出:新增Excel统计报表导出功能,便于HR存档;
  • GUI界面:使用PyQt5开发简单的图形界面,无需编写代码,点击按钮即可完成分析;
  • 定时任务:设置定时任务,自动读取考勤数据并生成报告,实现完全自动化。

七、总结

本文分享的Python考勤数据自动分析程序,完美解决了手动考勤统计的痛点,实现了从数据读取、指标计算到可视化报告生成的全流程自动化。无论是HR、行政人员,还是开发者,都能直接复用代码,大幅提升工作效率,减少人工错误。

该程序的核心优势的是:代码简洁易懂、配置灵活、无需复杂环境,新手也能快速上手;生成的HTML报告交互性强,可直观展示考勤数据的核心信息,辅助管理决策。

如果你的工作中需要处理考勤数据,不妨试试这款程序,只需简单配置,就能告别繁琐的手动统计,把更多精力放在更有价值的工作上。后续会持续优化项目,添加更多实用功能,欢迎大家留言交流、提出改进建议!

最后,附上完整源码(可直接复制运行),祝大家高效办公、少加班!

源码获取

本文所有代码均已完整展示,可直接复制到本地运行,无需额外获取。若需获取示例Excel考勤数据,可在评论区留言,我会及时回复。

�� 本文原创首发于CSDN,转载请注明出处,感谢支持!

�� 若有疑问或改进建议,欢迎在评论区留言,一起交流学习~

|(注:文档部分内容可能由 AI 生成)

更多推荐