考勤统计是每个企业HR月底的固定工作,但人工核对工作日总是容易出错。特别是遇到法定节假日调休、公司特殊假期等情况,传统的手动计算方式不仅效率低下,还经常出现遗漏。我曾经就遇到过因为漏算了一个调休工作日,导致全公司考勤数据需要重新核对的尴尬情况。
Python作为数据处理利器,完全可以帮我们解决这个问题。通过编写一个自动化脚本,我们能够:
这套方案特别适合200人以下的中小企业,不需要购买昂贵的考勤系统,用Python+飞书就能搭建轻量级解决方案。下面我就详细拆解实现过程。
要实现精准计算,首先需要可靠的节假日数据源。我对比过三种方案:
python复制holidays = {
"2023": {
"元旦": ["2023-01-01"],
"春节": ["2023-01-22", "2023-01-23", "2023-01-24"],
# 其他节假日...
},
"workdays": {
"2023": ["2023-01-28", "2023-01-29"] # 调休工作日
}
}
python复制import requests
from bs4 import BeautifulSoup
url = "http://www.gov.cn/zhengce/content/2022-12/08/content_5730764.htm"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# 解析节假日安排...
python复制import pandas as pd
# 使用tushare pro获取节假日数据
import tushare as ts
pro = ts.pro_api('your_token')
df = pro.trade_cal(exchange='', start_date='20230101', end_date='20231231')
提示:推荐使用方案3的tushare pro,虽然需要注册获取token,但数据最权威且维护及时。免费版完全够用。
核心算法需要考虑:
python复制def calculate_workdays(year, month):
# 获取当月第一天和最后一天
first_day = datetime.date(year, month, 1)
if month == 12:
last_day = datetime.date(year+1, 1, 1) - datetime.timedelta(days=1)
else:
last_day = datetime.date(year, month+1, 1) - datetime.timedelta(days=1)
# 生成当月所有日期
all_days = [first_day + datetime.timedelta(days=i)
for i in range((last_day - first_day).days + 1)]
# 过滤工作日
workdays = []
for day in all_days:
if day.weekday() < 5: # 周一到周五
if str(day) not in holidays.get(str(year), []): # 不在节假日列表
workdays.append(day)
elif str(day) in workdays_special.get(str(year), []): # 特殊调休工作日
workdays.append(day)
return workdays
建议采用以下更新策略:
python复制def update_holiday_data():
current_year = datetime.datetime.now().year
for year in [current_year, current_year+1, current_year+2]:
if not os.path.exists(f'holidays_{year}.json'):
# 调用API或爬取数据
save_holiday_data(year)
https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx)python复制import hashlib
import base64
import hmac
def gen_sign(secret, timestamp):
string_to_sign = f'{timestamp}\n{secret}'
hmac_code = hmac.new(string_to_sign.encode('utf-8'),
digestmod=hashlib.sha256).digest()
return base64.b64encode(hmac_code).decode('utf-8')
考勤提醒消息需要包含:
python复制def build_attendance_card(workdays, abnormal_users):
card = {
"msg_type": "interactive",
"card": {
"header": {
"title": {
"tag": "plain_text",
"content": f"{datetime.datetime.now().month}月考勤提醒"
},
"template": "wathet" # 蓝色主题
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": f"**本月工作日共 {len(workdays)} 天**\n\n"
f"最后更新:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}"
}
},
# 其他元素...
]
}
}
return card
推荐两种实现方式:
bash复制# 每天17:30执行
30 17 * * * /usr/bin/python3 /path/to/attendance_reminder.py
python复制from apscheduler.schedulers.blocking import BlockingScheduler
sched = BlockingScheduler()
@sched.scheduled_job('cron', day='last', hour=17)
def monthly_reminder():
# 执行提醒逻辑
pass
sched.start()
python复制def main():
try:
# 1. 检查节假日数据
check_holiday_data()
# 2. 计算当月工作日
today = datetime.date.today()
workdays = calculate_workdays(today.year, today.month)
# 3. 获取考勤异常人员
abnormal_users = get_abnormal_users(workdays)
# 4. 发送飞书提醒
if is_last_workday(today, workdays):
send_feishu_alert(workdays, abnormal_users)
except Exception as e:
log_error(e)
send_error_alert(e)
python复制except HolidayDataNotFoundError:
# 自动触发数据更新
update_holiday_data()
retry_calculation()
python复制except FeishuRateLimitError:
# 采用指数退避重试
time.sleep(2 ** retry_count)
if retry_count < 3:
retry_send()
python复制except ValueError as e:
if "day is out of range" in str(e):
# 处理闰年二月问题
handle_special_month(year, month)
建议采用分层日志:
python复制import logging
logger = logging.getLogger('attendance')
logger.setLevel(logging.DEBUG)
# 文件日志
file_handler = logging.FileHandler('attendance.log')
file_handler.setLevel(logging.INFO)
# 控制台日志
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 日志格式
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
推荐两种部署方式:
dockerfile复制FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "attendance_reminder.py"]
bash复制# 创建虚拟环境
python -m venv venv
source venv/bin/activate
# 安装依赖
pip install -r requirements.txt
# 后台运行
nohup python attendance_reminder.py > log.txt 2>&1 &
python复制from functools import lru_cache
@lru_cache(maxsize=3) # 缓存最近3年的数据
def get_holidays(year):
# 获取节假日数据
pass
python复制import asyncio
import aiohttp
async def async_send_feishu(card):
async with aiohttp.ClientSession() as session:
async with session.post(webhook_url, json=card) as resp:
return await resp.json()
python复制# 使用pandas批量处理
import pandas as pd
def batch_check_attendance(user_ids, workdays):
df = pd.DataFrame.from_records(get_raw_data())
return df[~df['date'].isin(workdays)].groupby('user_id').size()
python复制# 在环境变量中存储密钥
import os
SECRET = os.getenv('FEISHU_SECRET')
def verify_signature(timestamp, sign):
return gen_sign(SECRET, timestamp) == sign
python复制from cryptography.fernet import Fernet
key = Fernet.generate_key()
cipher_suite = Fernet(key)
# 加密
encrypted = cipher_suite.encrypt(b"Sensitive data")
# 解密
decrypted = cipher_suite.decrypt(encrypted)
python复制audit_log = {
"timestamp": datetime.datetime.now().isoformat(),
"operator": current_user,
"action": "send_reminder",
"detail": {
"workday_count": len(workdays),
"abnormal_users": len(abnormal_users)
}
}
这套方案经过适当改造,还可以应用于:
python复制# 计算项目周期内的工作日
def get_project_workdays(start_date, end_date):
# 类似实现...
python复制# 考虑季度末、半年末的特殊日期
def get_settlement_days(year):
# 特殊逻辑...
python复制# 自动生成值班表
def generate_duty_schedule(employees, workdays):
# 排班算法...
python复制# 计算合同有效工作日
def calculate_contract_days(start, end, holidays):
# 法律特殊要求...
在实际使用中,我发现这套系统最容易被忽视但最关键的点是节假日数据的及时更新。曾经因为忘记更新春节调休安排,导致系统漏算了2个工作日。现在我设置了双重提醒:每年12月自动邮件提醒更新下一年数据,同时在每次计算前强制检查数据完整性。