hahza
This commit is contained in:
99
MIGU_README.md
Normal file
99
MIGU_README.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# 咪咕短剧 flows.cdyylkj.com/miguSM/home 自动化说明
|
||||||
|
|
||||||
|
使用 **DrissionPage** + **TgeBrowser** 实现自动化:输入手机号、点击发送验证码、填写验证码。
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
1. **安装 TgeBrowser**:从 [https://tgebrowser.com/zh/download](https://tgebrowser.com/zh/download) 下载并安装
|
||||||
|
2. **启动 TgeBrowser 客户端**,确保本地 API 服务运行(默认端口 50326)
|
||||||
|
3. **获取 API Key**:TgeBrowser 客户端 → API → 生成新密钥
|
||||||
|
4. **设置环境变量**:
|
||||||
|
```powershell
|
||||||
|
$env:TGEBROWSER_API_KEY = "你的API密钥"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 流程说明
|
||||||
|
|
||||||
|
- **步骤一**:传入电话号码 → 新建一个 TgeBrowser 浏览器 → 打开 miguSM 页面 → 输入手机号 → 点击发送验证码 → 返回成功
|
||||||
|
- **步骤二**:传入验证码 → 在对应会话中填写验证码并提交
|
||||||
|
|
||||||
|
每个电话号码对应一个新建的浏览器实例。
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
### 方式一:API 服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 启动 API(端口 8001)
|
||||||
|
python api_migu.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**接口一:提交手机号**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST http://127.0.0.1:8001/api/submit_phone
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"phone": "13800138000",
|
||||||
|
"url": "https://flows.cdyylkj.com/miguSM/home"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"session_id": "uuid-xxx",
|
||||||
|
"message": "输入电话号码成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**接口二:提交验证码**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST http://127.0.0.1:8001/api/submit_code
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"session_id": "上一步返回的 session_id",
|
||||||
|
"code": "123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式二:命令行
|
||||||
|
|
||||||
|
**推荐:交互模式(一步完成)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python run_migu_cli.py --phone 13800138000 -i
|
||||||
|
# 输入手机号、点击发送验证码后,会提示「请输入短信验证码」,输入收到的验证码即可
|
||||||
|
```
|
||||||
|
|
||||||
|
**分步执行**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 步骤一
|
||||||
|
python run_migu_cli.py --phone 13800138000
|
||||||
|
|
||||||
|
# 步骤二(仅在同一 shell 内、步骤一之后执行,因为依赖内存中的 page)
|
||||||
|
python run_migu_cli.py --code 123456
|
||||||
|
```
|
||||||
|
|
||||||
|
> 跨进程/跨请求的会话请使用 API 模式(`api_migu.py`)。
|
||||||
|
|
||||||
|
## 选择器说明
|
||||||
|
|
||||||
|
若目标页面结构变化导致找不到输入框或按钮,可修改 `migu_miguSM_dp.py` 中的选择器列表(`_find_first` 所用选择器)。当前已适配常见 H5 表单结构。
|
||||||
|
|
||||||
|
## 文件说明
|
||||||
|
|
||||||
|
| 文件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `tgebrowser_client.py` | TgeBrowser REST API 客户端(创建/启动/停止浏览器) |
|
||||||
|
| `migu_miguSM_dp.py` | DrissionPage 自动化逻辑(输入手机号、发送验证码、填写验证码) |
|
||||||
|
| `api_migu.py` | FastAPI 接口(submit_phone / submit_code) |
|
||||||
|
| `run_migu_cli.py` | 命令行入口 |
|
||||||
135
api_migu.py
Normal file
135
api_migu.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
咪咕短剧 flows.cdyylkj.com/miguSM/home 接口:
|
||||||
|
- POST /api/submit_phone 传入电话号码 → 新建 TgeBrowser → 输入手机号、点击发送验证码 → 返回成功
|
||||||
|
- POST /api/submit_code 传入验证码 → 在对应会话中填写验证码并提交
|
||||||
|
|
||||||
|
每个电话号码对应一个新建的 TgeBrowser 浏览器会话。
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import uuid
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from migu_miguSM_dp import (
|
||||||
|
MIGU_HOME_URL,
|
||||||
|
connect_dp_to_tgebrowser,
|
||||||
|
connect_dp_to_ws,
|
||||||
|
input_code_and_submit,
|
||||||
|
input_phone_and_send_code,
|
||||||
|
)
|
||||||
|
from tgebrowser_client import TgeBrowserClient
|
||||||
|
|
||||||
|
app = FastAPI(title="咪咕短剧 miguSM 自动化 API", description="TgeBrowser + DrissionPage 手机号与验证码提交流程")
|
||||||
|
|
||||||
|
# 会话存储:session_id -> {page, env_id, client}
|
||||||
|
_sessions: dict[str, dict[str, Any]] = {}
|
||||||
|
_sessions_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
class SubmitPhoneRequest(BaseModel):
|
||||||
|
phone: str = Field(..., description="手机号码")
|
||||||
|
url: str = Field(MIGU_HOME_URL, description="目标页面 URL")
|
||||||
|
|
||||||
|
|
||||||
|
class SubmitPhoneResponse(BaseModel):
|
||||||
|
success: bool = True
|
||||||
|
session_id: str = Field(..., description="会话 ID,submit_code 时需传入")
|
||||||
|
message: str = "输入电话号码成功"
|
||||||
|
|
||||||
|
|
||||||
|
class SubmitCodeRequest(BaseModel):
|
||||||
|
session_id: str = Field(..., description="submit_phone 返回的会话 ID")
|
||||||
|
code: str = Field(..., description="短信验证码")
|
||||||
|
|
||||||
|
|
||||||
|
class SubmitCodeResponse(BaseModel):
|
||||||
|
success: bool = True
|
||||||
|
message: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/submit_phone", response_model=SubmitPhoneResponse)
|
||||||
|
def api_submit_phone(req: SubmitPhoneRequest):
|
||||||
|
"""
|
||||||
|
步骤一:传入电话号码。
|
||||||
|
新建 TgeBrowser 浏览器 → 打开 miguSM 页面 → 输入手机号 → 点击发送验证码。
|
||||||
|
返回 session_id,用于后续 submit_code。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
client = TgeBrowserClient()
|
||||||
|
# 每次输入电话号码新建一个浏览器
|
||||||
|
start_data = client.create_and_start(
|
||||||
|
browser_name=f"miguSM_{req.phone[-4:]}",
|
||||||
|
start_page_url=req.url,
|
||||||
|
)
|
||||||
|
port = start_data.get("port")
|
||||||
|
ws = start_data.get("ws")
|
||||||
|
env_id = start_data.get("envId")
|
||||||
|
|
||||||
|
# 优先用端口连接(DrissionPage 对端口支持更好)
|
||||||
|
if port:
|
||||||
|
page = connect_dp_to_tgebrowser(port)
|
||||||
|
elif ws:
|
||||||
|
page = connect_dp_to_ws(ws)
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail="TgeBrowser 未返回 port 或 ws")
|
||||||
|
|
||||||
|
input_phone_and_send_code(page, req.phone, url=req.url)
|
||||||
|
|
||||||
|
session_id = str(uuid.uuid4())
|
||||||
|
with _sessions_lock:
|
||||||
|
_sessions[session_id] = {
|
||||||
|
"page": page,
|
||||||
|
"env_id": env_id,
|
||||||
|
"client": client,
|
||||||
|
}
|
||||||
|
|
||||||
|
return SubmitPhoneResponse(success=True, session_id=session_id, message="输入电话号码成功")
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/submit_code", response_model=SubmitCodeResponse)
|
||||||
|
def api_submit_code(req: SubmitCodeRequest):
|
||||||
|
"""
|
||||||
|
步骤二:传入验证码。
|
||||||
|
在 submit_phone 创建的会话中填写验证码并提交。
|
||||||
|
"""
|
||||||
|
with _sessions_lock:
|
||||||
|
sess = _sessions.get(req.session_id)
|
||||||
|
if not sess:
|
||||||
|
raise HTTPException(status_code=404, detail=f"会话不存在或已过期: {req.session_id}")
|
||||||
|
|
||||||
|
page = sess["page"]
|
||||||
|
try:
|
||||||
|
result = input_code_and_submit(page, req.code)
|
||||||
|
return SubmitCodeResponse(success=True, message=result.get("message", "验证码已填写"))
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete("/api/session/{session_id}")
|
||||||
|
def close_session(session_id: str):
|
||||||
|
"""关闭并清理指定会话(可选,用于释放浏览器)。"""
|
||||||
|
with _sessions_lock:
|
||||||
|
sess = _sessions.pop(session_id, None)
|
||||||
|
if not sess:
|
||||||
|
return {"success": False, "message": "会话不存在"}
|
||||||
|
try:
|
||||||
|
client = sess.get("client")
|
||||||
|
env_id = sess.get("env_id")
|
||||||
|
if client and env_id is not None:
|
||||||
|
client.stop_browser(env_id=env_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return {"success": True, "message": "会话已关闭"}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8001)
|
||||||
163
migu_miguSM_dp.py
Normal file
163
migu_miguSM_dp.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
咪咕短剧超级会员 flows.cdyylkj.com/miguSM/home 自动化(DrissionPage + TgeBrowser):
|
||||||
|
1) 新建 TgeBrowser 浏览器
|
||||||
|
2) 打开页面,输入手机号,点击发送验证码 → 返回成功
|
||||||
|
3) 接收验证码并填写、提交
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from DrissionPage import ChromiumOptions, ChromiumPage
|
||||||
|
|
||||||
|
|
||||||
|
MIGU_HOME_URL = "https://flows.cdyylkj.com/miguSM/home"
|
||||||
|
|
||||||
|
|
||||||
|
def _find_first(page, selectors: list[str], timeout: float = 8):
|
||||||
|
"""尝试多个选择器,返回第一个找到的元素。"""
|
||||||
|
for sel in selectors:
|
||||||
|
try:
|
||||||
|
ele = page.ele(sel, timeout=timeout)
|
||||||
|
if ele:
|
||||||
|
return ele
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _click_safe(ele) -> None:
|
||||||
|
try:
|
||||||
|
ele.click()
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
ele.click(by_js=True)
|
||||||
|
except Exception:
|
||||||
|
ele.run_js("this.click()")
|
||||||
|
|
||||||
|
|
||||||
|
def input_phone_and_send_code(
|
||||||
|
page: ChromiumPage,
|
||||||
|
phone: str,
|
||||||
|
url: str = MIGU_HOME_URL,
|
||||||
|
wait_after_open: float = 1.0,
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
步骤一:输入手机号、点击发送验证码。
|
||||||
|
返回 {"success": True, "message": "输入电话号码成功"} 或抛出异常。
|
||||||
|
"""
|
||||||
|
page.get(url)
|
||||||
|
time.sleep(wait_after_open)
|
||||||
|
|
||||||
|
# 手机号输入框(按常见 H5 表单结构)
|
||||||
|
phone_input = _find_first(page, [
|
||||||
|
'x://input[@placeholder*="手机"]',
|
||||||
|
'x://input[@placeholder*="电话"]',
|
||||||
|
'x://input[@placeholder*="号码"]',
|
||||||
|
'x://input[@type="tel"]',
|
||||||
|
'x://input[@type="number"]',
|
||||||
|
"css:input[type='tel']",
|
||||||
|
"css:input[placeholder*='手机']",
|
||||||
|
"css:input[placeholder*='电话']",
|
||||||
|
"css:input.inp-txt",
|
||||||
|
"css:.phone-input input",
|
||||||
|
"css:input.phone",
|
||||||
|
], timeout=10)
|
||||||
|
if not phone_input:
|
||||||
|
raise RuntimeError("未找到手机号输入框,请根据页面调整选择器")
|
||||||
|
|
||||||
|
phone_input.input(phone, clear=True)
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
# 可选:勾选协议(若页面有)
|
||||||
|
agree = _find_first(page, [
|
||||||
|
"css:input[type='checkbox']",
|
||||||
|
"css:.agree-checkbox",
|
||||||
|
'x://input[@type="checkbox"]',
|
||||||
|
'x://i[contains(@class,"checkbox")]',
|
||||||
|
], timeout=2)
|
||||||
|
if agree:
|
||||||
|
try:
|
||||||
|
_click_safe(agree)
|
||||||
|
time.sleep(0.2)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 发送验证码按钮
|
||||||
|
send_btn = _find_first(page, [
|
||||||
|
'x://button[contains(text(),"获取验证码")]',
|
||||||
|
'x://span[contains(text(),"获取验证码")]',
|
||||||
|
'x://*[contains(text(),"获取验证码")]',
|
||||||
|
'x://button[contains(text(),"发送验证码")]',
|
||||||
|
'x://*[contains(text(),"发送验证码")]',
|
||||||
|
"css:button.btn-code",
|
||||||
|
"css:.send-code",
|
||||||
|
"css:.get-code",
|
||||||
|
"css:button[class*='code']",
|
||||||
|
"css:.verify-btn",
|
||||||
|
], timeout=8)
|
||||||
|
if not send_btn:
|
||||||
|
raise RuntimeError("未找到「获取验证码」按钮,请根据页面调整选择器")
|
||||||
|
|
||||||
|
_click_safe(send_btn)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
return {"success": True, "message": "输入电话号码成功"}
|
||||||
|
|
||||||
|
|
||||||
|
def input_code_and_submit(
|
||||||
|
page: ChromiumPage,
|
||||||
|
code: str,
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
步骤二:填写验证码并提交(若存在提交按钮)。
|
||||||
|
返回 {"success": True, "message": "验证码已填写"}。
|
||||||
|
"""
|
||||||
|
code_input = _find_first(page, [
|
||||||
|
'x://input[@placeholder*="验证码"]',
|
||||||
|
'x://input[@placeholder*="短信"]',
|
||||||
|
'x://input[@placeholder*="验证"]',
|
||||||
|
"css:input[placeholder*='验证码']",
|
||||||
|
"css:input[placeholder*='短信']",
|
||||||
|
"css:input.code-input",
|
||||||
|
"css:input.verify-input",
|
||||||
|
"css:input[type='text']",
|
||||||
|
"css:input.inp-txt",
|
||||||
|
], timeout=8)
|
||||||
|
if not code_input:
|
||||||
|
raise RuntimeError("未找到验证码输入框")
|
||||||
|
|
||||||
|
code_input.input(code, clear=True)
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# 若有确认/提交按钮则点击
|
||||||
|
submit_btn = _find_first(page, [
|
||||||
|
'x://button[contains(text(),"确认")]',
|
||||||
|
'x://button[contains(text(),"提交")]',
|
||||||
|
'x://*[contains(text(),"确认")]',
|
||||||
|
'x://*[contains(text(),"登录")]',
|
||||||
|
'x://*[contains(text(),"绑定")]',
|
||||||
|
"css:button.btn-primary",
|
||||||
|
"css:button.btn-buy",
|
||||||
|
"css:.submit-btn",
|
||||||
|
"css:.confirm-btn",
|
||||||
|
"css:img.btn-buy",
|
||||||
|
], timeout=5)
|
||||||
|
if submit_btn:
|
||||||
|
_click_safe(submit_btn)
|
||||||
|
|
||||||
|
return {"success": True, "message": "验证码已填写"}
|
||||||
|
|
||||||
|
|
||||||
|
def connect_dp_to_tgebrowser(port: int) -> ChromiumPage:
|
||||||
|
"""通过调试端口连接 TgeBrowser 已启动的浏览器。"""
|
||||||
|
co = ChromiumOptions().set_local_port(port=port)
|
||||||
|
return ChromiumPage(addr_or_opts=co)
|
||||||
|
|
||||||
|
|
||||||
|
def connect_dp_to_ws(ws_url: str) -> ChromiumPage:
|
||||||
|
"""通过 WebSocket 地址连接 TgeBrowser。"""
|
||||||
|
return ChromiumPage(addr_or_opts=ws_url)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# 项目依赖
|
# 项目依赖
|
||||||
DrissionPage>=4.0.0
|
DrissionPage>=4.0.0
|
||||||
|
requests>=2.28.0
|
||||||
Pillow>=10.0.0
|
Pillow>=10.0.0
|
||||||
numpy>=1.24.0
|
numpy>=1.24.0
|
||||||
fastapi>=0.100.0
|
fastapi>=0.100.0
|
||||||
|
|||||||
93
run_migu_cli.py
Normal file
93
run_migu_cli.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
咪咕 miguSM 命令行工具(TgeBrowser + DrissionPage):
|
||||||
|
1. 传入手机号 → 新建浏览器、输入手机号、点击发送验证码
|
||||||
|
2. 传入验证码 → 填写并提交
|
||||||
|
|
||||||
|
用法:
|
||||||
|
set TGEBROWSER_API_KEY=你的API密钥
|
||||||
|
python run_migu_cli.py --phone 13800138000
|
||||||
|
python run_migu_cli.py --session-id <上一步返回的session_id> --code 123456
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 确保 TgeBrowser 客户端和 migu 模块可导入
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from migu_miguSM_dp import (
|
||||||
|
MIGU_HOME_URL,
|
||||||
|
connect_dp_to_tgebrowser,
|
||||||
|
connect_dp_to_ws,
|
||||||
|
input_code_and_submit,
|
||||||
|
input_phone_and_send_code,
|
||||||
|
)
|
||||||
|
from tgebrowser_client import TgeBrowserClient
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_submit_phone(phone: str, url: str) -> str:
|
||||||
|
"""步骤一:输入手机号并发送验证码,返回 session_id(此处为 env_id 的字符串形式,用于后续填写验证码)。"""
|
||||||
|
client = TgeBrowserClient()
|
||||||
|
start_data = client.create_and_start(
|
||||||
|
browser_name=f"miguSM_{phone[-4:]}",
|
||||||
|
start_page_url=url,
|
||||||
|
)
|
||||||
|
port = start_data.get("port")
|
||||||
|
ws = start_data.get("ws")
|
||||||
|
env_id = start_data.get("envId")
|
||||||
|
|
||||||
|
if port:
|
||||||
|
page = connect_dp_to_tgebrowser(port)
|
||||||
|
elif ws:
|
||||||
|
page = connect_dp_to_ws(ws)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("TgeBrowser 未返回 port 或 ws")
|
||||||
|
|
||||||
|
input_phone_and_send_code(page, phone, url=url)
|
||||||
|
print("输入电话号码成功,请查收短信验证码")
|
||||||
|
# 保存到全局,供 submit_code 使用(CLI 模式只支持单次流程)
|
||||||
|
cmd_submit_phone._page = page # type: ignore
|
||||||
|
cmd_submit_phone._client = client # type: ignore
|
||||||
|
cmd_submit_phone._env_id = env_id # type: ignore
|
||||||
|
return str(env_id or "ok")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_submit_code(code: str) -> None:
|
||||||
|
"""步骤二:填写验证码并提交。"""
|
||||||
|
page = getattr(cmd_submit_phone, "_page", None)
|
||||||
|
if not page:
|
||||||
|
raise RuntimeError("请先执行步骤一:python run_migu_cli.py --phone 13800138000")
|
||||||
|
result = input_code_and_submit(page, code)
|
||||||
|
print(result.get("message", "验证码已填写"))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="咪咕 miguSM 自动化 CLI")
|
||||||
|
parser.add_argument("--phone", help="手机号码(步骤一)")
|
||||||
|
parser.add_argument("--code", help="短信验证码(步骤二)")
|
||||||
|
parser.add_argument("--interactive", "-i", action="store_true", help="步骤一后等待输入验证码(同一进程完成两步)")
|
||||||
|
parser.add_argument("--url", default=MIGU_HOME_URL, help=f"目标页面,默认 {MIGU_HOME_URL}")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.phone:
|
||||||
|
cmd_submit_phone(args.phone, args.url)
|
||||||
|
if args.interactive:
|
||||||
|
code = input("请输入短信验证码: ").strip()
|
||||||
|
if code:
|
||||||
|
cmd_submit_code(code)
|
||||||
|
elif args.code:
|
||||||
|
cmd_submit_code(args.code)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
print("\n示例:")
|
||||||
|
print(" python run_migu_cli.py --phone 13800138000 -i # 输入手机号后交互输入验证码")
|
||||||
|
print(" python run_migu_cli.py --phone 13800138000 # 仅步骤一")
|
||||||
|
print(" python run_migu_cli.py --code 123456 # 仅步骤二(需在 API 模式或同一进程的步骤一之后)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
159
tgebrowser_client.py
Normal file
159
tgebrowser_client.py
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
TgeBrowser API 客户端:用于通过 REST API 新建/启动浏览器环境,获取调试端口。
|
||||||
|
文档: https://docs.tgebrowser.com/api.html
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# TgeBrowser 本地 API 默认端口
|
||||||
|
DEFAULT_API_BASE = "http://127.0.0.1:50326"
|
||||||
|
# API Key(写死,也可通过环境变量 TGEBROWSER_API_KEY 覆盖)
|
||||||
|
DEFAULT_API_KEY = "asp_ad11de1727c77a5d514256b3209eec52130c6e5b4f9ccd92"
|
||||||
|
ENV_API_KEY = "TGEBROWSER_API_KEY"
|
||||||
|
|
||||||
|
|
||||||
|
def get_api_key() -> str:
|
||||||
|
"""优先从环境变量获取 API Key,未配置时使用默认值。"""
|
||||||
|
key = os.environ.get(ENV_API_KEY)
|
||||||
|
if key and key.strip():
|
||||||
|
return key.strip()
|
||||||
|
return DEFAULT_API_KEY
|
||||||
|
|
||||||
|
|
||||||
|
class TgeBrowserClient:
|
||||||
|
"""TgeBrowser 本地 API 客户端。"""
|
||||||
|
|
||||||
|
def __init__(self, base_url: str = DEFAULT_API_BASE, api_key: Optional[str] = None):
|
||||||
|
self.base_url = base_url.rstrip("/")
|
||||||
|
self.api_key = api_key or get_api_key()
|
||||||
|
self._headers = {
|
||||||
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _post(self, path: str, json: Optional[dict] = None) -> dict:
|
||||||
|
resp = requests.post(
|
||||||
|
f"{self.base_url}{path}",
|
||||||
|
headers=self._headers,
|
||||||
|
json=json or {},
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
if not data.get("success", False):
|
||||||
|
msg = data.get("message", "未知错误")
|
||||||
|
raise RuntimeError(f"TgeBrowser API 失败: {msg}")
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _get(self, path: str) -> dict:
|
||||||
|
resp = requests.get(
|
||||||
|
f"{self.base_url}{path}",
|
||||||
|
headers=self._headers,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
if not data.get("success", False):
|
||||||
|
msg = data.get("message", "未知错误")
|
||||||
|
raise RuntimeError(f"TgeBrowser API 失败: {msg}")
|
||||||
|
return data
|
||||||
|
|
||||||
|
def status(self) -> dict:
|
||||||
|
"""检查 API 服务状态。"""
|
||||||
|
return self._get("/api/status")
|
||||||
|
|
||||||
|
def create_browser(
|
||||||
|
self,
|
||||||
|
browser_name: str = "miguSM_automation",
|
||||||
|
start_page_url: Optional[str] = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
创建新的浏览器环境。
|
||||||
|
返回 data.envId 用于后续 start。
|
||||||
|
"""
|
||||||
|
# 使用 TgeBrowser 文档要求的完整结构,避免 400
|
||||||
|
start_page_value = [start_page_url] if start_page_url else []
|
||||||
|
start_page_mode = "custom" if start_page_value else "none"
|
||||||
|
|
||||||
|
# 指定手机端指纹,随机生成手机 UA
|
||||||
|
mobile_fingerprint = {
|
||||||
|
"os": "Android",
|
||||||
|
"platformVersion": 12,
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"browserName": browser_name,
|
||||||
|
"proxy": {"protocol": "direct"},
|
||||||
|
"fingerprint": mobile_fingerprint,
|
||||||
|
"startInfo": {
|
||||||
|
"startPage": {"mode": start_page_mode, "value": start_page_value},
|
||||||
|
"otherConfig": {"openConfigPage": False, "checkPage": False},
|
||||||
|
},
|
||||||
|
**kwargs,
|
||||||
|
}
|
||||||
|
data = self._post("/api/browser/create", json=payload)
|
||||||
|
return data.get("data", {})
|
||||||
|
|
||||||
|
def start_browser(
|
||||||
|
self,
|
||||||
|
env_id: Optional[int] = None,
|
||||||
|
user_index: Optional[int] = None,
|
||||||
|
port: Optional[int] = None,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
启动浏览器环境。
|
||||||
|
返回 data 含 ws、port、http 等,用于 DrissionPage 连接。
|
||||||
|
"""
|
||||||
|
payload: dict[str, Any] = {}
|
||||||
|
if env_id is not None:
|
||||||
|
payload["envId"] = env_id
|
||||||
|
elif user_index is not None:
|
||||||
|
payload["userIndex"] = user_index
|
||||||
|
else:
|
||||||
|
raise ValueError("必须指定 envId 或 userIndex")
|
||||||
|
|
||||||
|
if port is not None:
|
||||||
|
payload["port"] = port
|
||||||
|
payload.update(kwargs)
|
||||||
|
|
||||||
|
data = self._post("/api/browser/start", json=payload)
|
||||||
|
return data.get("data", {})
|
||||||
|
|
||||||
|
def stop_browser(self, env_id: Optional[int] = None, user_index: Optional[int] = None) -> dict:
|
||||||
|
"""停止浏览器环境。"""
|
||||||
|
payload: dict[str, Any] = {}
|
||||||
|
if env_id is not None:
|
||||||
|
payload["envId"] = env_id
|
||||||
|
elif user_index is not None:
|
||||||
|
payload["userIndex"] = user_index
|
||||||
|
else:
|
||||||
|
raise ValueError("必须指定 envId 或 userIndex")
|
||||||
|
return self._post("/api/browser/stop", json=payload)
|
||||||
|
|
||||||
|
def create_and_start(
|
||||||
|
self,
|
||||||
|
browser_name: str = "miguSM_automation",
|
||||||
|
start_page_url: Optional[str] = None,
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
创建并启动新浏览器,每次调用都会新建一个环境。
|
||||||
|
返回含 ws、port、envId 等,供 DrissionPage 连接。
|
||||||
|
"""
|
||||||
|
create_data = self.create_browser(
|
||||||
|
browser_name=browser_name,
|
||||||
|
start_page_url=start_page_url,
|
||||||
|
)
|
||||||
|
env_id = create_data.get("envId")
|
||||||
|
if env_id is None:
|
||||||
|
raise RuntimeError("创建浏览器失败:未返回 envId")
|
||||||
|
|
||||||
|
start_data = self.start_browser(env_id=env_id)
|
||||||
|
start_data["envId"] = env_id
|
||||||
|
return start_data
|
||||||
Reference in New Issue
Block a user