🔄 卡若AI 同步 2026-03-20 21:45 | 更新:运营中枢工作台 | 排除 >20MB: 11 个

This commit is contained in:
2026-03-20 21:45:42 +08:00
parent a8e5dcce7e
commit 8f1e5b4c46
3 changed files with 74 additions and 5 deletions

View File

@@ -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 个 |

View File

@@ -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()

View File

@@ -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) |