Python 下把“网站自动化点击”做成 CLI,通常用 Playwright + Typer/Click 最合适。

结论:
推荐方案是 Playwright 负责浏览器自动化,Typer 负责命令行封装。

相比 Selenium,Playwright 更现代,等待机制更好,适合自动登录、点击按钮、填写表单、下载文件、截图、批量执行等场景。



一、整体架构

你可以把项目做成这样:

web-click-cli/├── pyproject.toml├── webclick/│   ├── __init__.py│   ├── cli.py          # CLI 入口│   ├── browser.py      # 浏览器封装│   └── tasks.py        # 具体点击流程

核心思路:

用户在命令行输入参数        ↓Typer / Click 解析命令        ↓调用 Playwright 打开网页        ↓自动点击、输入、等待、截图或下载        ↓返回执行结果


二、安装依赖

pip install playwright typer richplaywright install

如果你只用 Chromium:

playwright install chromium


三、最小可用版本

webclick/cli.py

import typerfrom playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutErrorapp = typer.Typer(help="网站自动化点击 CLI 工具")@app.command()def click(    url: str = typer.Argument(..., help="要打开的网站地址"),    selector: str = typer.Option(..., "--selector", "-s", help="要点击的 CSS Selector"),    headless: bool = typer.Option(True, "--headless/--show", help="是否无头模式运行"),    timeout: int = typer.Option(10000, "--timeout", "-t", help="等待超时时间,单位毫秒"),):    """    打开网页并点击指定元素。    """    try:        with sync_playwright() as p:            browser = p.chromium.launch(headless=headless)            page = browser.new_page()            page.goto(url, wait_until="networkidle", timeout=timeout)            page.locator(selector).click(timeout=timeout)            typer.echo("点击成功")            browser.close()    except PlaywrightTimeoutError:        typer.echo("执行失败:元素等待超时", err=True)        raise typer.Exit(code=1)    except Exception as e:        typer.echo(f"执行失败:{e}", err=True)        raise typer.Exit(code=1)if __name__ == "__main__":    app()

运行:

python -m webclick.cli "https://example.com" --selector "button.submit" --show

其中:

--show

表示打开真实浏览器窗口,方便调试。



四、做成真正的 CLI 命令

推荐用 pyproject.toml

pyproject.toml

[project]name = "webclick"version = "0.1.0"description = "A CLI tool for website automation clicks"requires-python = ">=3.10"dependencies = [    "playwright",    "typer",    "rich"][project.scripts]webclick = "webclick.cli:app"

本地安装:

pip install -e .

然后可以直接运行:

webclick "https://example.com" --selector "button.submit" --show


五、支持登录、输入、点击的版本

很多网站不是只点一个按钮,还要登录、填写表单。可以这样封装:

@app.command()def login_and_click(    url: str,    username: str = typer.Option(..., "--username", "-u"),    password: str = typer.Option(..., "--password", "-p"),    username_selector: str = typer.Option(...),    password_selector: str = typer.Option(...),    login_button_selector: str = typer.Option(...),    target_selector: str = typer.Option(...),    headless: bool = typer.Option(True, "--headless/--show"),):    with sync_playwright() as p:        browser = p.chromium.launch(headless=headless)        page = browser.new_page()        page.goto(url, wait_until="networkidle")        page.locator(username_selector).fill(username)        page.locator(password_selector).fill(password)        page.locator(login_button_selector).click()        page.wait_for_load_state("networkidle")        page.locator(target_selector).click()        typer.echo("登录并点击完成")        browser.close()

运行示例:

webclick login-and-click \  "https://example.com/login" \  --username "myuser" \  --password "mypassword" \  --username-selector "#username" \  --password-selector "#password" \  --login-button-selector "button[type=submit]" \  --target-selector ".download-btn" \  --show


六、推荐加一个配置文件模式

如果点击流程比较固定,不建议每次在命令行写一长串 selector。可以用 YAML/JSON 配置。

例如 task.json

{  "url": "https://example.com/login",  "steps": [    {"type": "fill", "selector": "#username", "value": "myuser"},    {"type": "fill", "selector": "#password", "value": "mypassword"},    {"type": "click", "selector": "button[type=submit]"},    {"type": "wait", "seconds": 2},    {"type": "click", "selector": ".download-btn"}  ]}

然后 CLI:

import jsonimport timeimport typerfrom playwright.sync_api import sync_playwrightapp = typer.Typer()@app.command()def run(    config: str = typer.Argument(..., help="任务配置文件路径"),    headless: bool = typer.Option(True, "--headless/--show"),):    with open(config, "r", encoding="utf-8") as f:        task = json.load(f)    with sync_playwright() as p:        browser = p.chromium.launch(headless=headless)        page = browser.new_page()        page.goto(task["url"], wait_until="networkidle")        for step in task["steps"]:            step_type = step["type"]            if step_type == "fill":                page.locator(step["selector"]).fill(step["value"])            elif step_type == "click":                page.locator(step["selector"]).click()            elif step_type == "wait":                time.sleep(step["seconds"])            elif step_type == "screenshot":                page.screenshot(path=step["path"], full_page=True)            else:                raise ValueError(f"不支持的步骤类型: {step_type}")        browser.close()        typer.echo("任务执行完成")if __name__ == "__main__":    app()

运行:

webclick run task.json --show


七、选择元素时的建议

优先级建议:


方式示例稳定性
文本page.get_by_text("提交").click()
Rolepage.get_by_role("button", name="提交").click()
data-testid[data-testid="submit"]很高
CSS selector.submit-btn
XPath//button[text()='提交']较低

更推荐:

page.get_by_role("button", name="提交").click()

而不是:

page.locator("div > div > button:nth-child(2)").click()

后者很容易因为页面结构变化而失效。



八、实战中必须考虑的问题


问题处理方式
页面加载慢使用 wait_until="networkidle"locator.wait_for()
登录状态保持使用 storage_state 保存 Cookie
验证码不建议绕过,通常需要人工介入
反爬限制控制频率,遵守网站规则
元素动态变化使用 role、text、testid,而不是脆弱 XPath
下载文件page.expect_download()
调试困难增加 --show、截图、日志


九、保存登录状态

第一次手动登录:

with sync_playwright() as p:    browser = p.chromium.launch(headless=False)    page = browser.new_page()    page.goto("https://example.com/login")    input("请在浏览器中完成登录,然后按回车继续...")    page.context.storage_state(path="auth.json")    browser.close()

以后复用登录状态:

browser = p.chromium.launch(headless=True)context = browser.new_context(storage_state="auth.json")page = context.new_page()page.goto("https://example.com/dashboard")

这样就不需要每次重新登录。



十、我建议的最终技术选型


模块推荐
浏览器自动化Playwright
CLI 框架Typer
配置文件JSON 或 YAML
日志输出Rich
打包分发pyproject.toml
定时执行cron / systemd / GitHub Actions
登录态管理Playwright storage_state


最小推荐版本

如果只是自己用,先做成这个命令就够了:

webclick run task.json --show

task.json 定义所有步骤,Python 负责解释执行。

这样后续你要加“点击、输入、等待、截图、下载、循环、条件判断”,都比较容易扩展。