代码语言

知识点思维导图

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,进程常驻)

第三方库 schedulepip 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 篇。个人脚本用不到那么重。


八、最容易踩的坑

  1. 手动用字符串拼路径"logs" + "/" + name 在 Windows 上分隔符不对。永远用 Path("logs") / name,跨平台自动正确。

  2. read_text 不写 encoding。Mac/Linux 默认 UTF-8 没事,到 Windows 默认 gbk 直接乱码或报错。所有读写文本都显式 encoding="utf-8"

  3. subprocessshell=True + 字符串拼接。命令注入重灾区。默认用列表形式 ["git", "status"],参数自动转义、不过 shell。

  4. shutil.rmtree 删错目录。等价 rm -rf,不可逆。删之前先打印将删除的路径确认。

  5. schedule 注册了却不跑。忘了写 while True: schedule.run_pending(); time.sleep(1) 驱动循环——它不像 node-cron 有后台线程,必须自己轮询。

  6. glob 当成列表反复用。它返回的是惰性生成器,遍历一次就空了(详见第 06 篇)。需要多次用就 list(...) 固化。

  7. 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 帮助文档,写个人脚本完全够用,不必装第三方库。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“自动化脚本”中,需要同时满足“先建立前端锚点”与“pathlib:path + fs 的合体(面向对象)”。给定正文约束“自动化脚本 ≈ Node 脚本,但标准库更全,路径处理是面向对象的 Path 对象而不是字符串拼接。”,哪些判断保持了原有处理机制?多选
2“自动化脚本”出现偏差:“在“自动化脚本 / 构造路径与拼接”中,即使不满足“一眼能看出层级,而且自动处理 Windows 的 \ 和 Posix 的 /,别再手动用字符串 + "/" + 拼路径”,结果与副作用仍会保持不变。”已成为实际行为。围绕“构造路径与拼接”与“读写文件(小文件直接一行)”,哪些判断能定位被改变的职责或边界?多选
3评审“自动化脚本”方案时,验收条件包含“批处理的第一步永远是"先把要处理的文件捞出来"。”。关于“glob:批量找文件(自动化脚本的灵魂)”与“shutil:高级文件操作(fs-extra 的角色)”的哪些决策符合正文机制?多选