104 lines
3.3 KiB
Python
104 lines
3.3 KiB
Python
"""浏览器会话管理:基于skill-template的real_browser_rpa示例。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import os
|
||
import random
|
||
from typing import Any, Optional, Tuple
|
||
|
||
from jiangchang_skill_core import config
|
||
from jiangchang_skill_core.activity import interruptible_sleep
|
||
from jiangchang_skill_core.rpa import launch_persistent_browser, poll_until
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 目标网站配置
|
||
TARGET_URL = "https://sandbox.jc2009.com/industries/campus/smartcampus/app/finance/invoice-precheck"
|
||
LOGIN_URL = "https://sandbox.jc2009.com/login"
|
||
|
||
|
||
async def start_browser_session(
|
||
profile_dir: str,
|
||
headless: bool = False,
|
||
) -> Tuple[Any, Any, Any]:
|
||
"""启动浏览器会话。
|
||
|
||
Args:
|
||
profile_dir: 浏览器配置目录
|
||
headless: 是否无头模式(生产必须为False)
|
||
|
||
Returns:
|
||
(playwright, context, page) 三元组
|
||
|
||
Raises:
|
||
RuntimeError: 浏览器启动失败
|
||
"""
|
||
if not profile_dir:
|
||
raise RuntimeError("ERROR:MISSING_BROWSER 缺少浏览器配置目录")
|
||
|
||
# 确保有头模式(生产要求)
|
||
if headless:
|
||
logger.warning("生产环境强制使用有头模式")
|
||
headless = False
|
||
|
||
try:
|
||
# 启动持久化浏览器上下文
|
||
pw, context = await launch_persistent_browser(
|
||
user_data_dir=profile_dir,
|
||
headless=headless,
|
||
args=[
|
||
"--start-maximized",
|
||
"--disable-blink-features=AutomationControlled",
|
||
],
|
||
)
|
||
|
||
# 创建新页面
|
||
page = await context.new_page()
|
||
|
||
# 导航到目标页面
|
||
await page.goto(TARGET_URL, wait_until="domcontentloaded", timeout=60000)
|
||
|
||
logger.info("browser_started url=%s profile_dir=%s", page.url, profile_dir)
|
||
return pw, context, page
|
||
|
||
except Exception as e:
|
||
logger.error("browser_start_failed error=%s", str(e))
|
||
raise RuntimeError(f"ERROR:MISSING_BROWSER 浏览器启动失败: {str(e)}")
|
||
|
||
|
||
async def close_browser_context(pw: Any, context: Any) -> None:
|
||
"""安全关闭浏览器上下文。"""
|
||
try:
|
||
if context:
|
||
await context.close()
|
||
if pw:
|
||
await pw.stop()
|
||
except Exception as e:
|
||
logger.warning("browser_close_error error=%s", str(e))
|
||
|
||
|
||
async def wait_for_page_ready(page: Any, timeout_sec: float = 30.0) -> bool:
|
||
"""等待页面加载完成。"""
|
||
async def _check_ready():
|
||
try:
|
||
# 检查页面是否加载完成
|
||
if await page.evaluate("document.readyState") == "complete":
|
||
return True
|
||
# 检查是否有加载指示器
|
||
loading_selectors = [".loading", ".spinner", "[data-loading]"]
|
||
for sel in loading_selectors:
|
||
if await page.locator(sel).count() > 0:
|
||
return False
|
||
return True
|
||
except Exception:
|
||
return False
|
||
|
||
result = await poll_until(_check_ready, timeout_sec=timeout_sec, interval_sec=0.5)
|
||
return result.ok
|
||
|
||
|
||
async def random_delay(min_sec: float = 1.0, max_sec: float = 3.0) -> None:
|
||
"""随机延迟,模拟人类操作节奏。"""
|
||
delay = random.uniform(min_sec, max_sec)
|
||
await interruptible_sleep(delay) |