用 schedule 库做轻量级定时触发,避免 apscheduler 的冗余依赖;数据加载优先用 read_parquet;读取前校验路径,分层捕获异常并记录上下文日志;输出前校验数据质量与类型。

用 schedule 做轻量级定时触发,别碰 APScheduler 除非真需要集群
绝大多数单机数据分析脚本根本不需要 APScheduler 的复杂调度能力,它引入 SQLAlchemy、后台线程、持久化等冗余依赖,反而让脚本启动变慢、日志混乱、调试困难。直接用 schedule 库更可控——它就一个文件,纯 Python,无外部依赖。
安装只需:
pip install schedule
-
schedule.every().day.at("09:00").do(run_etl)是最常用写法,注意时间字符串必须是 24 小时制且带前导零("09:00"✅,"9:00"❌) - 避免在
do()里传带括号的调用(do(run_etl())),这会导致函数立即执行;应传函数对象(do(run_etl)) - 主循环必须显式加
while True:+schedule.run_pending()+time.sleep(60),漏掉sleep会让 CPU 占用飙到 100%
数据加载阶段优先用 pandas.read_parquet,别默认写 read_csv
CSV 是调试友好,但生产环境会拖垮整个流水线:解析慢、无类型推断、压缩率低、不支持行列裁剪。只要上游能输出 Parquet(比如 Spark、DuckDB、甚至 to_parquet()),就该切过去。
-
pandas.read_parquet("data/2024-05-20/part-00000.parquet")比同数据量 CSV 快 3–5 倍,内存占用低 40%+ - 若源是数据库,优先用
pd.read_sql配合chunksize流式拉取,避免一次性 load 全表 OOM - 读取前务必检查文件是否存在:
if not Path("input/").exists(): raise FileNotFoundError("missing input dir"),否则调度任务静默失败
异常处理必须包围每个关键步骤,且日志要带上下文
自动化脚本一旦某步出错又没捕获,后续任务就卡死或产生脏数据。但只写 except Exception: 是反模式——它会吞掉 KeyboardInterrupt(导致无法 Ctrl+C 中断),也掩盖真实错误类型。
- 按操作分层捕获:
FileNotFoundError处理路径问题,pd.errors.EmptyDataError处理空文件,requests.exceptions.Timeout处理 API 超时 - 每条日志必须含当前步骤标识和输入参数,例如:
logger.info("Loaded %d rows from %s", len(df), source_path) - 关键步骤失败后,建议主动退出(
sys.exit(1)),别靠调度器重试——重试可能放大问题(如重复写库、发告警)
输出写入前校验数据质量,用 pd.api.types.is_numeric_dtype 这类原生工具
很多脚本把清洗后的 DataFrame 直接 to_csv 或 to_sql,结果下游发现数值列混了字符串、时间格式错乱、空值未处理。这类问题在单次运行时难察觉,但调度跑一周后积重难返。
- 用
df.select_dtypes(include=["number"]).isna().sum()快速统计数值列空值数 - 对关键字段做类型断言:
assert pd.api.types.is_datetime64_any_dtype(df["event_time"]), "event_time must be datetime" - 写入数据库前,先用
df.to_sql(..., if_exists="replace", index=False)临时表验证结构,成功再RENAME切换,避免阻塞主表
真正麻烦的不是写调度逻辑,而是当某天凌晨三点 schedule 触发后,read_parquet 报 OSError: Invalid parquet file,而日志里只有一行“task failed”——这种时候,前面每一步的校验和上下文记录,就是你唯一能抓住的线索。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











