某公司HR每天深夜手动复制表单数据从Excel到系统折腾到半夜发现设置自动同步后一天轻松搞定不再熬夜
凌晨两点,办公室只剩小陈一个人。
显示器泛着冷白的光,映着她疲惫的脸。Excel表格在左边,系统界面在右边。光标一次次选中,Ctrl+C,切窗口,Ctrl+V,保存,刷新。一行、两行、三行……几百行员工信息,已经重复了这个动作快两个小时。
这是她连续加班的第七天。
那个让人崩溃的夜晚
小陈是某中型互联网公司的人事专员,负责每月的员工信息更新。每个月末,业务部门会把新增、离职、调岗员工的信息汇总到一个Excel表格里发给她。
然后就是这场”数据马拉松”。
公司的HR系统是个老旧的内部系统,界面还是十几年前的风格,没有批量导入功能,只能一条一条手动录入。更折磨人的是,有些字段还需要二次核对——比如工号要对上、入职日期要转换格式、部门名称要和系统里的一致。
手动录入的流程大概是这样的:
- 打开Excel,找到新员工信息行
- 复制姓名、工号、部门、入职日期等字段
- 切换到HR系统,新建员工档案
- 逐一对应粘贴,确认无误后保存
- 回到Excel,标记已完成
- 重复1-5,直到全部录完
听起来只是”复制粘贴”那么简单?
实际情况比这复杂得多。每个月平均有80-100人需要更新,有时候遇到数据质量问题(比如日期格式不统一、部门名称有细微差异),还得花额外时间处理。有时候系统还会抽风,保存失败,前面的数据可能要重新填。
最让小陈崩溃的一次,是某个月有127人入职,她一直干到凌晨三点半。第二天还要正常上班,整个人像被掏空了一样。
改变是从一个想法开始的
转机发生在某个周五的下午。
小陈在茶水间听到两个同事聊天,说他们技术部最近搞了个自动化脚本,把报表生成从两小时缩短到了三分钟。
“真的假的?”小陈好奇地问。
“真的,就是用Python写的,自动从Excel读数据然后填到系统里。”
小陈的心动了一下。她之前也想过找人做个自动化,但每次提出来都被搁置——”业务太忙没空搞”“系统没有接口”“手动还能接受”。
但这个周五,她决定不再等了。
先搞懂系统的”脾气”
自动化的第一步,不是写代码,而是理解系统。
小陈花了整整两天时间,做了一件事:仔细研究HR系统的每一条操作。
她用浏览器自带的开发者工具(F12)打开了系统,观察每一个表单提交的过程。她发现:
- 系统确实没有公开API,但表单提交用的是标准的POST请求
- 每个字段的名称都藏在HTML里,比如
name="employee_name"、name="employee_id" - 登录状态是通过Cookie维持的
- 系统有一个反自动化机制——如果请求太快,会弹出验证码
这些信息至关重要。它们决定了自动化的可行性和方案选择。
小陈做了一个详细的记录表格:
| 字段名 | HTML name属性 | 数据来源 | 是否需要转换 |
|--------|--------------|----------|-------------|
| 姓名 | emp_name | Excel B列 | 否 |
| 工号 | emp_id | Excel A列 | 否 |
| 部门 | dept_code | Excel C列 | 是,需映射 |
| 入职日期 | hire_date | Excel D列 | 是,格式转换 |
| 手机号 | phone | Excel E列 | 否 |
这个表格后来成了整个自动化项目的”地图”。
选择了Python,写了自己的”助手”
小陈之前完全没写过代码,但她发现,这种重复性很强的自动化任务,Python是最合适的工具。
原因很简单:
- 语法简单,学习曲线平缓
- 有强大的库支持(处理Excel、发送HTTP请求、自动化浏览器)
- 社区资源丰富,遇到问题容易找到答案
她花了大约一周时间,在B站上跟着教程入门了Python基础。然后就进入了真正的实战阶段。
第一步:读取Excel数据
她首先写了一个简单的脚本,把Excel数据读进来看看。
import pandas as pd
# 读取Excel文件
excel_path = "2024年11月员工入职信息.xlsx"
df = pd.read_excel(excel_path)
# 查看数据结构
print(df.head())
print(df.columns)
print(f"共有 {len(df)} 条员工记录")
输出结果让她松了口气——数据格式基本正常,只有一两处需要清理。
第二步:处理登录和Session
因为系统没有API,她选择了用Selenium来模拟浏览器操作。这是最”笨”但也最可靠的方法——就像真的在用浏览器一样操作,系统很难区分。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
# 启动浏览器
options = webdriver.ChromeOptions()
options.add_argument('--headless') # 无头模式,不显示浏览器界面
driver = webdriver.Chrome(options=options)
# 打开HR系统登录页
driver.get("https://hr.example.com/login")
# 等待页面加载,输入账号密码
wait = WebDriverWait(driver, 10)
wait.until(EC.presence_of_element_located((By.NAME, "username")))
driver.find_element(By.NAME, "username").send_keys("chenxia")
driver.find_element(By.NAME, "password").send_keys("your_password")
driver.find_element(By.ID, "login-btn").click()
# 等待登录成功
time.sleep(3)
print("登录成功!")
这里有个技巧——把登录后的Cookie保存下来,这样下次就不需要重新登录了。
# 保存Cookie,避免每次都重新登录
cookies = driver.get_cookies()
import json
with open("cookies.json", "w", encoding="utf-8") as f:
json.dump(cookies, f, ensure_ascii=False)
# 后续使用:先加载Cookie
with open("cookies.json", "r", encoding="utf-8") as f:
cookies = json.load(f)
for cookie in cookies:
driver.add_cookie(cookie)
driver.get("https://hr.example.com")
第三步:核心逻辑——自动填充并提交
这是最关键的部分。小陈把每一条员工信息填进表单,然后提交。
import json
# 读取员工数据
with open("employees.json", "r", encoding="utf-8") as f:
employees = json.load(f)
# 打开新增员工页面
driver.get("https://hr.example.com/employee/add")
# 部门代码映射(Excel里的部门名 -> 系统里的部门编码)
dept_mapping = {
"技术部": "TECH01",
"市场部": "MKT01",
"人力资源部": "HR01",
"财务部": "FIN01",
}
success_count = 0
error_list = []
for i, emp in enumerate(employees):
try:
print(f"正在处理第 {i+1}/{len(employees)} 条: {emp['name']}")
# 刷新页面(防止状态残留)
driver.get("https://hr.example.com/employee/add")
time.sleep(2)
# 填充工号
driver.find_element(By.NAME, "emp_id").send_keys(emp["emp_id"])
# 填充姓名
driver.find_element(By.NAME, "emp_name").send_keys(emp["name"])
# 填充部门(需要映射)
dept_code = dept_mapping.get(emp["dept"], "UNKNOWN")
driver.find_element(By.NAME, "dept_code").send_keys(dept_code)
# 填充入职日期(格式转换)
from datetime import datetime
raw_date = emp["hire_date"]
if isinstance(raw_date, str):
# 处理不同格式的日期
hire_date = datetime.strptime(raw_date, "%Y/%m/%d").strftime("%Y-%m-%d")
else:
hire_date = raw_date.strftime("%Y-%m-%d")
driver.find_element(By.NAME, "hire_date").send_keys(hire_date)
# 填充手机号
driver.find_element(By.NAME, "phone").send_keys(emp["phone"])
# 点击提交
driver.find_element(By.ID, "submit-btn").click()
# 等待提交结果
time.sleep(2)
# 检查是否成功
if "成功" in driver.page_source or "添加成功" in driver.page_source:
success_count += 1
print(f"✓ {emp['name']} 添加成功")
else:
error_list.append({"name": emp["name"], "reason": "提交未成功"})
print(f"✗ {emp['name']} 添加失败,需要检查")
except Exception as e:
error_list.append({"name": emp["name"], "error": str(e)})
print(f"✗ {emp['name']} 处理异常: {e}")
# 出错后尝试恢复,继续下一条
continue
print(f"\n处理完成!成功 {success_count} 条,失败 {len(error_list)} 条")
第四步:加上进度和异常处理
真正上线前,小陈又做了一些优化:
import json
import time
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
import logging
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler("hr_auto_sync.log", encoding="utf-8"),
logging.StreamHandler()
]
)
class HRAutoSync:
def __init__(self, excel_path):
self.excel_path = excel_path
self.driver = None
self.success_count = 0
self.error_count = 0
self.error_details = []
def start_browser(self):
"""启动浏览器"""
options = Options()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
self.driver = webdriver.Chrome(options=options)
logging.info("浏览器启动成功")
def login(self):
"""登录系统"""
self.driver.get("https://hr.example.com/login")
self.driver.find_element(By.NAME, "username").send_keys("chenxia")
self.driver.find_element(By.NAME, "password").send_keys("your_password")
self.driver.find_element(By.ID, "login-btn").click()
time.sleep(3)
# 检查登录是否成功
if "退出" in self.driver.page_source:
logging.info("登录成功")
return True
else:
logging.error("登录失败,请检查账号密码")
return False
def load_employees(self):
"""读取Excel数据"""
import pandas as pd
df = pd.read_excel(self.excel_path)
# 转换为字典列表
employees = df.to_dict(orient='records')
logging.info(f"读取到 {len(employees)} 条员工数据")
return employees
def sync_employee(self, emp):
"""同步单条员工数据"""
try:
# 刷新页面
self.driver.get("https://hr.example.com/employee/add")
time.sleep(1)
# 填充各字段
self.driver.find_element(By.NAME, "emp_id").send_keys(str(emp["emp_id"]))
self.driver.find_element(By.NAME, "emp_name").send_keys(emp["name"])
# 部门映射
dept_map = {"技术部": "TECH01", "市场部": "MKT01", "人力资源部": "HR01"}
dept_code = dept_map.get(emp.get("dept", ""), "UNKNOWN")
self.driver.find_element(By.NAME, "dept_code").send_keys(dept_code)
# 日期处理
hire_date = emp.get("hire_date", "")
if isinstance(hire_date, str) and "/" in hire_date:
from datetime import datetime
hire_date = datetime.strptime(hire_date, "%Y/%m/%d").strftime("%Y-%m-%d")
self.driver.find_element(By.NAME, "hire_date").send_keys(str(hire_date))
self.driver.find_element(By.NAME, "phone").send_keys(str(emp["phone"]))
# 提交
self.driver.find_element(By.ID, "submit-btn").click()
time.sleep(2)
# 验证结果
if "添加成功" in self.driver.page_source:
return True
else:
return False
except Exception as e:
logging.error(f"处理 {emp.get('name', '未知')} 时出错: {e}")
return False
def run(self):
"""执行同步"""
self.start_browser()
if not self.login():
return
employees = self.load_employees()
for i, emp in enumerate(employees):
logging.info(f"进度: {i+1}/{len(employees)} - {emp.get('name', '未知')}")
if self.sync_employee(emp):
self.success_count += 1
logging.info(f" ✓ 成功")
else:
self.error_count += 1
self.error_details.append(emp.get("name"))
logging.warning(f" ✗ 失败")
# 避免请求过快,加个小延迟
time.sleep(0.5)
self.driver.quit()
# 生成报告
self.generate_report()
def generate_report(self):
"""生成同步报告"""
report = f"""
╔══════════════════════════════════════╗
║ HR系统数据同步报告 ║
╠══════════════════════════════════════╣
║ 处理时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
║ 总记录数: {self.success_count + self.error_count}
║ 成功: {self.success_count}
║ 失败: {self.error_count}
║ 成功率: {self.success_count/(self.success_count+self.error_count)*100:.1f}%
╚══════════════════════════════════════╝
"""
if self.error_details:
report += f"\n失败名单: {', '.join(self.error_details)}"
logging.info(report)
print(report)
# 保存报告
with open(f"同步报告_{datetime.now().strftime('%Y%m%d')}.txt", "w", encoding="utf-8") as f:
f.write(report)
# 运行
if __name__ == "__main__":
sync = HRAutoSync("2024年11月员工入职信息.xlsx")
sync.run()
第一次运行的紧张与惊喜
写完后,小陈把代码跑了起来。
屏幕上的命令行飞快滚动,每处理一条员工信息,就打印一个进度。她盯着屏幕,心跳加速——万一出错了怎么办?万一数据填错了怎么办?万一系统崩了怎么办?
第一条:✓ 成功 第二条:✓ 成功 第三条:✓ 成功
十分钟后,二十条数据全部处理完毕。小陈打开HR系统,一个一个核对——数据完全正确。
她瘫在椅子上,眼眶有点热。
从那以后,每个月末的”数据马拉松”变成了一键启动的脚本。她只需要把业务部门发来的Excel表格丢进文件夹,运行脚本,然后该干嘛干嘛。
以前需要熬到凌晨两三个小时的工作,现在十分钟不到就搞定了。
这个方案背后的几个关键点
如果你也想类似的方式解放自己,这里有几个小陈踩过的坑:
1. 不要一上来就写代码,先理解系统
花两天时间研究系统的每个字段、每种交互方式,比你写一周代码但跑不通要有用得多。小陈的字段映射表,成了后来所有脚本的基础。
2. 从简单开始,逐步迭代
第一版脚本只做了最基本的”填表单-提交”,没有任何错误处理。跑通之后再逐步加日志、加异常处理、加报告生成。不要试图一次性写出完美版本。
3. 做好异常处理
系统不是每次都能顺利提交,有时候字段验证不通过,有时候网络超时。小陈后来加了一个”失败重试”机制,并且把失败的记录单独存起来,方便人工复查。
# 失败重试逻辑
def sync_with_retry(self, emp, max_retries=3):
for attempt in range(max_retries):
try:
if self.sync_employee(emp):
return True
except:
pass
time.sleep(2) # 失败后等两秒再试
return False
4. 安全第一
小陈后来把密码从代码里搬到了环境变量中,这样代码可以安全地提交到Git,也不怕密码泄露。
import os
from dotenv import load_dotenv
load_dotenv() # 从.env文件读取环境变量
username = os.getenv("HR_SYSTEM_USER")
password = os.getenv("HR_SYSTEM_PASSWORD")
现在的生活
小陈不再需要在深夜里和Excel较劲了。
每个月末,她收到业务部门发来的表格,花五分钟确认数据没问题,然后运行脚本。十分钟后,她可以安心地去吃晚饭、追剧、或者早点回家。
上个月,她甚至用省下来的时间,帮部门做了一个员工满意度调查的自动分析工具——同样的思路,用Python读问卷数据,自动生成图表和报告。
自动化不只是省时间,更重要的是,它把人的精力从重复劳动中解放出来,去做更有价值的事。
给想尝试的你
如果你也受够了每天手动复制粘贴的日子,别犹豫,开始行动。
工具不需要多高大上,Python加上Selenium就足够。知识不需要多深奥,B站上很多免费的入门教程。项目不需要一步到位,先让最基础的版本跑起来,再慢慢优化。
小陈说,她后悔的就是:没有更早开始。
那些曾经熬到深夜的两个小时,如果用在一件真正有意义的事情上,会是什么?这个问题,她现在有了新的答案。
