🔄 卡若AI 同步 2026-03-20 21:45 | 更新:运营中枢工作台 | 排除 >20MB: 11 个
This commit is contained in:
@@ -404,3 +404,4 @@
|
||||
| 2026-03-20 12:22:19 | 🔄 卡若AI 同步 2026-03-20 12:22 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-20 13:39:57 | 🔄 卡若AI 同步 2026-03-20 12:40 | 更新:水桥平台对接 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-20 16:08:50 | 🔄 卡若AI 同步 2026-03-20 16:08 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
| 2026-03-20 21:38:30 | 🔄 卡若AI 同步 2026-03-20 21:38 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 |
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
"""
|
||||
S2 私域管理后台:按路由批量全页截图(Playwright)
|
||||
|
||||
用法(需已登录态,任选其一):
|
||||
1) 先在本机 Chrome 登录后台,再用「用户数据目录」启动浏览器:
|
||||
用法(登录态,任选其一):
|
||||
0) **账号密码自动登录(推荐)**:环境变量(勿提交仓库)
|
||||
export S2_ADMIN_USER='你的账号'
|
||||
export S2_ADMIN_PASS='你的密码'
|
||||
python3 s2_admin_fullpage_capture.py --wait-ms 2500
|
||||
登录成功后才进入路由截图;失败会抛错并中止。
|
||||
|
||||
1) 本机 Chrome 已登录 +「用户数据目录」:
|
||||
python3 s2_admin_fullpage_capture.py --user-data-dir "$HOME/Library/Application Support/Google/Chrome" --channel chromium --headless
|
||||
(无 Google Chrome.app 时用 channel chromium;有则用 --channel chrome。建议先关 Chrome 避免配置锁。)
|
||||
|
||||
2) 或使用已导出的 storage_state.json:
|
||||
playwright install chromium
|
||||
2) 已导出的 storage_state.json:
|
||||
python3 s2_admin_fullpage_capture.py --storage-state /path/to/state.json
|
||||
|
||||
默认输出:卡若Ai 报告目录下 screenshots/fullpage_playwright/
|
||||
@@ -17,6 +22,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
@@ -61,6 +67,38 @@ def parse_routes_from_app_js(text: str) -> list[str]:
|
||||
return uniq
|
||||
|
||||
|
||||
async def login_if_needed(page, username: str, password: str, timeout_ms: int = 120000) -> None:
|
||||
"""若未登录则在 #/login 填写账号密码;已登录则跳过。"""
|
||||
await page.goto(f"{ADMIN}/home", wait_until="domcontentloaded", timeout=timeout_ms)
|
||||
await page.wait_for_timeout(2000)
|
||||
acct = page.get_by_placeholder("账号")
|
||||
if await acct.count() == 0 or not await acct.first.is_visible():
|
||||
await page.goto(f"{ADMIN}/login", wait_until="domcontentloaded", timeout=timeout_ms)
|
||||
await page.wait_for_timeout(1500)
|
||||
acct = page.get_by_placeholder("账号")
|
||||
if await acct.count() == 0 or not await acct.first.is_visible():
|
||||
return
|
||||
await acct.first.fill(username)
|
||||
await page.get_by_placeholder("密码").fill(password)
|
||||
await page.get_by_role("button", name="登录").click()
|
||||
for _ in range(120):
|
||||
await page.wait_for_timeout(500)
|
||||
h = await page.evaluate("() => location.hash || ''")
|
||||
if "/login" not in h:
|
||||
break
|
||||
else:
|
||||
raise RuntimeError("登录失败:超时仍停留在登录页")
|
||||
await page.wait_for_timeout(800)
|
||||
errs = page.locator(".el-message--error")
|
||||
if await errs.count() > 0:
|
||||
e0 = errs.first
|
||||
if await e0.is_visible():
|
||||
raise RuntimeError("登录失败:" + (await e0.inner_text()).strip())
|
||||
acct2 = page.get_by_placeholder("账号")
|
||||
if await acct2.count() > 0 and await acct2.first.is_visible():
|
||||
raise RuntimeError("登录失败:提交后仍显示登录表单,请检查账号密码")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--dest", type=Path, default=DEST_DEFAULT)
|
||||
@@ -79,8 +117,26 @@ async def main() -> None:
|
||||
action="store_true",
|
||||
help="不在每页加载后按 Escape(默认按一次以尝试关闭 v-modal 遮罩)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--login-user",
|
||||
default=None,
|
||||
help="登录账号(优先用环境变量 S2_ADMIN_USER,勿把密码写进 shell 历史可用 env 文件)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--login-password",
|
||||
default=None,
|
||||
help="登录密码(强烈建议仅用环境变量 S2_ADMIN_PASS)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--headed",
|
||||
action="store_true",
|
||||
help="有界面运行(调试用;默认无头)",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
login_user = (os.environ.get("S2_ADMIN_USER") or args.login_user or "").strip()
|
||||
login_pass = (os.environ.get("S2_ADMIN_PASS") or args.login_password or "").strip()
|
||||
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
async with httpx.AsyncClient(verify=False, timeout=60) as client:
|
||||
@@ -92,6 +148,8 @@ async def main() -> None:
|
||||
args.dest.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
async with async_playwright() as p:
|
||||
context = None
|
||||
browser = None
|
||||
if args.user_data_dir:
|
||||
browser = await p.chromium.launch_persistent_context(
|
||||
user_data_dir=str(args.user_data_dir),
|
||||
@@ -101,13 +159,20 @@ async def main() -> None:
|
||||
)
|
||||
page = browser.pages[0] if browser.pages else await browser.new_page()
|
||||
else:
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
browser = await p.chromium.launch(headless=not args.headed)
|
||||
context = await browser.new_context(
|
||||
viewport={"width": 1440, "height": 900},
|
||||
storage_state=str(args.storage_state) if args.storage_state else None,
|
||||
)
|
||||
page = await context.new_page()
|
||||
|
||||
if login_user and login_pass:
|
||||
print("登录中…")
|
||||
await login_if_needed(page, login_user, login_pass)
|
||||
print("登录成功,开始截图")
|
||||
elif not args.user_data_dir and not args.storage_state:
|
||||
print("警告:未提供 S2_ADMIN_USER/S2_ADMIN_PASS、storage-state 或 user-data-dir,可能截到未登录页")
|
||||
|
||||
for i, route in enumerate(routes, 1):
|
||||
url = f"{ADMIN}{route}"
|
||||
name = route.strip("/").replace("/", "__") or "home"
|
||||
@@ -126,6 +191,8 @@ async def main() -> None:
|
||||
if args.user_data_dir:
|
||||
await browser.close()
|
||||
else:
|
||||
if context:
|
||||
await context.close()
|
||||
await browser.close()
|
||||
|
||||
|
||||
|
||||
@@ -407,3 +407,4 @@
|
||||
| 2026-03-20 12:22:19 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 12:22 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-20 13:39:57 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 12:40 | 更新:水桥平台对接 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-20 16:08:50 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 16:08 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-20 21:38:30 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 21:38 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
|
||||
Reference in New Issue
Block a user