前沿与实践 · Frontier & Practice
lv.4 前沿
kp-043
自动化与 CLI 工具
1. 一句话定义
Python 是"把重复劳动变成一条命令"的语言:argparse/typer 做命令行界面,subprocess 驱动外部程序,requests/httpx 做网络采集,openpyxl/python-docx 处理办公文档——脚本通过 uv tool/pipx 变成全局命令。
2. 为什么重要
自动化是 Python 最低门槛的"复利场景":一次写好,每天省十分钟。同时 CLI 开发综合了参数解析、异常处理、日志、打包(kp-015/036/037)——是极佳的练手综合项目。
3. 前置知识
4. 核心概念
python
# ① 标准库 argparse:零依赖 CLI
import argparse
def main():
p = argparse.ArgumentParser(description="批量重命名")
p.add_argument("src", help="源目录")
p.add_argument("--dry-run", action="store_true", help="只预览不执行")
p.add_argument("-v", "--verbose", action="count", default=0)
args = p.parse_args()
print(args.src, args.dry_run, args.verbose)
if __name__ == "__main__":
main()python
# ② Typer:类型注解驱动的现代 CLI(argparse 的 3.x)
import typer
app = typer.Typer()
@app.command()
def rename(src: str, dry_run: bool = False):
"""批量重命名文件"""
...
if __name__ == "__main__":
app()python
# ③ subprocess:驱动外部命令(替代 os.system)
import subprocess
r = subprocess.run(
["git", "status", "--porcelain"],
capture_output=True, text=True, check=True, # check:非零退出即抛异常
timeout=30,
)
print(r.stdout)5. 原理与机制
工具化发布闭环(kp-036 的应用面):
bash
uv tool install . # 项目装成全局命令(独立隔离环境)
mytool src --dry-run
uvx mytool ... # 免安装直接跑(一次性工具)爬虫/采集分层:
text
静态页 → httpx/requests + BeautifulSoup(或 selectolax)
需执行 JS → Playwright(无头浏览器,现代首选)
批量并发 → asyncio + httpx.AsyncClient(kp-031)
登录/鉴权 → 会话对象、cookie 管理
尊重边界 → robots.txt、限速、目标站点条款(伦理,kp-048)办公自动化:openpyxl(xlsx)、python-docx(word)、reportlab(pdf)、pandas.read_excel(批量表)。
6. 关键事实(模型/图示)
text
CLI 设计四原则:
① --dry-run 先行(破坏性操作必须可预演)
② 退出码语义化(0 成功;非 0 失败——供 shell/CI 判断)
③ stderr 放日志/进度,stdout 只放"结果数据"(可管道)
④ 长任务给进度与可中断(Ctrl+C 干净退出)7. 直观类比
CLI 工具是自动售货机:投币(参数)出货(结果),不用排队找店员(不用打开编辑器);--dry-run 是"先看试吃装";uvx 是即弃碗筷——吃完就走,不用洗(不污染本地环境)。
8. 实例与案例
python
# 一个健壮的自动化脚本骨架
#!/usr/bin/env python3
"""备份指定目录到目标盘。"""
import argparse, logging, sys
from pathlib import Path
import shutil
def main(argv=None):
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("src", type=Path)
p.add_argument("dst", type=Path)
p.add_argument("--dry-run", action="store_true")
args = p.parse_args(argv)
if not args.src.is_dir():
p.error(f"源目录不存在: {args.src}")
for f in args.src.rglob("*.md"):
target = args.dst / f.name
if args.dry_run:
print(f"将复制 {f} -> {target}")
else:
shutil.copy2(f, target)
return 0
if __name__ == "__main__":
sys.exit(main())9. 常见误区
- 用
os.system/ shell 字符串拼接 —— 注入风险 + 无退出码;一律subprocess.run列表参数。 - 破坏性操作没有 --dry-run —— 误删是自动化事故第一名。
- 爬虫不带限速 —— 高频请求既违法纪又易被封;sleep/并发上限是礼貌也是自保。
- 脚本退出码永远是 0 —— CI 与 cron 无法感知失败;
sys.exit(1)+ 异常上抛。
10. 自测题
subprocess.run(..., check=True)的行为?为什么优于 shell=True?- stdout 与 stderr 在 CLI 中的分工?
- 把脚本变成全局命令的现代方式?
参考答案
- 非零退出码抛 CalledProcessError,失败不会静默;列表参数避免 shell 注入与引号转义地狱。
- stdout 只输出"程序结果数据"(可被管道消费);stderr 输出日志、进度、错误(给人看)。
- pyproject 声明 [project.scripts] 后
uv tool install/pipx install;临时用 uvx。
11. 与其他知识点的关系
12. 延伸阅读
- Typer 文档:https://typer.tiangolo.com/
- Click(Typer 底层)与 argparse 对比