知识点思维导图
29 个知识节点
参考资料
Python(29) - 自动化脚本
读完后,你应能完成以下任务:
- 绘制“Python(29) - 自动化脚本 / 先建立前端锚点”的关键对象与数据流,解释“核心切入点:自动化脚本 ≈ Node 脚本,但标准库更全,路径处理是面向对象的 Path 对象而不是字符串拼接。”,并用源码位置、日志或 Trace 标注证据。
- 为“Python(29) - 自动化脚本 / pathlib:path + fs 的合体(面向对象)”设计正常与异常输入,验证“Node 里路径是字符串,你得 path.join(a, b) 再 fs.readFileSync 分两步。”,输出首个偏差位置与回归测试结果。
- 实现“Python(29) - 自动化脚本 / 构造路径与拼接”的最小代码或配置,检验“Path 用 / 拼路径是它最讨喜的设计:一眼能看出层级,而且自动处理 Windows 的 \ 和 Posix 的 /,别再手动用字符串 + "/" + 拼路径。”,输出命令、结果与 Diff,并说明不适用边界。
你在前端早就写过自动化脚本了——
node build.js批量压缩图片、写个fs脚本把一堆文件改名、配node-cron半夜跑构建。Python 干的是同一件事,而且因为它"自带电池"(标准库极全)、又是系统胶水语言,写这类脚本比 Node 更顺手。本篇解决三个问题:怎么用pathlib优雅地批处理文件(对标 Node 的path+fs);怎么调用外部命令(对标child_process);怎么让脚本定时跑起来(对标node-cron/ 系统 crontab)。
一、先建立前端锚点
写自动化脚本,你在 Node 里用过的那套,Python 几乎都有对应物:
| 你在 Node 里这么做 | Python 对应 | 干什么 |
|---|---|---|
require('path') 拼路径 |
pathlib.Path |
路径操作(面向对象) |
fs.readFileSync / fs.writeFile |
Path.read_text() / open() |
读写文件 |
fs.copyFile / fs.rename / fs-extra |
shutil 模块 |
复制/移动/压缩 |
glob 包找文件 |
Path.glob() / Path.rglob() |
通配符批量找文件 |
child_process.exec/spawn |
subprocess.run() |
调用外部命令 |
process.argv / commander / yargs |
argparse 模块 |
解析命令行参数 |
node-cron / setInterval |
schedule 库 / 系统 crontab |
定时任务 |
核心切入点:自动化脚本 ≈ Node 脚本,但标准库更全,路径处理是面向对象的 Path 对象而不是字符串拼接。下面逐个讲透。
二、pathlib:path + fs 的合体(面向对象)
Node 里路径是字符串,你得 path.join(a, b) 再 fs.readFileSync 分两步。Python 的 pathlib.Path 把"路径"和"对这个路径的文件操作"合在一个对象上。
2.1 构造路径与拼接
并排看 Node:
const path = require('path')
const logFile = path.join('logs', 'app.log')
path.basename(logFile) // app.log
path.basename(logFile, '.log') // app
path.extname(logFile) // .log
path.dirname(logFile) // logs
Path 用 / 拼路径是它最讨喜的设计:一眼能看出层级,而且自动处理 Windows 的 \ 和 Posix 的 /,别再手动用字符串 + "/" + 拼路径。
2.2 读写文件(小文件直接一行)
const fs = require('fs')
const content = fs.readFileSync('config.txt', 'utf-8')
fs.writeFileSync('config.txt', '新内容')
大文件别用
read_text一次性读,会吃满内存——用with open(...) as f: for line in f逐行流式读(详见第 06 篇生成器、第 09 篇上下文管理)。
2.3 glob:批量找文件(自动化脚本的灵魂)
批处理的第一步永远是"先把要处理的文件捞出来"。glob 用通配符匹配:
const { glob } = require('glob')
const pngs = await glob('images/*.png') // 当前层
const allPngs = await glob('images/**/*.png') // 递归
对照表:
| Node glob | Python |
|---|---|
glob('*.png') |
Path('.').glob('*.png') |
glob('**/*.png') 递归 |
Path('.').rglob('*.png') |
| 返回数组 | 返回惰性生成器(需要列表就 list(...)) |
三、shutil:高级文件操作(fs-extra 的角色)
Path 管单个文件的读写,复制、移动、整目录删除、打压缩包这些"重活"交给 shutil,它对标 Node 社区的 fs-extra。
const fse = require('fs-extra')
fse.copySync('a.txt', 'backup/a.txt')
fse.copySync('src', 'dist')
fse.moveSync('old.txt', 'new.txt')
fse.removeSync('temp_dir') // 对应 rmtree
⚠️
shutil.rmtree和 Node 的rm -rf一样危险,删错路径无法找回。脚本里务必先校验路径、或先打印将要删除的内容确认。
四、批量重命名实战(串起 pathlib + 推导式)
把前面两节串起来——一个高频需求:把目录下所有图片按 照片_001.jpg 顺序重命名。
并排看 Node:
const fs = require('fs')
const path = require('path')
function batchRename(folder, prefix = '照片') {
const files = fs.readdirSync(folder).filter(f => f.endsWith('.jpg')).sort()
files.forEach((old, i) => {
const newName = `${prefix}_${String(i + 1).padStart(3, '0')}${path.extname(old)}`
fs.renameSync(path.join(folder, old), path.join(folder, newName))
})
}
两点 Python 更顺手:glob 直接带过滤、f"{index:03d}" 内建补零(不用 padStart)。
五、subprocess:调用外部命令(child_process 的角色)
自动化脚本经常要"喊别的程序干活"——压缩视频喊 ffmpeg、提交代码喊 git。Node 用 child_process,Python 用 subprocess。
并排看 Node:
const { execFileSync } = require('child_process')
// execFile 同样推荐传参数数组,避免 shell 注入(对应 subprocess 的列表写法)
const out = execFileSync('git', ['status', '--short'], { encoding: 'utf-8' })
console.log(out)
对照表:
| Node | Python | 说明 |
|---|---|---|
execFileSync(cmd, args) |
subprocess.run([cmd, *args]) |
同步执行 |
spawn 流式 |
subprocess.Popen |
需要实时读输出时 |
{ encoding: 'utf-8' } |
text=True |
输出转字符串 |
| 非 0 退出抛错 | check=True |
失败即中断 |
⚠️ 千万别传
shell=True+ 拼接的字符串命令。subprocess.run(f"rm {user_input}", shell=True)一旦user_input是; rm -rf /就是灾难(命令注入)。默认用列表形式,参数自动转义,安全。
六、argparse:解析命令行参数(commander 的角色)
脚本要复用就得能传参,比如 python backup.py --src logs --dest /tmp。Node 用 commander/yargs,Python 标准库自带 argparse。
命令行运行:python backup.py logs --dest /tmp/bak --zip
并排看 Node(commander):
const { program } = require('commander')
program
.argument('<src>', '要备份的源目录')
.option('--dest <path>', '备份输出目录', './backup')
.option('--zip', '是否打成 zip')
.parse()
const opts = program.opts()
argparse 是标准库、零依赖,还自动生成 -h/--help 帮助文档,写个人脚本完全够用,不必装第三方库。
七、定时任务:三种方式按场景选
让脚本"到点自动跑",有三档方案,从轻到重:
7.1 schedule 库(最像 node-cron,进程常驻)
第三方库 schedule(pip install schedule),API 极直白,适合脚本自己常驻跑:
const cron = require('node-cron')
// node-cron 用 crontab 表达式;进程同样要常驻
cron.schedule('*/10 * * * *', () => console.log('执行备份...'))
边界(哪里不一样):node-cron 注册完回调,事件循环自己会在后台触发;schedule 没有后台线程,必须你自己写 while True 轮询,否则注册了也不会跑。这是从 Node 过来最容易愣住的点。
7.2 系统 crontab(Linux/Mac,脚本跑完就退出)
更省资源的做法:脚本写成"跑一次就退出"的普通脚本,把"定时"交给操作系统。这才是生产环境主流。
# 编辑当前用户的定时任务表
crontab -e
# 五个字段:分 时 日 月 周。下面表示「每天 02:30 执行」
# 务必用 python 的绝对路径和脚本绝对路径,WHY:cron 的环境变量极简,
# 不带你 shell 里的 PATH,写 python 而非绝对路径常常找不到解释器
30 2 * * * /usr/bin/python3 /home/imber/backup.py >> /home/imber/backup.log 2>&1
对前端来说,这就是把 node-cron 的表达式搬到了系统层;脚本本身不再常驻,到点由系统拉起、跑完即退,更省内存也更稳。Windows 上对应的是「任务计划程序」。
7.3 该选哪个
| 场景 | 选择 |
|---|---|
| 临时脚本、想一份代码搞定调度 | schedule(进程常驻) |
| 服务器上长期定时任务 | 系统 crontab(脚本跑完退出) |
| 已经在用 FastAPI/异步服务 | APScheduler(能跑在事件循环里) |
真实后端项目里的重型分布式定时任务(多机、可视化、失败重试)会用 xxl-job 这类调度中心——对照 Java 笔记第 28 篇。个人脚本用不到那么重。
八、最容易踩的坑
-
手动用字符串拼路径。
"logs" + "/" + name在 Windows 上分隔符不对。永远用Path("logs") / name,跨平台自动正确。 -
read_text不写 encoding。Mac/Linux 默认 UTF-8 没事,到 Windows 默认 gbk 直接乱码或报错。所有读写文本都显式encoding="utf-8"。 -
subprocess用shell=True+ 字符串拼接。命令注入重灾区。默认用列表形式["git", "status"],参数自动转义、不过 shell。 -
shutil.rmtree删错目录。等价rm -rf,不可逆。删之前先打印将删除的路径确认。 -
schedule注册了却不跑。忘了写while True: schedule.run_pending(); time.sleep(1)驱动循环——它不像node-cron有后台线程,必须自己轮询。 -
glob当成列表反复用。它返回的是惰性生成器,遍历一次就空了(详见第 06 篇)。需要多次用就list(...)固化。 -
cron 里直接写
python xxx.py。cron 环境的 PATH 极简,往往找不到解释器或脚本。用绝对路径,并把日志重定向到文件方便排查。
九、总结
- 先建立前端锚点:核心切入点:自动化脚本 ≈ Node 脚本,但标准库更全,路径处理是面向对象的 Path 对象而不是字符串拼接。
- pathlib:path + fs 的合体(面向对象):Node 里路径是字符串,你得 path.join(a, b) 再 fs.readFileSync 分两步。
- shutil:高级文件操作(fs-extra 的角色):Path 管单个文件的读写,复制、移动、整目录删除、打压缩包这些"重活"交给 shutil,它对标 Node 社区的 fs-extra。
- 批量重命名实战(串起 pathlib + 推导式):把前面两节串起来——一个高频需求:把目录下所有图片按 照片_001.jpg 顺序重命名。
- subprocess:调用外部命令(child_process 的角色):| spawn 流式 | subprocess.Popen | 需要实时读输出时 |
- argparse:解析命令行参数(commander 的角色):argparse 是标准库、零依赖,还自动生成 -h/--help 帮助文档,写个人脚本完全够用,不必装第三方库。
学完自测
选择所有正确答案;提交后逐项核对判断依据。