chore: auto release commit (2026-07-03 12:09:55)
All checks were successful
技能自动化发布 / release (push) Successful in 5s
All checks were successful
技能自动化发布 / release (push) Successful in 5s
This commit is contained in:
136
scripts/service/browser_session.py
Normal file
136
scripts/service/browser_session.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""浏览器会话启动(系统 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()
|
||||
Reference in New Issue
Block a user