网站自动化点击如何做成 CLI
- 编程开发
- 2026-05-04
- 104 阅读
- 1 点赞
简介
Python 下把“网站自动化点击”做成 CLI,通常用 Playwright + Typer/Click 最合适。结论:推荐方案是 Playwright 负责浏览器自动化,...
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() | 中 |
| Role | page.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 --showtask.json 定义所有步骤,Python 负责解释执行。
这样后续你要加“点击、输入、等待、截图、下载、循环、条件判断”,都比较容易扩展。
下一篇 没有了