137 lines
4.2 KiB
Python
137 lines
4.2 KiB
Python
"""浏览器会话启动(系统 Chrome/Edge;URL 仅通过 page.goto 打开)。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import os
|
||
from contextlib import contextmanager
|
||
from typing import Any, Generator, List, Optional
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
DEFAULT_PAGE_TIMEOUT_MS = 15_000
|
||
GOTO_TIMEOUT_MS = 60_000
|
||
|
||
CHROME_LAUNCH_ARGS: List[str] = [
|
||
"--start-maximized",
|
||
"--disable-blink-features=AutomationControlled",
|
||
]
|
||
|
||
STEALTH_INIT_SCRIPT = """
|
||
(() => {
|
||
try { Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); } catch (e) {}
|
||
})();
|
||
"""
|
||
|
||
|
||
def chrome_launch_args() -> List[str]:
|
||
return list(CHROME_LAUNCH_ARGS)
|
||
|
||
|
||
def _headless_from_env() -> bool:
|
||
v = (os.getenv("OPENCLAW_BROWSER_HEADLESS") or "0").strip().lower()
|
||
return v in ("1", "true", "yes", "on")
|
||
|
||
|
||
def _find_chrome_executable() -> str | None:
|
||
try:
|
||
from jiangchang_skill_core.runtime_env import find_chrome_executable
|
||
|
||
chrome = find_chrome_executable()
|
||
if chrome and os.path.isfile(chrome):
|
||
return chrome
|
||
except ImportError:
|
||
pass
|
||
|
||
candidates = [
|
||
os.path.expandvars(r"%ProgramFiles%\Google\Chrome\Application\chrome.exe"),
|
||
os.path.expandvars(r"%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe"),
|
||
os.path.expandvars(r"%ProgramFiles%\Microsoft\Edge\Application\msedge.exe"),
|
||
os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft\Edge\Application\msedge.exe"),
|
||
]
|
||
for path in candidates:
|
||
if path and os.path.isfile(path):
|
||
return path
|
||
return None
|
||
|
||
|
||
def _clear_node_options() -> None:
|
||
os.environ.pop("NODE_OPTIONS", None)
|
||
|
||
|
||
@contextmanager
|
||
def browser_session(
|
||
start_url: str,
|
||
*,
|
||
profile_dir: Optional[str] = None,
|
||
headless: Optional[bool] = None,
|
||
) -> Generator[Any, None, None]:
|
||
"""
|
||
同步 Playwright 浏览器会话。
|
||
|
||
- Playwright Python 包由宿主共享 runtime 提供;技能侧不要 playwright install。
|
||
- launch args 仅放 Chrome 参数,不含 URL。
|
||
- 流程:launch → new_page → goto(start_url)
|
||
"""
|
||
from playwright.sync_api import sync_playwright
|
||
|
||
_clear_node_options()
|
||
hl = headless if headless is not None else _headless_from_env()
|
||
chrome = _find_chrome_executable()
|
||
if not chrome:
|
||
raise RuntimeError("未检测到 Chrome 或 Edge 浏览器,请安装系统浏览器后重试。")
|
||
|
||
args = chrome_launch_args()
|
||
logger.info(
|
||
"browser_launch chrome=%s profile_dir=%s start_url=%s args=%s headless=%s",
|
||
chrome,
|
||
profile_dir or "",
|
||
start_url,
|
||
args,
|
||
hl,
|
||
)
|
||
|
||
browser_holder = None
|
||
context = None
|
||
with sync_playwright() as pw:
|
||
try:
|
||
if profile_dir:
|
||
context = pw.chromium.launch_persistent_context(
|
||
user_data_dir=profile_dir,
|
||
headless=hl,
|
||
executable_path=chrome,
|
||
locale="zh-CN",
|
||
no_viewport=True,
|
||
args=args,
|
||
ignore_default_args=["--enable-automation"],
|
||
)
|
||
else:
|
||
browser_holder = pw.chromium.launch(
|
||
headless=hl,
|
||
executable_path=chrome,
|
||
args=args,
|
||
ignore_default_args=["--enable-automation"],
|
||
)
|
||
context = browser_holder.new_context(no_viewport=True, locale="zh-CN")
|
||
|
||
context.add_init_script(STEALTH_INIT_SCRIPT)
|
||
page = context.new_page()
|
||
page.set_default_timeout(DEFAULT_PAGE_TIMEOUT_MS)
|
||
page.goto(start_url, wait_until="domcontentloaded", timeout=GOTO_TIMEOUT_MS)
|
||
yield page
|
||
finally:
|
||
try:
|
||
if context is not None:
|
||
context.close()
|
||
except Exception as exc:
|
||
logger.warning("browser_context_close_failed: %s", exc)
|
||
if browser_holder is not None:
|
||
try:
|
||
browser_holder.close()
|
||
except Exception as exc:
|
||
logger.warning("browser_close_failed: %s", exc)
|
||
|
||
|
||
def find_chrome_executable() -> str | None:
|
||
return _find_chrome_executable()
|