feat: add dedicated douyin workbench entry
This commit is contained in:
40
README.md
40
README.md
@@ -22,23 +22,45 @@ cd /Users/kris/code/StoryForge-gitea/android-app
|
||||
## Douyin Browser Capture
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea/scripts/douyin-browser-capture
|
||||
npm install
|
||||
npx playwright install chromium
|
||||
npm run control-panel
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
./scripts/start_douyin_workbench.sh
|
||||
```
|
||||
|
||||
打开:
|
||||
业务页:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:3618/workbench
|
||||
```
|
||||
|
||||
完整采集控制台:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:3618
|
||||
```
|
||||
|
||||
这个本地页面现在包含两部分:
|
||||
常用脚本:
|
||||
|
||||
- 上半部分是浏览器辅助采集控制台
|
||||
- 下半部分是 `Douyin Workbench`,可直接查看账号列表、商业化账号分析、快照详情、相似账号和对标关系
|
||||
- 作品工作台支持按 `高分作品 / 最新作品 / 全部作品` 切换,并可按综合分、商业价值、发布时间、播放、点赞、分享、评论排序
|
||||
```bash
|
||||
./scripts/start_douyin_workbench.sh
|
||||
./scripts/status_douyin_workbench.sh
|
||||
./scripts/stop_douyin_workbench.sh
|
||||
./scripts/cleanup_debug_ui.sh
|
||||
```
|
||||
|
||||
如果第一次使用,还需要先安装浏览器依赖:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea/scripts/douyin-browser-capture
|
||||
npm install
|
||||
npx playwright install chromium
|
||||
```
|
||||
|
||||
当前本地页面已经拆成两个入口:
|
||||
|
||||
- `/workbench`:业务优先的 `Douyin Workbench`,可直接查看账号列表、商业化账号分析、快照详情、相似账号和对标关系
|
||||
- `/`:完整浏览器辅助采集控制台,同时保留工作台能力
|
||||
- 作品工作台支持按 `高分作品 / 最新作品 / 全部作品` 切换,并可按综合分、受欢迎程度、商业价值、发布时间、播放、点赞、分享、评论排序
|
||||
- 作品列表支持 `视频 / 图文` 类型筛选,并可直接打开原作品链接
|
||||
- 高分作品支持自动化分析,每条作品卡片下都会展示商业判断、复刻计划、运营动作和风险提醒
|
||||
|
||||
或者继续用命令行:
|
||||
|
||||
@@ -342,7 +342,8 @@ function sendHtml(res, html) {
|
||||
res.end(html);
|
||||
}
|
||||
|
||||
function renderPage() {
|
||||
function renderPage(mode = "full") {
|
||||
const isWorkbenchMode = mode === "workbench";
|
||||
return `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
@@ -375,6 +376,36 @@ function renderPage() {
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
h1, h2, h3 { margin: 0; }
|
||||
.topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.topbar-links {
|
||||
display: inline-flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.nav-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 9px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(22, 49, 61, 0.1);
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav-link.active {
|
||||
background: rgba(31, 110, 95, 0.12);
|
||||
color: var(--accent);
|
||||
border-color: rgba(31, 110, 95, 0.24);
|
||||
}
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #0b3c5d, #1f6e5f 58%, #b97524);
|
||||
color: white;
|
||||
@@ -827,20 +858,31 @@ function renderPage() {
|
||||
background: rgba(22, 49, 61, 0.1);
|
||||
margin: 4px 0;
|
||||
}
|
||||
.business-mode .capture-grid,
|
||||
.business-mode .recent-runs-section,
|
||||
.business-mode .capture-footnote {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.grid, .row, .checks, .workbench-layout, .metric-grid, .two-col, .analysis-grid, .video-grid, .video-layout, .toolbar-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body class="${isWorkbenchMode ? "business-mode" : ""}">
|
||||
<main>
|
||||
<div class="topbar">
|
||||
<span class="pill">${isWorkbenchMode ? "StoryForge / Douyin Workbench" : "StoryForge / Douyin Browser Assist"}</span>
|
||||
<div class="topbar-links">
|
||||
<a class="nav-link ${isWorkbenchMode ? "active" : ""}" href="/workbench">业务工作台</a>
|
||||
<a class="nav-link ${isWorkbenchMode ? "" : "active"}" href="/">采集控制台</a>
|
||||
</div>
|
||||
</div>
|
||||
<section class="hero">
|
||||
<span class="pill">StoryForge / Douyin Browser Assist</span>
|
||||
<h1 style="margin-top: 14px;">用网页点按钮,驱动真实浏览器采集抖音账号</h1>
|
||||
<p>这不是无头绕反爬,而是一个可控的半自动流程。你点击“开始采集”后,脚本会打开真实 Chromium,会话沿用同一份登录态。你在浏览器里登录或过滑块后,回到这里点“已完成登录,继续采集”,系统就会继续抓取主页、creator-center,并按安全规则同步进 StoryForge。</p>
|
||||
<h1 style="margin-top: 2px;">${isWorkbenchMode ? "用业务工作台直接查看账号结论、作品榜单和运营分析" : "用网页点按钮,驱动真实浏览器采集抖音账号"}</h1>
|
||||
<p>${isWorkbenchMode ? "这是面向日常运营的业务页。登录后即可查看账号列表、Agent 结论、完整作品工作台、快照和对标结果。只有在需要抓取新账号时,再切到“采集控制台”。" : "这不是无头绕反爬,而是一个可控的半自动流程。你点击“开始采集”后,脚本会打开真实 Chromium,会话沿用同一份登录态。你在浏览器里登录或过滑块后,回到这里点“已完成登录,继续采集”,系统就会继续抓取主页、creator-center,并按安全规则同步进 StoryForge。"}</p>
|
||||
</section>
|
||||
|
||||
<div class="grid">
|
||||
<div class="grid capture-grid">
|
||||
<section class="card stack">
|
||||
<div>
|
||||
<h2>开始新采集</h2>
|
||||
@@ -923,7 +965,7 @@ function renderPage() {
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="card" style="margin-top: 18px;">
|
||||
<section class="card recent-runs-section" style="margin-top: 18px;">
|
||||
<div style="display: flex; justify-content: space-between; gap: 12px; align-items: center;">
|
||||
<div>
|
||||
<h2>最近运行</h2>
|
||||
@@ -1096,6 +1138,7 @@ function renderPage() {
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<p class="hint capture-footnote" style="margin-top:14px;">如果你主要是在做账号分析和作品运营,建议直接使用上面的业务工作台;只有在补采新账号时再回到采集控制台。</p>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
@@ -2188,7 +2231,11 @@ const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || "/", "http://127.0.0.1");
|
||||
try {
|
||||
if (req.method === "GET" && url.pathname === "/") {
|
||||
sendHtml(res, renderPage());
|
||||
sendHtml(res, renderPage("full"));
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && url.pathname === "/workbench") {
|
||||
sendHtml(res, renderPage("workbench"));
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && url.pathname === "/api/status") {
|
||||
|
||||
35
scripts/start_douyin_workbench.sh
Executable file
35
scripts/start_douyin_workbench.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
ROOT="$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)"
|
||||
PORT="${DOUYIN_WORKBENCH_PORT:-3618}"
|
||||
SCRIPT="$ROOT/scripts/douyin-browser-capture/control_panel.mjs"
|
||||
LOG_FILE="${DOUYIN_WORKBENCH_LOG:-/tmp/storyforge-douyin-workbench.log}"
|
||||
|
||||
if lsof -nP -iTCP:"$PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
echo "douyin workbench already running: http://127.0.0.1:$PORT/workbench"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
nohup env PORT="$PORT" node "$SCRIPT" >"$LOG_FILE" 2>&1 &
|
||||
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
port = os.environ.get("PORT", "3618")
|
||||
url = f"http://127.0.0.1:{port}/workbench"
|
||||
deadline = time.time() + 15
|
||||
last_error = ""
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=3) as resp:
|
||||
print(f"douyin workbench ready: {resp.status} {url}")
|
||||
raise SystemExit(0)
|
||||
except Exception as exc:
|
||||
last_error = str(exc)
|
||||
time.sleep(0.5)
|
||||
print(f"douyin workbench start timeout: {last_error}")
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
20
scripts/status_douyin_workbench.sh
Executable file
20
scripts/status_douyin_workbench.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PORT="${DOUYIN_WORKBENCH_PORT:-3618}"
|
||||
|
||||
if ! lsof -nP -iTCP:"$PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
echo "douyin workbench stopped"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
port = os.environ.get("PORT", "3618")
|
||||
for path in ("/workbench", "/"):
|
||||
url = f"http://127.0.0.1:{port}{path}"
|
||||
with urllib.request.urlopen(url, timeout=5) as resp:
|
||||
print(f"{path}: {resp.status}")
|
||||
PY
|
||||
7
scripts/stop_douyin_workbench.sh
Executable file
7
scripts/stop_douyin_workbench.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PORT="${DOUYIN_WORKBENCH_PORT:-3618}"
|
||||
|
||||
lsof -tiTCP:"$PORT" -sTCP:LISTEN | xargs -r kill
|
||||
echo "douyin workbench stopped: $PORT"
|
||||
Reference in New Issue
Block a user