myagent-ai 1.30.1 → 1.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/aiskills/browser_stealth.py +203 -24
- package/aiskills/registry.py +3 -0
- package/aiskills/site-operations/SKILL.md +150 -0
- package/core/browser_profile.py +50 -53
- package/core/site_registry.py +773 -0
- package/main.py +41 -7
- package/package.json +1 -1
- package/scripts/cli.py +179 -2
|
@@ -165,7 +165,8 @@ class StealthBrowser:
|
|
|
165
165
|
logger.debug(f"反检测 JS 注入提示: {e}")
|
|
166
166
|
|
|
167
167
|
# 等待浏览器完全就绪
|
|
168
|
-
time.sleep
|
|
168
|
+
# Bug Fix: 使用 asyncio.sleep 而非 time.sleep,避免阻塞事件循环
|
|
169
|
+
await asyncio.sleep(1)
|
|
169
170
|
|
|
170
171
|
self._started = True
|
|
171
172
|
logger.info(
|
|
@@ -576,17 +577,45 @@ class StealthBrowser:
|
|
|
576
577
|
if found:
|
|
577
578
|
return found
|
|
578
579
|
|
|
579
|
-
# 3.
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
"/usr/bin/chromium-browser", "/usr/bin/chromium",
|
|
583
|
-
"/snap/bin/chromium",
|
|
584
|
-
):
|
|
585
|
-
if os.path.isfile(p) and os.access(p, os.X_OK):
|
|
586
|
-
return p
|
|
587
|
-
|
|
588
|
-
# 4. Puppeteer 缓存
|
|
580
|
+
# 3. 操作系统特定路径
|
|
581
|
+
import platform
|
|
582
|
+
system = platform.system()
|
|
589
583
|
home = os.path.expanduser("~")
|
|
584
|
+
|
|
585
|
+
if system == "Darwin":
|
|
586
|
+
# macOS 常见路径
|
|
587
|
+
for p in (
|
|
588
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
589
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
590
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
591
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
592
|
+
):
|
|
593
|
+
if os.path.isfile(p) and os.access(p, os.X_OK):
|
|
594
|
+
return p
|
|
595
|
+
elif system == "Windows":
|
|
596
|
+
# Windows 常见路径
|
|
597
|
+
for p in (
|
|
598
|
+
os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"), "Google", "Chrome", "Application", "chrome.exe"),
|
|
599
|
+
os.path.join(os.environ.get("PROGRAMFILES(X86)", "C:\\Program Files (x86)"), "Google", "Chrome", "Application", "chrome.exe"),
|
|
600
|
+
os.path.join(os.environ.get("LOCALAPPDATA", ""), "Google", "Chrome", "Application", "chrome.exe"),
|
|
601
|
+
os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"), "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
602
|
+
os.path.join(os.environ.get("LOCALAPPDATA", ""), "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
603
|
+
):
|
|
604
|
+
if os.path.isfile(p):
|
|
605
|
+
return p
|
|
606
|
+
else:
|
|
607
|
+
# Linux 常见路径
|
|
608
|
+
for p in (
|
|
609
|
+
"/usr/bin/google-chrome", "/usr/bin/google-chrome-stable",
|
|
610
|
+
"/usr/bin/chromium-browser", "/usr/bin/chromium",
|
|
611
|
+
"/snap/bin/chromium",
|
|
612
|
+
"/usr/bin/brave-browser",
|
|
613
|
+
"/usr/bin/microsoft-edge",
|
|
614
|
+
):
|
|
615
|
+
if os.path.isfile(p) and os.access(p, os.X_OK):
|
|
616
|
+
return p
|
|
617
|
+
|
|
618
|
+
# 4. Puppeteer / Playwright 缓存
|
|
590
619
|
for cache in (
|
|
591
620
|
os.path.join(home, ".cache", "puppeteer", "chrome"),
|
|
592
621
|
os.path.join(home, ".cache", "ms-playwright"),
|
|
@@ -621,8 +650,18 @@ def get_stealth_browser(
|
|
|
621
650
|
with _browser_lock:
|
|
622
651
|
if profile_name in _browsers:
|
|
623
652
|
browser = _browsers[profile_name]
|
|
653
|
+
# Bug Fix: 检测浏览器是否已崩溃,如果崩溃则清理并重建
|
|
624
654
|
if browser._started and browser._ensure_page():
|
|
625
655
|
return browser
|
|
656
|
+
else:
|
|
657
|
+
# 浏览器已崩溃或未启动,清理旧实例
|
|
658
|
+
logger.info(f"浏览器实例失效 (profile={profile_name}),正在重建...")
|
|
659
|
+
try:
|
|
660
|
+
if browser._browser:
|
|
661
|
+
browser._browser.quit()
|
|
662
|
+
except Exception:
|
|
663
|
+
pass
|
|
664
|
+
del _browsers[profile_name]
|
|
626
665
|
|
|
627
666
|
browser = StealthBrowser(profile_name=profile_name, headless=headless)
|
|
628
667
|
_browsers[profile_name] = browser
|
|
@@ -669,15 +708,15 @@ class StealthBrowserStartSkill(Skill):
|
|
|
669
708
|
name = "stealth_browser_start"
|
|
670
709
|
description = (
|
|
671
710
|
"启动反检测浏览器 — 使用 DrissionPage 启动带有反检测能力的 Chromium 浏览器,"
|
|
672
|
-
"
|
|
673
|
-
"
|
|
711
|
+
"适合需要登录的网站操作。每个网站使用独立 Profile,登录状态自动保持。"
|
|
712
|
+
"使用 site-manage list 查看所有可用网站。"
|
|
674
713
|
)
|
|
675
714
|
category = "browser_stealth"
|
|
676
715
|
parameters = [
|
|
677
716
|
SkillParameter(
|
|
678
717
|
name="profile",
|
|
679
718
|
type="string",
|
|
680
|
-
description="Profile
|
|
719
|
+
description="Profile 名称(使用 site-manage list 查看可用网站)",
|
|
681
720
|
required=False,
|
|
682
721
|
default="default",
|
|
683
722
|
),
|
|
@@ -692,7 +731,8 @@ class StealthBrowserStartSkill(Skill):
|
|
|
692
731
|
dangerous = False
|
|
693
732
|
|
|
694
733
|
async def execute(self, profile: str = "default", headless: bool = False, **kw) -> SkillResult:
|
|
695
|
-
|
|
734
|
+
# Bug Fix: get_stealth_browser 是同步函数,不能用 await
|
|
735
|
+
browser = get_stealth_browser(profile_name=profile, headless=headless)
|
|
696
736
|
return await browser.start()
|
|
697
737
|
|
|
698
738
|
|
|
@@ -708,7 +748,7 @@ class StealthBrowserNavigateSkill(Skill):
|
|
|
708
748
|
]
|
|
709
749
|
|
|
710
750
|
async def execute(self, url: str, profile: str = "default", **kw) -> SkillResult:
|
|
711
|
-
browser =
|
|
751
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
712
752
|
if not browser._started:
|
|
713
753
|
r = await browser.start()
|
|
714
754
|
if not r.success:
|
|
@@ -728,7 +768,7 @@ class StealthBrowserClickSkill(Skill):
|
|
|
728
768
|
]
|
|
729
769
|
|
|
730
770
|
async def execute(self, selector: str, profile: str = "default", **kw) -> SkillResult:
|
|
731
|
-
browser =
|
|
771
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
732
772
|
return await browser.click(selector)
|
|
733
773
|
|
|
734
774
|
|
|
@@ -748,7 +788,7 @@ class StealthBrowserFillSkill(Skill):
|
|
|
748
788
|
async def execute(
|
|
749
789
|
self, selector: str, value: str, clear: bool = True, profile: str = "default", **kw
|
|
750
790
|
) -> SkillResult:
|
|
751
|
-
browser =
|
|
791
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
752
792
|
return await browser.fill(selector, value, clear=clear)
|
|
753
793
|
|
|
754
794
|
|
|
@@ -764,7 +804,7 @@ class StealthBrowserScreenshotSkill(Skill):
|
|
|
764
804
|
]
|
|
765
805
|
|
|
766
806
|
async def execute(self, save_path: str = "", profile: str = "default", **kw) -> SkillResult:
|
|
767
|
-
browser =
|
|
807
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
768
808
|
return await browser.screenshot(save_path=save_path)
|
|
769
809
|
|
|
770
810
|
|
|
@@ -780,7 +820,7 @@ class StealthBrowserEvalSkill(Skill):
|
|
|
780
820
|
]
|
|
781
821
|
|
|
782
822
|
async def execute(self, script: str, profile: str = "default", **kw) -> SkillResult:
|
|
783
|
-
browser =
|
|
823
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
784
824
|
return await browser.evaluate(script)
|
|
785
825
|
|
|
786
826
|
|
|
@@ -795,7 +835,7 @@ class StealthBrowserGetContentSkill(Skill):
|
|
|
795
835
|
]
|
|
796
836
|
|
|
797
837
|
async def execute(self, profile: str = "default", **kw) -> SkillResult:
|
|
798
|
-
browser =
|
|
838
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
799
839
|
return await browser.get_content()
|
|
800
840
|
|
|
801
841
|
|
|
@@ -821,7 +861,7 @@ class StealthBrowserCookieSkill(Skill):
|
|
|
821
861
|
]
|
|
822
862
|
|
|
823
863
|
async def execute(self, action: str, profile: str = "default", **kw) -> SkillResult:
|
|
824
|
-
browser =
|
|
864
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
825
865
|
if action == "save":
|
|
826
866
|
return await browser.save_cookies()
|
|
827
867
|
elif action == "load":
|
|
@@ -881,7 +921,7 @@ class StealthBrowserWaitManualSkill(Skill):
|
|
|
881
921
|
async def execute(
|
|
882
922
|
self, reason: str = "验证码", timeout: int = 300, profile: str = "default", **kw
|
|
883
923
|
) -> SkillResult:
|
|
884
|
-
browser =
|
|
924
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
885
925
|
return await browser.wait_for_manual(reason=reason, timeout=float(timeout))
|
|
886
926
|
|
|
887
927
|
|
|
@@ -900,10 +940,149 @@ class StealthBrowserWaitSkill(Skill):
|
|
|
900
940
|
async def execute(
|
|
901
941
|
self, selector: str, timeout: int = 10, profile: str = "default", **kw
|
|
902
942
|
) -> SkillResult:
|
|
903
|
-
browser =
|
|
943
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
904
944
|
return await browser.wait_for(selector, timeout=float(timeout))
|
|
905
945
|
|
|
906
946
|
|
|
947
|
+
# ── Site Registry 管理技能 (v1.31.0) ────────────────────
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
class SiteManageListSkill(Skill):
|
|
951
|
+
"""列出所有已注册网站"""
|
|
952
|
+
|
|
953
|
+
name = "site_manage_list"
|
|
954
|
+
description = "列出所有已注册网站 — 显示网站名称、分类、登录 URL 和 Profile 状态"
|
|
955
|
+
category = "browser_stealth"
|
|
956
|
+
parameters = [
|
|
957
|
+
SkillParameter(
|
|
958
|
+
name="category",
|
|
959
|
+
type="string",
|
|
960
|
+
description="按分类筛选(空=全部)",
|
|
961
|
+
required=False,
|
|
962
|
+
default="",
|
|
963
|
+
),
|
|
964
|
+
]
|
|
965
|
+
|
|
966
|
+
async def execute(self, category: str = "", **kw) -> SkillResult:
|
|
967
|
+
from core.site_registry import get_site_registry
|
|
968
|
+
reg = get_site_registry()
|
|
969
|
+
sites = reg.list_sites(category=category)
|
|
970
|
+
summary = reg.to_summary()
|
|
971
|
+
|
|
972
|
+
return SkillResult(
|
|
973
|
+
success=True,
|
|
974
|
+
message=f"共 {len(sites)} 个网站" + (f" (分类: {category})" if category else ""),
|
|
975
|
+
data={"sites": sites, "summary": {k: v for k, v in summary.items() if k != "sites"}},
|
|
976
|
+
output=json.dumps(sites, ensure_ascii=False, indent=2),
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
class SiteManageShowSkill(Skill):
|
|
981
|
+
"""显示网站详细配置"""
|
|
982
|
+
|
|
983
|
+
name = "site_manage_show"
|
|
984
|
+
description = "显示网站详细配置 — 查看网站的登录 URL、可用页面和操作提示"
|
|
985
|
+
category = "browser_stealth"
|
|
986
|
+
parameters = [
|
|
987
|
+
SkillParameter(name="name", type="string", description="网站名称", required=True),
|
|
988
|
+
]
|
|
989
|
+
|
|
990
|
+
async def execute(self, name: str, **kw) -> SkillResult:
|
|
991
|
+
from core.site_registry import get_site_registry
|
|
992
|
+
reg = get_site_registry()
|
|
993
|
+
site = reg.get_site(name)
|
|
994
|
+
if not site:
|
|
995
|
+
return SkillResult(success=False, error=f"网站不存在: {name}")
|
|
996
|
+
|
|
997
|
+
return SkillResult(
|
|
998
|
+
success=True,
|
|
999
|
+
message=f"网站: {site.get('display_name', name)}",
|
|
1000
|
+
data=site,
|
|
1001
|
+
output=json.dumps(site, ensure_ascii=False, indent=2),
|
|
1002
|
+
)
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
class SiteManageAddSkill(Skill):
|
|
1006
|
+
"""添加自定义网站"""
|
|
1007
|
+
|
|
1008
|
+
name = "site_manage_add"
|
|
1009
|
+
description = (
|
|
1010
|
+
"添加自定义网站 — 为新网站创建配置,后续可用 stealth-open 登录。"
|
|
1011
|
+
"需提供 name(英文标识)、login_url(登录页)和 display_name(显示名称)。"
|
|
1012
|
+
)
|
|
1013
|
+
category = "browser_stealth"
|
|
1014
|
+
parameters = [
|
|
1015
|
+
SkillParameter(name="name", type="string", description="网站名称(英文,如 my_site)", required=True),
|
|
1016
|
+
SkillParameter(name="display_name", type="string", description="显示名称", required=False, default=""),
|
|
1017
|
+
SkillParameter(name="login_url", type="string", description="登录页 URL", required=False, default=""),
|
|
1018
|
+
SkillParameter(name="home_url", type="string", description="首页 URL", required=False, default=""),
|
|
1019
|
+
SkillParameter(name="category", type="string", description="分类", required=False, default="custom"),
|
|
1020
|
+
]
|
|
1021
|
+
|
|
1022
|
+
async def execute(
|
|
1023
|
+
self, name: str, display_name: str = "", login_url: str = "",
|
|
1024
|
+
home_url: str = "", category: str = "custom", **kw
|
|
1025
|
+
) -> SkillResult:
|
|
1026
|
+
from core.site_registry import get_site_registry
|
|
1027
|
+
reg = get_site_registry()
|
|
1028
|
+
try:
|
|
1029
|
+
site = reg.add_site({
|
|
1030
|
+
"name": name,
|
|
1031
|
+
"display_name": display_name or name,
|
|
1032
|
+
"login_url": login_url,
|
|
1033
|
+
"home_url": home_url,
|
|
1034
|
+
"category": category,
|
|
1035
|
+
})
|
|
1036
|
+
return SkillResult(
|
|
1037
|
+
success=True,
|
|
1038
|
+
message=f"已添加网站: {name}",
|
|
1039
|
+
data=site,
|
|
1040
|
+
)
|
|
1041
|
+
except ValueError as e:
|
|
1042
|
+
return SkillResult(success=False, error=str(e))
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
class SiteManageRemoveSkill(Skill):
|
|
1046
|
+
"""删除自定义网站"""
|
|
1047
|
+
|
|
1048
|
+
name = "site_manage_remove"
|
|
1049
|
+
description = "删除自定义网站(内置网站不可删除)"
|
|
1050
|
+
category = "browser_stealth"
|
|
1051
|
+
parameters = [
|
|
1052
|
+
SkillParameter(name="name", type="string", description="网站名称", required=True),
|
|
1053
|
+
]
|
|
1054
|
+
|
|
1055
|
+
async def execute(self, name: str, **kw) -> SkillResult:
|
|
1056
|
+
from core.site_registry import get_site_registry
|
|
1057
|
+
reg = get_site_registry()
|
|
1058
|
+
ok = reg.remove_site(name)
|
|
1059
|
+
if ok:
|
|
1060
|
+
return SkillResult(success=True, message=f"已删除网站: {name}")
|
|
1061
|
+
return SkillResult(
|
|
1062
|
+
success=False,
|
|
1063
|
+
error=f"删除失败: {name}(内置网站不可删除,或网站不存在)",
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
class SiteManageInitSkill(Skill):
|
|
1068
|
+
"""批量初始化网站 Profile"""
|
|
1069
|
+
|
|
1070
|
+
name = "site_manage_init"
|
|
1071
|
+
description = "批量初始化所有网站的浏览器 Profile(仅创建目录,不自动登录)"
|
|
1072
|
+
category = "browser_stealth"
|
|
1073
|
+
parameters = []
|
|
1074
|
+
|
|
1075
|
+
async def execute(self, **kw) -> SkillResult:
|
|
1076
|
+
from core.site_registry import get_site_registry
|
|
1077
|
+
reg = get_site_registry()
|
|
1078
|
+
initialized = reg.init_profiles()
|
|
1079
|
+
return SkillResult(
|
|
1080
|
+
success=True,
|
|
1081
|
+
message=f"已初始化 {len(initialized)} 个网站的 Profile",
|
|
1082
|
+
data={"initialized": initialized, "count": len(initialized)},
|
|
1083
|
+
)
|
|
1084
|
+
|
|
1085
|
+
|
|
907
1086
|
# ── Profile 管理技能 ──────────────────────────────────────
|
|
908
1087
|
|
|
909
1088
|
|
package/aiskills/registry.py
CHANGED
|
@@ -206,6 +206,9 @@ CLI_COMMANDS: List[Dict[str, Any]] = [
|
|
|
206
206
|
"description": "创建浏览器 Profile"},
|
|
207
207
|
{"name": "profile-delete", "category": "stealth", "cli": "myagent-ai profile-delete <name>",
|
|
208
208
|
"description": "删除浏览器 Profile"},
|
|
209
|
+
# ── Site 管理 (v1.31.0) ──
|
|
210
|
+
{"name": "site-manage", "category": "stealth", "cli": "myagent-ai site-manage <list|show|add|remove|init> [args]",
|
|
211
|
+
"description": "网站注册管理 — 列出/查看/添加/删除/初始化网站配置(支持 20+ 内置网站和动态扩展)"},
|
|
209
212
|
# ── GUI 桌面 ──
|
|
210
213
|
{"name": "screenshot", "category": "gui", "cli": "myagent-ai screenshot [region] [-m monitor]",
|
|
211
214
|
"description": "屏幕截图(仅 Windows/macOS)"},
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: site-operations
|
|
3
|
+
description: |
|
|
4
|
+
统一网站操作指南 — 使用反检测浏览器(DrissionPage)登录和操作任何网站。
|
|
5
|
+
覆盖邮箱、社交媒体、云服务、AI 对话平台等 20+ 网站,支持动态扩展。
|
|
6
|
+
metadata:
|
|
7
|
+
category: site_operation
|
|
8
|
+
version: "1.31.0"
|
|
9
|
+
requires: ["stealth-browser"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# 统一网站操作指南
|
|
13
|
+
|
|
14
|
+
## 概述
|
|
15
|
+
|
|
16
|
+
本指南使用反检测浏览器(stealth browser)登录和操作各种网站。
|
|
17
|
+
所有网站共享同一套命令接口(stealth-*),只是 Profile 名称和 URL 不同。
|
|
18
|
+
|
|
19
|
+
## 支持的网站
|
|
20
|
+
|
|
21
|
+
使用 `myagent-ai site-manage list` 查看所有可用网站。
|
|
22
|
+
|
|
23
|
+
### 分类一览
|
|
24
|
+
|
|
25
|
+
| 分类 | 网站 | Profile 名称 |
|
|
26
|
+
|------|------|-------------|
|
|
27
|
+
| 邮箱 | Gmail, QQ邮箱, 139邮箱 | gmail, qq_mail, mail139 |
|
|
28
|
+
| 社交 | X.com, 微博, 微信网页版, 微信公众号 | x_com, weibo, wechat_web, wechat_mp |
|
|
29
|
+
| 云服务 | 百度网盘 | baidu_netdisk |
|
|
30
|
+
| AI 对话 | Gemini, DeepSeek, z.ai, 智谱清言, 豆包, Kimi, 腾讯元宝, 通义千问 | gemini, deepseek, z_ai, zhipu, doubao, kimi, tencent_yuanbao, qianwen |
|
|
31
|
+
| 开发 | GitHub, Google 账号 | github, google |
|
|
32
|
+
| 内容 | 抖音, 哔哩哔哩 | douyin, bilibili |
|
|
33
|
+
|
|
34
|
+
## 通用操作流程
|
|
35
|
+
|
|
36
|
+
### 1. 首次登录(通用模板)
|
|
37
|
+
|
|
38
|
+
任何网站的首次登录流程都遵循以下模板:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 第1步:查看网站配置
|
|
42
|
+
myagent-ai site-manage show <profile_name>
|
|
43
|
+
|
|
44
|
+
# 第2步:启动浏览器(有头模式,方便观察和手动操作)
|
|
45
|
+
myagent-ai stealth-open <profile_name> --no-headless
|
|
46
|
+
|
|
47
|
+
# 第3步:导航到登录页
|
|
48
|
+
myagent-ai stealth-navigate <login_url> -p <profile_name>
|
|
49
|
+
|
|
50
|
+
# 第4步:尝试自动填写(如果页面允许)
|
|
51
|
+
myagent-ai stealth-screenshot -p <profile_name>
|
|
52
|
+
myagent-ai stealth-fill '<用户名选择器>' '用户名' -p <profile_name>
|
|
53
|
+
myagent-ai stealth-click '<下一步按钮选择器>' -p <profile_name>
|
|
54
|
+
myagent-ai stealth-wait '<密码输入框选择器>' -p <profile_name>
|
|
55
|
+
myagent-ai stealth-fill '<密码选择器>' '密码' -p <profile_name>
|
|
56
|
+
myagent-ai stealth-click '<登录按钮选择器>' -p <profile_name>
|
|
57
|
+
|
|
58
|
+
# 第5步:如果出现验证码/2FA,等待用户手动操作
|
|
59
|
+
myagent-ai stealth-wait-manual "验证码或安全验证" -t 300 -p <profile_name>
|
|
60
|
+
|
|
61
|
+
# 第6步:确认登录成功并保存 Cookie
|
|
62
|
+
myagent-ai stealth-cookies save -p <profile_name>
|
|
63
|
+
myagent-ai stealth-close <profile_name>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. 后续操作(已登录)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 启动浏览器(自动恢复登录状态)
|
|
70
|
+
myagent-ai stealth-open <profile_name> --no-headless
|
|
71
|
+
|
|
72
|
+
# 导航到目标页面
|
|
73
|
+
myagent-ai stealth-navigate <目标URL> -p <profile_name>
|
|
74
|
+
|
|
75
|
+
# 等待页面加载
|
|
76
|
+
myagent-ai stealth-wait '<关键元素选择器>' -t 15 -p <profile_name>
|
|
77
|
+
|
|
78
|
+
# 截图确认
|
|
79
|
+
myagent-ai stealth-screenshot -p <profile_name>
|
|
80
|
+
|
|
81
|
+
# 获取页面内容
|
|
82
|
+
myagent-ai stealth-content -p <profile_name>
|
|
83
|
+
|
|
84
|
+
# 点击操作
|
|
85
|
+
myagent-ai stealth-click '<选择器>' -p <profile_name>
|
|
86
|
+
|
|
87
|
+
# 填写输入
|
|
88
|
+
myagent-ai stealth-fill '<选择器>' '内容' -p <profile_name>
|
|
89
|
+
|
|
90
|
+
# 执行 JavaScript(高级操作)
|
|
91
|
+
myagent-ai stealth-eval 'document.title' -p <profile_name>
|
|
92
|
+
|
|
93
|
+
# 完成后关闭
|
|
94
|
+
myagent-ai stealth-close <profile_name>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 查看网站详情
|
|
98
|
+
|
|
99
|
+
使用 `site-manage show` 查看具体网站的登录 URL、可用页面和操作提示:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# 查看 Gmail 的配置
|
|
103
|
+
myagent-ai site-manage show gmail
|
|
104
|
+
|
|
105
|
+
# 查看 DeepSeek 的配置
|
|
106
|
+
myagent-ai site-manage show deepseek
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 动态管理网站
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 列出所有网站
|
|
113
|
+
myagent-ai site-manage list
|
|
114
|
+
|
|
115
|
+
# 按分类查看
|
|
116
|
+
myagent-ai site-manage list --category ai_chat
|
|
117
|
+
|
|
118
|
+
# 添加自定义网站
|
|
119
|
+
myagent-ai site-manage add my_site --display "我的网站" --login-url "https://example.com/login" --home-url "https://example.com/dashboard"
|
|
120
|
+
|
|
121
|
+
# 更新网站配置
|
|
122
|
+
myagent-ai site-manage update my_site --display "新名称"
|
|
123
|
+
|
|
124
|
+
# 删除自定义网站(内置网站不可删除)
|
|
125
|
+
myagent-ai site-manage remove my_site
|
|
126
|
+
|
|
127
|
+
# 批量初始化所有网站的 Profile
|
|
128
|
+
myagent-ai site-manage init
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 选择器技巧
|
|
132
|
+
|
|
133
|
+
- CSS 选择器: `input[type="email"]`, `button.submit`, `div#main`
|
|
134
|
+
- 文本匹配: `button:contains("登录")`, `a:contains("下一步")`
|
|
135
|
+
- 属性匹配: `input[name="password"]`, `[data-testid="login-btn"]`
|
|
136
|
+
- XPath: `//div[@class="login-form"]//input`
|
|
137
|
+
|
|
138
|
+
不确定选择器时,先用 `stealth-screenshot` 截图查看页面结构,再用 `stealth-eval` 测试选择器:
|
|
139
|
+
```bash
|
|
140
|
+
myagent-ai stealth-eval "document.querySelectorAll('button').forEach((b,i) => console.log(i, b.textContent))" -p <profile>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 重要提示
|
|
144
|
+
|
|
145
|
+
1. **反检测模式**:stealth browser 使用 DrissionPage,自动隐藏 webdriver 标志
|
|
146
|
+
2. **Profile 隔离**:每个网站独立 Profile,Cookie/Session 互不干扰
|
|
147
|
+
3. **验证码处理**:遇到验证码时使用 `stealth-wait-manual`,通过 VNC/Web 手动处理
|
|
148
|
+
4. **操作间隔**:建议操作间等待 2-3 秒,避免被网站检测为自动化
|
|
149
|
+
5. **Cookie 持久化**:登录后执行 `stealth-cookies save`,下次自动恢复
|
|
150
|
+
6. **有头模式**:登录场景建议用 `--no-headless`,方便观察和手动干预
|
package/core/browser_profile.py
CHANGED
|
@@ -41,54 +41,48 @@ from core.logger import get_logger
|
|
|
41
41
|
|
|
42
42
|
logger = get_logger("myagent.core.browser_profile")
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"github": {
|
|
87
|
-
"display_name": "GitHub",
|
|
88
|
-
"login_url": "https://github.com/login",
|
|
89
|
-
"detect_url": "https://github.com/",
|
|
90
|
-
},
|
|
91
|
-
}
|
|
44
|
+
|
|
45
|
+
# ════════════════════════════════════════════════════════════
|
|
46
|
+
# 预置 Profile(兼容性保留 — 优先从 site_registry 加载)
|
|
47
|
+
# ════════════════════════════════════════════════════════════
|
|
48
|
+
# v1.31.0: PRESET_PROFILES 不再硬编码,改为从 core.site_registry 动态获取。
|
|
49
|
+
# 此处保留为空 dict,由 get_preset_profiles() 延迟加载。
|
|
50
|
+
|
|
51
|
+
_PRESET_PROFILES_CACHE: Optional[Dict[str, Dict[str, str]]] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_preset_profiles() -> Dict[str, Dict[str, str]]:
|
|
55
|
+
"""获取预置 Profile 配置(从 SiteRegistry 延迟加载)。
|
|
56
|
+
|
|
57
|
+
返回 {name: {display_name, login_url, detect_url}} 格式,
|
|
58
|
+
保持与旧 PRESET_PROFILES 完全兼容。
|
|
59
|
+
"""
|
|
60
|
+
global _PRESET_PROFILES_CACHE
|
|
61
|
+
if _PRESET_PROFILES_CACHE is not None:
|
|
62
|
+
return _PRESET_PROFILES_CACHE
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
from core.site_registry import get_site_registry
|
|
66
|
+
reg = get_site_registry()
|
|
67
|
+
sites = reg.list_sites()
|
|
68
|
+
_PRESET_PROFILES_CACHE = {}
|
|
69
|
+
for s in sites:
|
|
70
|
+
_PRESET_PROFILES_CACHE[s["name"]] = {
|
|
71
|
+
"display_name": s["display_name"],
|
|
72
|
+
"login_url": s.get("login_url", ""),
|
|
73
|
+
"detect_url": s.get("detect_url", s.get("home_url", "")),
|
|
74
|
+
}
|
|
75
|
+
logger.debug(f"从 SiteRegistry 加载了 {len(_PRESET_PROFILES_CACHE)} 个预置 Profile")
|
|
76
|
+
return _PRESET_PROFILES_CACHE
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.warning(f"从 SiteRegistry 加载失败,使用空预置: {e}")
|
|
79
|
+
_PRESET_PROFILES_CACHE = {}
|
|
80
|
+
return _PRESET_PROFILES_CACHE
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# 向后兼容别名
|
|
84
|
+
PRESET_PROFILES = {} # 由 get_preset_profiles() 延迟填充,直接引用会得到空 dict
|
|
85
|
+
# 注意:BrowserProfile.__init__ 已改用 get_preset_profiles(),不再直接读 PRESET_PROFILES
|
|
92
86
|
|
|
93
87
|
|
|
94
88
|
class BrowserProfile:
|
|
@@ -112,8 +106,9 @@ class BrowserProfile:
|
|
|
112
106
|
self.cookies_file = self.profile_dir / "cookies.json"
|
|
113
107
|
self.config_file = self.profile_dir / "config.json"
|
|
114
108
|
|
|
115
|
-
#
|
|
116
|
-
|
|
109
|
+
# 预置配置(从 SiteRegistry 延迟加载)
|
|
110
|
+
_presets = get_preset_profiles()
|
|
111
|
+
preset = _presets.get(name, {})
|
|
117
112
|
self.display_name = (config or preset).get("display_name", name)
|
|
118
113
|
self.login_url = (config or preset).get("login_url", "")
|
|
119
114
|
self.detect_url = (config or preset).get("detect_url", "")
|
|
@@ -394,18 +389,20 @@ class BrowserProfileManager:
|
|
|
394
389
|
return profile.delete_profile()
|
|
395
390
|
|
|
396
391
|
def get_preset_names(self) -> List[str]:
|
|
397
|
-
"""获取所有预置 Profile
|
|
398
|
-
return list(
|
|
392
|
+
"""获取所有预置 Profile 名称列表(从 SiteRegistry 动态获取)"""
|
|
393
|
+
return list(get_preset_profiles().keys())
|
|
399
394
|
|
|
400
395
|
def init_presets(self) -> List[str]:
|
|
401
396
|
"""
|
|
402
397
|
初始化所有预置 Profile(仅创建目录结构,不自动登录)。
|
|
398
|
+
预置列表从 SiteRegistry 动态获取。
|
|
403
399
|
|
|
404
400
|
Returns:
|
|
405
401
|
已初始化的 Profile 名称列表
|
|
406
402
|
"""
|
|
403
|
+
_presets = get_preset_profiles()
|
|
407
404
|
initialized = []
|
|
408
|
-
for name, config in
|
|
405
|
+
for name, config in _presets.items():
|
|
409
406
|
profile = self.get_profile(name)
|
|
410
407
|
if not profile.is_initialized():
|
|
411
408
|
profile.ensure_dirs()
|