>>>PyPathPython 学习站
前沿与实践 · Frontier & Practice lv.4 前沿 kp-043

自动化与 CLI 工具

前置知识:kp-022、kp-036

1. 一句话定义

Python 是"把重复劳动变成一条命令"的语言:argparse/typer 做命令行界面,subprocess 驱动外部程序,requests/httpx 做网络采集,openpyxl/python-docx 处理办公文档——脚本通过 uv tool/pipx 变成全局命令。

2. 为什么重要

自动化是 Python 最低门槛的"复利场景":一次写好,每天省十分钟。同时 CLI 开发综合了参数解析、异常处理、日志、打包(kp-015/036/037)——是极佳的练手综合项目。

3. 前置知识

kp-022(标准库)、kp-036(打包)。

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. 常见误区

  1. 用 os.system / shell 字符串拼接 —— 注入风险 + 无退出码;一律 subprocess.run 列表参数。
  2. 破坏性操作没有 --dry-run —— 误删是自动化事故第一名。
  3. 爬虫不带限速 —— 高频请求既违法纪又易被封;sleep/并发上限是礼貌也是自保。
  4. 脚本退出码永远是 0 —— CI 与 cron 无法感知失败;sys.exit(1) + 异常上抛。

10. 自测题

  1. subprocess.run(..., check=True) 的行为?为什么优于 shell=True?
  2. stdout 与 stderr 在 CLI 中的分工?
  3. 把脚本变成全局命令的现代方式?
参考答案
  1. 非零退出码抛 CalledProcessError,失败不会静默;列表参数避免 shell 注入与引号转义地狱。
  2. stdout 只输出"程序结果数据"(可被管道消费);stderr 输出日志、进度、错误(给人看)。
  3. pyproject 声明 [project.scripts] 后 uv tool install / pipx install;临时用 uvx。

11. 与其他知识点的关系

  • kp-036 打包:工具发布的机制层。
  • kp-031 asyncio:高并发采集的执行模型。
  • kp-048 安全与伦理:注入、爬虫边界。

12. 延伸阅读

  • Typer 文档:https://typer.tiangolo.com/
  • Click(Typer 底层)与 argparse 对比