From 1ba7a0f4a19b6f755d942b453ccde7a6d6384fc0 Mon Sep 17 00:00:00 2001 From: chendelian <116870791@qq.com> Date: Wed, 16 Sep 2026 15:40:50 +0800 Subject: [PATCH] feat: open browser with stable CDP port for scrape attach --- CHANGELOG.md | 4 ++++ SKILL.md | 2 +- scripts/service/browser_service.py | 32 ++++++++++++++++++++++++++---- tests/test_browser_open.py | 11 ++++++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c502288..e4b52de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ 本文件对应技能市场 **「更新日志」** Tab。 发布 CI 会按当前 tag 版本提取匹配的 `##` 小节。 +## 2.0.30 + +- 「打开浏览器」为本账号 Profile 附加稳定本机调试口(与科力普等采集技能约定同一端口算法),便于采集挂接已打开的原生窗口;仍使用 `--restore-last-session` 恢复登录态 + ## 2.0.29 - 「打开浏览器」有可恢复会话时改用 `--restore-last-session` 强制恢复标签与 Session Cookie;不再裸写受 Secure Preferences HMAC 保护的 `restore_on_startup`(该写法会被 Chrome 否掉并落到新标签页) diff --git a/SKILL.md b/SKILL.md index 9aa42c1..8ca2b29 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,7 +1,7 @@ --- name: 账号与凭据管理 description: "通用账号 & 凭据 & 登录流程管理器。管理网页/RPA 账号、API Key/Token;支持 9 种登录策略(账密自动、扫码、2FA、SSO 等);提供加密本地存储(Fernet);提供 rpa_helpers 库供业务 skill 同进程调用 ensure_logged_in 完成多平台自动登录。涵盖物流、LLM、内容、开发、API 五类平台。" -version: 2.0.29 +version: 2.0.30 author: 深圳匠厂科技有限公司 metadata: openclaw: diff --git a/scripts/service/browser_service.py b/scripts/service/browser_service.py index 5ee252b..81f0772 100644 --- a/scripts/service/browser_service.py +++ b/scripts/service/browser_service.py @@ -91,25 +91,46 @@ HUMAN_CHROME_FLAGS = ( #(该项受 Secure Preferences HMAC 保护,裸写会被 Chrome 否掉)。 RESTORE_LAST_SESSION_FLAG = "--restore-last-session" +# 与 scrape-product-colipu 等业务技能约定同一算法,便于 CDP 挂接已打开的账号窗口。 +_CDP_PORT_BASE = 9330 +_CDP_PORT_SPAN = 60 -def human_chrome_launch_args(profile_dir, url=None, restore_session=False): + +def cdp_port_for_profile(profile_dir: str) -> int: + """按 profile 绝对路径派生稳定本机调试端口(9330–9389)。""" + import hashlib + + key = os.path.abspath(profile_dir).replace("/", "\\").lower().encode("utf-8") + digest = hashlib.sha1(key).hexdigest() + return _CDP_PORT_BASE + (int(digest[:8], 16) % _CDP_PORT_SPAN) + + +def human_chrome_launch_args(profile_dir, url=None, restore_session=False, *, enable_cdp=True): """chrome.exe 之后的参数(Popen 列表形式,路径不额外加引号)。""" profile_abs = os.path.abspath(profile_dir) args = [f"--user-data-dir={profile_abs}", *HUMAN_CHROME_FLAGS] if restore_session: args.append(RESTORE_LAST_SESSION_FLAG) + if enable_cdp: + port = cdp_port_for_profile(profile_abs) + args.append(f"--remote-debugging-port={port}") + args.append("--remote-debugging-address=127.0.0.1") cleaned = (url or "").strip() if cleaned: args.append(cleaned) return args -def human_chrome_shortcut_arguments(profile_dir, url=None, restore_session=False): +def human_chrome_shortcut_arguments(profile_dir, url=None, restore_session=False, *, enable_cdp=True): """Windows .lnk Arguments 字符串(user-data-dir 带引号)。""" profile_abs = os.path.abspath(profile_dir) parts = [f'--user-data-dir="{profile_abs}"', *HUMAN_CHROME_FLAGS] if restore_session: parts.append(RESTORE_LAST_SESSION_FLAG) + if enable_cdp: + port = cdp_port_for_profile(profile_abs) + parts.append(f"--remote-debugging-port={port}") + parts.append("--remote-debugging-address=127.0.0.1") cleaned = (url or "").strip() if cleaned: parts.append(cleaned) @@ -329,21 +350,23 @@ def cmd_open(account_id): # 首次/空 Profile:仍打开平台首页便于登录。 has_session = profile_has_restorable_session(profile_abs) launch_url = None if has_session else url + cdp_port = cdp_port_for_profile(profile_abs) cmd = [ browser_exe, *human_chrome_launch_args( - profile_abs, launch_url, restore_session=has_session + profile_abs, launch_url, restore_session=has_session, enable_cdp=True ), ] log.info( "browser_open_launch exe=%s profile_dir=%s launch_url=%s has_session=%s " - "restore_last_session=%s platform=%s", + "restore_last_session=%s cdp_port=%s platform=%s", browser_exe, profile_abs, launch_url or "(restore)", has_session, has_session, + cdp_port, target.get("platform_key"), ) @@ -396,6 +419,7 @@ def cmd_open(account_id): "pid": child.pid, "has_session": has_session, "restore_last_session": has_session, + "cdp_port": cdp_port, "session_restore_on_startup": prefs_result.get("restore_after"), }, }, diff --git a/tests/test_browser_open.py b/tests/test_browser_open.py index 7d6ee4a..a7a950c 100644 --- a/tests/test_browser_open.py +++ b/tests/test_browser_open.py @@ -193,6 +193,7 @@ class TestHumanChromeLaunchArgs: for flag in HUMAN_CHROME_FLAGS: assert flag in args assert "--restore-last-session" not in args + assert any(a.startswith("--remote-debugging-port=") for a in args) assert args[-1] == "https://chat.deepseek.com" assert "--enable-automation" not in args @@ -201,6 +202,7 @@ class TestHumanChromeLaunchArgs: args = human_chrome_launch_args(str(tmp_path / "prof"), None, restore_session=True) assert "--restore-last-session" in args + assert any(a.startswith("--remote-debugging-port=") for a in args) assert not any(a.startswith("http") for a in args) def test_shortcut_arguments_quote_profile_and_include_url(self, tmp_path): @@ -213,6 +215,7 @@ class TestHumanChromeLaunchArgs: assert "--start-maximized" in text assert "--no-first-run" in text assert "--restore-last-session" not in text + assert "--remote-debugging-port=" in text assert text.endswith("https://example.com") def test_shortcut_restore_session(self, tmp_path): @@ -220,8 +223,16 @@ class TestHumanChromeLaunchArgs: text = human_chrome_shortcut_arguments(str(tmp_path / "p"), None, restore_session=True) assert "--restore-last-session" in text + assert "--remote-debugging-port=" in text assert "http" not in text + def test_cdp_port_stable_for_profile(self, tmp_path): + from service.browser_service import cdp_port_for_profile + + p = str(tmp_path / "prof") + assert cdp_port_for_profile(p) == cdp_port_for_profile(p) + assert 9330 <= cdp_port_for_profile(p) < 9390 + class TestResolveChromiumChannel: """resolve_chromium_channel returns a channel string when browsers exist."""