用 Python 写自动化脚本:定时任务、文件批处理、自动出报表
重复三遍的活,就该交给脚本;你省下的不是那几分钟,是"又得手动搞一遍"的烦躁。
很多同学一听到"自动化"就想到写个平台、搭个服务、搞个前端界面,结果三个月过去还在搭架子,真正要 automate 的活一件没动。其实对大学生和初中级开发者来说,90% 的需求,一个几十行的小脚本 + 一个定时任务就能解决。这篇我把自己常用的三板斧------文件批处理、定时任务、自动出报表------连同排雷清单一起给你。它们不炫技,但真能天天帮你省时间,而且每一段代码你复制过去改改路径就能跑。
我写自动化有个原则:先跑通一个最細最小、但确确实实每天省我 20 分钟的脚本,而不是先规划一个"完美系统"。等这个小脚本稳定跑了一个月,你自然知道下一步该把什么也自动化。
一、什么活该自动化
先判断值不值得,别把一次性活硬做成系统。我给自己定的三条标准:重复频率 ≥ 每月一次、规则明确可描述、出错成本可控。
| 场景 | 是否值得自动化 | 理由 |
|---|---|---|
| 每月导报表、重命名几百个文件 | ✅ 值得 | 频率高、规则固定、纯体力 |
| 每天备份某目录到移动硬盘 | ✅ 值得 | 重复且容易忘,漏了才后悔 |
| 一次性数据清洗(只用一次) | ❌ 不值得 | 写完就扔,性价比极低 |
| 规则含糊、要靠人判断的审批 | ❌ 不值得 | 自动化出错成本太高,人更稳 |
| 但需要"人拍板"前的整理工作 | ✅ 值得 | 把脏活前置,人只做判断 |
记住:自动化是为了省心且可重复,不是为了炫技。能用一行命令解决的,别上框架。
我第一次尝到甜头,是大三做助教时每周要收几十份作业、手动按学号重命名再打分。前两周还行,第三周就烦了,于是花一个晚上写了个重命名脚本,之后每周省下快一小时。后来我发现,判断"该不该自动化"还有个更实在的角度:算一笔账------写脚本花 2 小时,如果它能每周帮你省 1 小时,两周就回本,之后全是纯赚。那些"写脚本比手动还慢"的活,通常是因为你想把它做成通用系统了。
二、场景一:文件批处理
这是最入门也最容易出事的场景。两个我踩过血泪坑的原则:用 pathlib 而不是字符串拼路径 (路径里有中文、空格、反斜杠时,字符串拼接必翻车);危险操作先 dry-run 打印,确认无误再执行(删文件、移动文件前先打印要干什么)。
为什么非要用 pathlib?因为它把路径当成对象而不是字符串,folder / "a.jpg" 自动按系统补分隔符,还能链式调用 .glob()、.stat()、.rename(),读起来就是人话。相比之下 os.path.join 拼一堆字符串,一旦漏了斜杠或混了正反斜杠,在 Windows 上就会悄悄出错。另外 glob 的通配符(如 *.xlsx、2024*)比手写遍历再正则判断清爽太多,配合 iterdir() 遍历目录几乎是批处理标配。
python
from pathlib import Path
from datetime import datetime, timedelta
import shutil
BASE = Path(r"D:/工作资料/导出") # pathlib 自动处理斜杠,不怕中文空格
BACKUP = Path(r"D:/工作资料/已归档")
# ---- 批量重命名:给所有 jpg 加日期前缀 ----
def rename_with_date(folder: Path):
for f in folder.glob("*.jpg"):
new_name = f"{datetime.now():%Y%m%d}_{f.name}"
print(f"[dry-run] {f.name} -> {new_name}") # 先打印
# 确认无误后取消注释:
# f.rename(f.with_name(new_name))
# ---- 按日期归档:把 30 天前的文件搬进归档目录 ----
def archive_old(folder: Path, days=30):
cutoff = datetime.now() - timedelta(days=days)
for f in folder.iterdir():
if f.is_file() and datetime.fromtimestamp(f.stat().st_mtime) < cutoff:
dest = BACKUP / f.name
print(f"[dry-run] 归档 {f.name}") # 先打印
# shutil.move(str(f), str(dest))
# ---- 清理过期文件:只删 .tmp 且超过 7 天 ----
def clean_tmp(folder: Path, days=7):
cutoff = datetime.now() - timedelta(days=days)
for f in folder.glob("*.tmp"):
if datetime.fromtimestamp(f.stat().st_mtime) < cutoff:
print(f"[dry-run] 删除 {f}") # 先打印
# f.unlink() # 真删只在 dry-run 验证后执行
批量改 Excel 用 openpyxl 或 pandas,同样先只读预览。一个我最想强调的保险:批量删除前,先整体备份目录 。一行 shutil.copytree 花几秒,却能让你在删错时保住全部数据,这是最便宜的保险。
三、场景二:定时任务
脚本写完,总不能天天手动跑。三种方案怎么选:
如果你用的是 Windows 本机,最简单的其实是图形化的"任务计划程序":搜索打开后新建任务,触发器设"每天 9:00",操作选"启动程序",填 python.exe 的绝对路径和脚本路径即可,全程不用碰命令行。缺点是换电脑要重新配一遍。而服务器上基本就是 crontab 的天下,稳定且开机自启。至于 schedule 库,适合那种"脚本自己常驻、不想依赖系统调度"的小工具,但它本质是进程内循环,进程挂了就停,可靠性弱于系统级调度。
| 方案 | 适用 | 优点 | 缺点 |
|---|---|---|---|
| Windows 任务计划程序 | 自己电脑、Windows | 图形界面、零代码 | 换机器要重配 |
| Linux crontab | 服务器 | 稳定、开机自启 | 语法易写错、环境精简 |
Python schedule 库 |
脚本常驻 | 配置在代码里、跨平台 | 进程挂了就停 |
crontab 五个星号 :分 时 日 月 周,从左到右。新手最常写错的就是"每隔"和"每天":
# ❌ 想每天 9 点跑,写成 */9 * * * * (这是每隔 9 小时跑一次)
# ✅ 每天 9:00
0 9 * * *
# ❌ 漏了分钟,写成 9 * * * * (9 点那一小时每分钟都跑)
# ✅
0 9 * * *
# 每 5 分钟:*/5 * * * *
# 工作日 18:30:30 18 * * 1-5
最关键的坑:crontab 里找不到 python 。cron 的环境变量极精简,没有你的 PATH,所以必须写 python 的绝对路径:
# 先 which python3 拿到路径,再写进 crontab
0 9 * * * /usr/bin/python3 /home/you/report.py >> /home/you/run.log 2>&1
上一次没跑完怎么办? 这是定时任务最容易翻车的地方。比如报表脚本要跑 40 分钟,你设了每 30 分钟一次,就可能重叠。用锁文件防止重叠执行:
python
import os, sys, time
LOCK = "/tmp/my_job.lock"
if os.path.exists(LOCK):
print("上次还没跑完,本次跳过")
sys.exit(0)
open(LOCK, "w").close() # 占个坑
try:
main() # 真正的业务逻辑
finally:
os.remove(LOCK) # 无论成败都释放锁,避免死锁
四、场景三:自动出报表
读多个 Excel/CSV → 汇总 → 写多 sheet 并设格式 → 发出去,这是办公室场景最香的自动化。很多人以为"出报表"就是 to_excel 一把梭,但真正能用的报表,还讲究两点:一是多 sheet 分区 (明细放一张、汇总放一张、异常放一张),让人一眼找到重点;二是格式与条件格式,比如金额用色阶标出高低,领导扫一眼就能看出异常。下面代码把这两点都做了:
核心代码如下:
python
import pandas as pd
from pathlib import Path
from openpyxl.styles import Font, PatternFill
from openpyxl.formatting.rule import ColorScaleRule
src = Path(r"D:/数据/每日")
files = list(src.glob("销售_*.xlsx"))
# 1) 读取并合并多个文件(每个文件是某天的销售)
frames = [pd.read_excel(f) for f in files]
df = pd.concat(frames, ignore_index=True)
# 合并前最好先统一列名和类型,否则 concat 会把不一致的列补成 NaN
# 比如:df.columns = [c.strip() for c in df.columns]
# 2) 汇总:按区域统计金额和笔数
summary = df.groupby("区域")["金额"].agg(["sum", "count"]).reset_index()
# 3) 写出多 sheet,并设格式
with pd.ExcelWriter("月报.xlsx", engine="openpyxl") as writer:
df.to_excel(writer, sheet_name="明细", index=False)
summary.to_excel(writer, sheet_name="汇总", index=False)
wb = writer.book
ws = writer.sheets["汇总"]
# 标题加粗白字蓝底
for c in ws[1]:
c.font = Font(bold=True, color="FFFFFF")
c.fill = PatternFill("solid", fgColor="4472C4")
# 金额列加色阶条件格式,高值绿低值红,一眼看出异常
ws.conditional_formatting.add(
"B2:B100",
ColorScaleRule(start_type="min", start_color="F8696B",
end_type="max", end_color="63BE7B")
)
# 4) 发出去:塞共享盘 or 发邮件(邮件见第五节)
Path("月报.xlsx").replace(r"\\NAS\共享\月报.xlsx")
五、让脚本更可靠
脚本稳定运行后,要面对"半夜它挂了没人知道"的问题。四条加固建议:
- 日志写文件 :用
logging而不是print,出问题能回看时间线。
python
import logging
logging.basicConfig(filename="job.log", level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s")
logging.info("报表生成完成,共 %d 行", len(df))
- 异常重试 :网络请求、文件 IO 这类不稳定操作,包一层重试(可用
tenacity,或自己写 for 循环)。 - 失败通知:脚本抛异常时,发邮件或打 webhook,让你第一时间知道:
python
import requests
def notify(text):
requests.post("https://你的webhook地址", json={"text": text})
- 参数化 + 配置分离 :用
argparse接收路径、日期,别写死;数据库密码、token 放环境变量或.env,绝不写死在代码里,也别提交到 git。
六、打包给别人用
同事不会装 Python?用 pyinstaller 打成 exe,他双击就能跑:
bash
pip install pyinstaller
pyinstaller -F -w report.py # -F 打包成单文件,-w 不弹控制台窗口
坑提醒:体积往往 10MB+ (它把整个解释器和依赖都打进去了);杀软可能误报 ,给同事用前最好数字签名或让他们加白名单;-w 模式下报错你看不到,调试期先不加 -w,等稳定了再加。
如果体积和误报让你头疼,还有个替代思路:不打包 exe,而是给同事一个 requirements.txt 加一句 pip install -r requirements.txt && python main.py,很多时候反而更干净。或者试 nuitka 把 Python 编译成二进制,体积和启动速度都更好,只是配置略复杂。选哪种取决于你的使用对象:给完全不懂技术的同事,exe 最省事;给会点命令行的同学,源码分发更透明。
七、常见坑
下面这些坑我几乎每个都踩过,列出来帮你省时间。它们大多不是语法错误,而是"在别人的机器上突然跑不起来"------所以脚本能在本机跑通,离"真能用"还差最后这几步。
- 坑 1:路径里的空格与中文 。统一用
pathlib.Path,别手写字符串拼接,Windows 下尤其容易炸。 - 坑 2:Excel 被占用无法写入 。写之前确保文件已关闭,或者用临时文件名写完再
replace覆盖,避免"文件正在使用"报错。 - 坑 3:编码乱码 。写 CSV 用
encoding="utf-8-sig",Excel 才认中文 BOM,否则打开是一堆乱码。 - 坑 4:时区 。服务器多是 UTC,涉及"今天""本月"的判定要用
zoneinfo锁定时区,否则半夜跑出来的是昨天的数。 - 坑 5:crontab 找不到 python。见第三节,必须写绝对路径。
- 坑 6:脚本没设工作目录 。cron 下工作目录是用户根目录,相对路径会全错------开头用
os.chdir()或全程用绝对路径。
八、收束
自动化最大的敌人不是技术,是"想一口气做个平台"的冲动。我建议:先写一个"每天省我 20 分钟"的小脚本(比如自动归档导出文件、自动重命名截图),跑顺了再考虑定时任务和报表。等它真正省下时间,你自然知道下一步该自动化什么------而不是停在"规划完美的系统"这一步,永远不动手。等你攒了三五个小脚本,再想"要不要整合",那时你才真正知道整合要解决什么问题。