208 lines
6.0 KiB
Python
208 lines
6.0 KiB
Python
"""登录处理器:处理网站登录流程。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import time
|
|
from typing import Any, Optional
|
|
|
|
from jiangchang_skill_core.activity import interruptible_sleep
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 登录相关选择器(需要根据实际页面调整)
|
|
LOGIN_SELECTORS = {
|
|
"username_input": [
|
|
"input[name='username']",
|
|
"input[placeholder*='用户名']",
|
|
"input[placeholder*='账号']",
|
|
"#username",
|
|
"[data-testid='username']",
|
|
],
|
|
"password_input": [
|
|
"input[name='password']",
|
|
"input[type='password']",
|
|
"#password",
|
|
"[data-testid='password']",
|
|
],
|
|
"login_button": [
|
|
"button[type='submit']",
|
|
"button:has-text('登录')",
|
|
"input[type='submit']",
|
|
".login-btn",
|
|
"[data-testid='login-button']",
|
|
],
|
|
"captcha_input": [
|
|
"input[name='captcha']",
|
|
"input[placeholder*='验证码']",
|
|
"#captcha",
|
|
],
|
|
"captcha_image": [
|
|
".captcha-image",
|
|
"img[alt*='验证码']",
|
|
"#captcha-image",
|
|
],
|
|
}
|
|
|
|
# 登录成功检测
|
|
LOGIN_SUCCESS_SELECTORS = [
|
|
".user-info",
|
|
".welcome",
|
|
"[data-testid='user-avatar']",
|
|
"a[href*='logout']",
|
|
".dashboard",
|
|
]
|
|
|
|
|
|
async def login_if_needed(
|
|
page: Any,
|
|
username: str,
|
|
password: str,
|
|
wait_sec: int = 120,
|
|
) -> bool:
|
|
"""检查登录状态,如需要则执行登录。
|
|
|
|
Args:
|
|
page: Playwright页面对象
|
|
username: 用户名
|
|
password: 密码
|
|
wait_sec: 等待人工验证的超时时间
|
|
|
|
Returns:
|
|
bool: 登录是否成功
|
|
"""
|
|
# 检查是否已登录
|
|
if await _is_logged_in(page):
|
|
logger.info("already_logged_in url=%s", page.url)
|
|
return True
|
|
|
|
# 导航到登录页面
|
|
if "/login" not in page.url:
|
|
await page.goto("https://sandbox.jc2009.com/login", wait_until="domcontentloaded")
|
|
await interruptible_sleep(2.0)
|
|
|
|
# 尝试自动登录
|
|
login_success = await _try_auto_login(page, username, password)
|
|
if login_success:
|
|
return True
|
|
|
|
# 等待人工登录
|
|
return await _wait_for_human_login(page, wait_sec)
|
|
|
|
|
|
async def _try_auto_login(page: Any, username: str, password: str) -> bool:
|
|
"""尝试自动登录。"""
|
|
try:
|
|
# 查找用户名输入框
|
|
username_input = await _find_element(page, LOGIN_SELECTORS["username_input"])
|
|
if not username_input:
|
|
logger.warning("username_input_not_found")
|
|
return False
|
|
|
|
# 查找密码输入框
|
|
password_input = await _find_element(page, LOGIN_SELECTORS["password_input"])
|
|
if not password_input:
|
|
logger.warning("password_input_not_found")
|
|
return False
|
|
|
|
# 输入用户名(模拟人类输入)
|
|
await username_input.click()
|
|
await interruptible_sleep(0.5)
|
|
await username_input.fill("")
|
|
await page.keyboard.type(username, delay=100)
|
|
await interruptible_sleep(0.5)
|
|
|
|
# 输入密码
|
|
await password_input.click()
|
|
await interruptible_sleep(0.5)
|
|
await password_input.fill("")
|
|
await page.keyboard.type(password, delay=100)
|
|
await interruptible_sleep(0.5)
|
|
|
|
# 查找并点击登录按钮
|
|
login_button = await _find_element(page, LOGIN_SELECTORS["login_button"])
|
|
if not login_button:
|
|
logger.warning("login_button_not_found")
|
|
return False
|
|
|
|
await login_button.click()
|
|
await interruptible_sleep(3.0)
|
|
|
|
# 检查是否登录成功
|
|
return await _is_logged_in(page)
|
|
|
|
except Exception as e:
|
|
logger.error("auto_login_failed error=%s", str(e))
|
|
return False
|
|
|
|
|
|
async def _wait_for_human_login(page: Any, wait_sec: int) -> bool:
|
|
"""等待人工完成登录。"""
|
|
print(f"请在浏览器中完成登录,最多等待 {wait_sec} 秒...")
|
|
deadline = time.monotonic() + wait_sec
|
|
|
|
while time.monotonic() < deadline:
|
|
if await _is_logged_in(page):
|
|
print("检测到登录成功")
|
|
return True
|
|
|
|
# 检查是否有验证码需要处理
|
|
captcha_present = await _check_captcha(page)
|
|
if captcha_present:
|
|
print("检测到验证码,请人工完成验证...")
|
|
|
|
await interruptible_sleep(2.0)
|
|
|
|
print("登录超时")
|
|
return False
|
|
|
|
|
|
async def _is_logged_in(page: Any) -> bool:
|
|
"""检查是否已登录。"""
|
|
try:
|
|
# 检查URL是否包含登录成功标识
|
|
if "/login" not in page.url and "/index" in page.url:
|
|
return True
|
|
|
|
# 检查页面元素
|
|
for selector in LOGIN_SUCCESS_SELECTORS:
|
|
try:
|
|
element = page.locator(selector)
|
|
if await element.count() > 0 and await element.is_visible():
|
|
return True
|
|
except Exception:
|
|
continue
|
|
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
async def _check_captcha(page: Any) -> bool:
|
|
"""检查是否有验证码。"""
|
|
try:
|
|
for selector in LOGIN_SELECTORS["captcha_input"]:
|
|
element = page.locator(selector)
|
|
if await element.count() > 0 and await element.is_visible():
|
|
return True
|
|
|
|
for selector in LOGIN_SELECTORS["captcha_image"]:
|
|
element = page.locator(selector)
|
|
if await element.count() > 0 and await element.is_visible():
|
|
return True
|
|
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
async def _find_element(page: Any, selectors: list) -> Optional[Any]:
|
|
"""根据选择器列表查找元素。"""
|
|
for selector in selectors:
|
|
try:
|
|
element = page.locator(selector)
|
|
if await element.count() > 0 and await element.is_visible():
|
|
return element
|
|
except Exception:
|
|
continue
|
|
return None |