2026-06-26 15:30:37 +08:00
|
|
|
|
# 架构设计
|
|
|
|
|
|
|
|
|
|
|
|
> 本文讲“怎么把技术栈搭起来”:模块职责、存储模型、流水线、CDP 已验证事实、开发顺序。
|
|
|
|
|
|
> 具体用了哪些库 / 平台,见 [技术栈](03-tech-stack.md)。
|
|
|
|
|
|
|
|
|
|
|
|
## 一、系统结构
|
|
|
|
|
|
|
|
|
|
|
|
Windows 本地桌面自动化工具,无后端服务,5 Tab GUI 驱动一条流水线。
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
运营(人)
|
|
|
|
|
|
|
|
|
|
|
|
|
v
|
2026-06-26 16:09:09 +08:00
|
|
|
|
GUI(PySide6 QTabWidget,5 Tab)
|
2026-06-26 15:30:37 +08:00
|
|
|
|
① 导入采集 ② AI生成 ③ 更新shopee ④ 账号管理 ⑤ 设置
|
|
|
|
|
|
|
|
|
|
|
|
|
v
|
|
|
|
|
|
核心模块(Python)
|
|
|
|
|
|
├── appconfig 读应用配置 config.json(Chrome 路径、目录根、AI 配置、端口范围…)
|
|
|
|
|
|
├── db SQLite 读写:账号、任务、各阶段结果(cmshopee.db)
|
|
|
|
|
|
├── excel openpyxl 导入输入列 / 回写输出列到原 Excel
|
|
|
|
|
|
├── config 账号 ↔ user-data-dir 绑定、slug、目录创建
|
2026-06-27 10:30:45 +08:00
|
|
|
|
├── accounts 账号 CRUD 服务:生成 slug/目录、启动登录、检测登录
|
2026-06-26 15:30:37 +08:00
|
|
|
|
├── chrome 按账号拼启动参数、启动/探测 Chrome、生成快捷方式
|
|
|
|
|
|
├── cdp CDP 客户端(连接、找/开 tab、执行 JS、拖拽)
|
|
|
|
|
|
├── editor 登录检测 / 采集旧标题旧封面 / 改标题 / 换封面 / 点更新
|
|
|
|
|
|
└── ai 文本生成(提示词+旧标题→新标题)/ 图像生成(提示词+旧封面→新封面)
|
|
|
|
|
|
|
|
|
|
|
|
|
v
|
|
|
|
|
|
Google Chrome(每账号独立 --user-data-dir + --remote-debugging-port) + AI 服务(外部)
|
|
|
|
|
|
|
|
|
|
|
|
|
v
|
|
|
|
|
|
Shopee 卖家中心页面 / 本地图片目录
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
真实组件:
|
|
|
|
|
|
|
2026-06-26 17:05:18 +08:00
|
|
|
|
- GUI 入口:根目录 `main.py` 调用 `app/gui.py`(待建,PySide6 + `QMainWindow` + `QTabWidget`,5 Tab);也支持 `python -m app`。
|
2026-06-27 10:30:45 +08:00
|
|
|
|
- 核心模块统一放在正式代码包 `app/`:`appconfig.py`、`db.py`、`excel.py`、`config.py`、`accounts.py`、`chrome.py`、`editor.py`、`workers.py`、`ai.py`、`prompts.py`;CDP 底座迁入 `app/cdp.py`(当前根目录 `cdp.py` 为已验证来源)。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- 已验证脚本(重构进模块):`prototypes/demo.py`、`prototypes/set_title.py`、`prototypes/set_cover.py`、`prototypes/get_title.py`、`prototypes/cookies.py`、`prototypes/inspect_images.py`、`prototypes/grab.py`。
|
|
|
|
|
|
- 外部依赖:本机 Google Chrome;Shopee;AI 服务(文本+图像,服务商待定);`openpyxl`。
|
|
|
|
|
|
|
|
|
|
|
|
## 二、流水线(核心)
|
|
|
|
|
|
|
2026-06-26 16:27:33 +08:00
|
|
|
|
每个任务(Excel 行)依次走过 4 个业务阶段,状态字段 `stage` 贯穿全程:
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
imported → collected → generated → applied
|
|
|
|
|
|
(导入 Excel) (采集旧数据) (AI 生成新数据) (改 Shopee 并提交)
|
|
|
|
|
|
① Tab ① Tab ② Tab ③ Tab
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- **imported**:openpyxl 解析输入列入库。
|
|
|
|
|
|
- **collected**:只读打开商品页,读旧标题、下载旧封面到本地,写 `old_title/old_cover_path`,回写 Excel 旧字段。
|
2026-06-26 15:55:28 +08:00
|
|
|
|
- **generated**:AI 用提示词+旧标题生成新标题;用提示词+旧封面生成新封面(image-to-image),新图存本地。**不设逐条人工审核阶段**,生成完即可进入 ③(新图留档本地 + 回写 Excel 供事后追溯)。
|
|
|
|
|
|
- **applied**:③ 点击「开始更新」后弹窗确认当前筛选范围和任务数量;确认后打开编辑页换标题+封面,逐条点「更新」提交线上,回写结果。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
任意阶段失败 → `stage` 不前进、记 `error`、`result/skipped`,不影响其他任务。
|
|
|
|
|
|
|
|
|
|
|
|
## 三、职责划分
|
|
|
|
|
|
|
2026-06-27 14:37:58 +08:00
|
|
|
|
**GUI(5 Tab)**:见 [routes.md](routes.md)。只做交互与预览,不写业务逻辑;耗时操作走 PySide6 `QObject` worker + `QThread`,用 signal 回主线程刷新 UI。**首次未配账号、对应账号 Chrome 未启动或未登录时,① ③ 执行按钮禁用或执行前拦截,并提示去 ④。**
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
**核心模块**
|
|
|
|
|
|
|
2026-06-26 15:55:28 +08:00
|
|
|
|
- `appconfig`:读写 `config.json`(Chrome 路径、`chrome_user_data_dir` 根、图片目录、AI 配置、端口、DB 路径)。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- `db`:SQLite 读写账号、任务、各阶段结果;建表/迁移。
|
|
|
|
|
|
- `excel`:openpyxl 读输入列、把输出列回写原 Excel(处理文件锁)。
|
|
|
|
|
|
- `config`:账号 ↔ user-data-dir 绑定;slug;目录创建。
|
2026-06-27 10:30:45 +08:00
|
|
|
|
- `accounts`:账号 CRUD 服务;生成目录、启动登录、检测登录;不自动登录/填密码。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- `chrome`:拼接启动命令、启动、探测端口、(可选)生成快捷方式。
|
|
|
|
|
|
- `cdp`:连接调试端口、找/开 tab、执行 JS、拖拽、注入文件。
|
|
|
|
|
|
- `editor`:登录检测、**采集**(读旧标题、下载旧封面)、改标题、换封面、点更新。
|
|
|
|
|
|
- `ai`:`gen_title(prompt, old_title)`、`gen_cover(prompt, old_cover_path)`(外部 AI;服务商待定)。
|
|
|
|
|
|
|
|
|
|
|
|
**存储(同一事实只存一处)**
|
|
|
|
|
|
|
|
|
|
|
|
- 应用配置(模型选择、生成参数、目录、Chrome 路径)→ `config.json`。
|
2026-06-26 16:27:33 +08:00
|
|
|
|
- AI 模型清单(url/模型/密钥/类型/连接超时)→ `config/ai_models.json`(API Key 本地明文保存,必须 gitignore,UI 打码显示)。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- 业务数据(账号、任务、各阶段结果)→ SQLite `cmshopee.db`。
|
|
|
|
|
|
- 图片(采集的旧封面、AI 生成的新封面)→ 本地图片目录(路径记在 DB)。
|
|
|
|
|
|
- 提示词 → 标题提示词存单文件 `title_prompt.txt`;封面提示词存多模板 `prompts/cover/<名称>.txt`。
|
|
|
|
|
|
- 登录态 → 各账号 `chrome_user_data_dir/<slug>/`。
|
|
|
|
|
|
|
|
|
|
|
|
## 四、多账号隔离方案(决策)
|
|
|
|
|
|
|
|
|
|
|
|
采用**每账号独立 user-data-dir**(非 Chrome profile)。`--remote-debugging-port` 绑定在 user-data-dir/进程上,profile 方案无法每账号独立 CDP、串号风险高。启动主路径用程序 `subprocess` 直启(`--remote-debugging-port` + `--remote-allow-origins=*` + `--user-data-dir`);可选生成 `.lnk` 快捷方式(PowerShell `WScript.Shell`,参数写在「目标」字段)。
|
|
|
|
|
|
|
|
|
|
|
|
## 五、数据模型
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 应用配置 `config.json`
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"chrome_path": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
|
|
|
|
"user_data_root": "chrome_user_data_dir",
|
|
|
|
|
|
"image_dir": "images",
|
|
|
|
|
|
"db_path": "cmshopee.db",
|
|
|
|
|
|
"default_debug_port": 9222,
|
|
|
|
|
|
"debug_port_range": [9222, 9260],
|
|
|
|
|
|
"cdp_ready_timeout": 60,
|
|
|
|
|
|
"ai": {
|
|
|
|
|
|
"default_text_model": "GPT-5.5 文本",
|
|
|
|
|
|
"default_image_model": "Nano Banana 2",
|
|
|
|
|
|
"title_concurrency": 4,
|
|
|
|
|
|
"image_concurrency": 4,
|
|
|
|
|
|
"retry": 2,
|
|
|
|
|
|
"jpg_quality": 90,
|
|
|
|
|
|
"resolution": "1k",
|
|
|
|
|
|
"resolution_timeouts": { "512": 180, "1k": 240, "2k": 360, "4k": 600 }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`ai` 段只放**选择 + 全局生成参数**:
|
|
|
|
|
|
|
|
|
|
|
|
- `default_text_model` / `default_image_model`:引用 `ai_models.json` 里的模型名(标题用文本模型、封面用图像模型)。
|
|
|
|
|
|
- `resolution`:当前分辨率,下拉 `512 / 1k / 2k / 4k`。
|
|
|
|
|
|
- `resolution_timeouts`:分辨率 → **等待大模型返回超时(秒)** 的映射;用户选分辨率即自动套用,不单独填。
|
|
|
|
|
|
- 模型本身的定义(url/key/类型/连接超时…)在 `config/ai_models.json`,见 5.1b。
|
2026-06-26 16:27:33 +08:00
|
|
|
|
- 密钥不在 `config.json`:每个模型的 `api_key` 存于 `config/ai_models.json`,本地明文保存、UI 打码、gitignore、不入日志。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
### 5.1b AI 模型清单 `config/ai_models.json`
|
|
|
|
|
|
|
|
|
|
|
|
模型定义清单("有哪些模型"),与 `config.json` 的 `ai` 段("选了哪个 + 全局参数")职责分开。
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"models": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": "Nano Banana 2", // 服务商/模型名,唯一,作下拉显示与引用键
|
|
|
|
|
|
"category": "image", // text | image —— 决定它出现在“标题/图片大模型”哪个下拉
|
|
|
|
|
|
"enabled": true,
|
|
|
|
|
|
"url": "https://api.vectorengine.ai/v1/chat/completions",
|
|
|
|
|
|
"model": "gemini-3.1-flash-image-preview", // 模型 ID
|
2026-06-26 16:27:33 +08:00
|
|
|
|
"api_key": "***", // 密钥:本地明文存、UI 打码、不入日志、不进版本库
|
2026-06-26 15:30:37 +08:00
|
|
|
|
"api_type": "auto", // chat | images_edits | auto —— 决定请求构造方式
|
|
|
|
|
|
"connect_timeout_seconds": 30, // 连接该服务超时(每模型,默认 30)
|
|
|
|
|
|
"timeout_seconds": 0, // 返回超时:0/留空 = 运行时按 resolution_timeouts 取值
|
|
|
|
|
|
"extra_body": {}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
关键事实:
|
|
|
|
|
|
|
|
|
|
|
|
- **`category`(文本/图像)是必需的**:角色下拉据此过滤(标题下拉只列 text、图片下拉只列 image),防止错配。
|
|
|
|
|
|
- 约束:**至少各有一个 text 与一个 image 模型**;下拉默认最少一项、删到剩一项时禁用「删除」。
|
|
|
|
|
|
- `connect_timeout_seconds`(连接超时)属于**模型**;返回超时由分辨率映射决定(不在模型上单设)。
|
|
|
|
|
|
- `api_type` 反映不同 API 形状(`chat`/`images_edits`/`auto`),请求构造按它分支。
|
2026-06-26 16:27:33 +08:00
|
|
|
|
- `name` 唯一;`api_key` 本地明文保存、打码显示。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
### 5.2 SQLite `cmshopee.db`
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
2026-06-26 16:27:33 +08:00
|
|
|
|
-- 批次(一次导入动作)
|
|
|
|
|
|
CREATE TABLE batches (
|
|
|
|
|
|
id TEXT PRIMARY KEY, -- batch_id,如 20260626_153000_xxxx
|
|
|
|
|
|
source_files_json TEXT NOT NULL, -- 导入文件绝对路径列表(JSON)
|
|
|
|
|
|
status TEXT NOT NULL DEFAULT 'active', -- active/done/partial/failed
|
|
|
|
|
|
note TEXT,
|
|
|
|
|
|
created_at TEXT NOT NULL,
|
|
|
|
|
|
updated_at TEXT NOT NULL
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-06-26 15:30:37 +08:00
|
|
|
|
-- 账号(④ 账号管理)
|
|
|
|
|
|
CREATE TABLE accounts (
|
|
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
|
|
account_name TEXT NOT NULL, -- Shopee 登录账号名(展示/参考)
|
|
|
|
|
|
alias TEXT UNIQUE NOT NULL, -- 别名,Excel 用它匹配
|
|
|
|
|
|
region_host TEXT NOT NULL,
|
|
|
|
|
|
slug TEXT UNIQUE NOT NULL, -- user-data-dir 子目录名 [a-z0-9_]
|
|
|
|
|
|
user_data_dir TEXT NOT NULL,
|
|
|
|
|
|
debug_port INTEGER NOT NULL,
|
2026-06-26 16:27:33 +08:00
|
|
|
|
password TEXT, -- 本地明文,仅参考,不自动登录;UI 打码,gitignore
|
2026-06-26 15:30:37 +08:00
|
|
|
|
note TEXT,
|
|
|
|
|
|
created_at TEXT NOT NULL,
|
2026-06-26 16:27:33 +08:00
|
|
|
|
updated_at TEXT NOT NULL,
|
2026-06-26 15:30:37 +08:00
|
|
|
|
last_login_at TEXT
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
-- 任务 + 各阶段结果(贯穿流水线)
|
|
|
|
|
|
CREATE TABLE tasks (
|
2026-06-26 16:27:33 +08:00
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
|
|
batch_id TEXT NOT NULL REFERENCES batches(id),
|
|
|
|
|
|
-- Excel 回写定位
|
|
|
|
|
|
source_file TEXT NOT NULL, -- 展示用原路径
|
|
|
|
|
|
source_file_abs TEXT NOT NULL, -- 绝对路径,作为回写分组依据
|
|
|
|
|
|
source_sheet TEXT NOT NULL, -- sheet 名
|
|
|
|
|
|
source_row INTEGER NOT NULL, -- Excel 行号(1-based)
|
|
|
|
|
|
row_key TEXT NOT NULL UNIQUE, -- batch/file/sheet/row 组成,防重复导入
|
2026-06-26 15:30:37 +08:00
|
|
|
|
-- 输入列(Excel)
|
2026-06-26 16:27:33 +08:00
|
|
|
|
account_name TEXT,
|
|
|
|
|
|
alias TEXT NOT NULL,
|
|
|
|
|
|
item_id TEXT NOT NULL,
|
2026-06-26 15:30:37 +08:00
|
|
|
|
-- 采集输出(程序写,改前快照)
|
2026-06-26 16:27:33 +08:00
|
|
|
|
old_title TEXT,
|
|
|
|
|
|
old_cover_path TEXT, -- 旧封面本地图片路径
|
2026-06-26 15:30:37 +08:00
|
|
|
|
-- AI 输出
|
2026-06-26 16:27:33 +08:00
|
|
|
|
new_title TEXT,
|
|
|
|
|
|
new_cover_path TEXT, -- 新封面本地图片路径
|
2026-06-26 15:30:37 +08:00
|
|
|
|
-- 应用
|
2026-06-26 16:27:33 +08:00
|
|
|
|
committed INTEGER NOT NULL DEFAULT 0, -- 是否成功点「更新」提交
|
|
|
|
|
|
stage TEXT NOT NULL DEFAULT 'imported', -- imported/collected/generated/applied
|
|
|
|
|
|
status TEXT NOT NULL DEFAULT 'pending', -- pending/running/success/failed/skipped/cancelled
|
|
|
|
|
|
last_error TEXT,
|
|
|
|
|
|
collect_attempts INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
|
generate_attempts INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
|
apply_attempts INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
|
imported_at TEXT NOT NULL,
|
|
|
|
|
|
collected_at TEXT,
|
|
|
|
|
|
generated_at TEXT,
|
|
|
|
|
|
applied_at TEXT,
|
|
|
|
|
|
updated_at TEXT NOT NULL,
|
|
|
|
|
|
UNIQUE(batch_id, source_file_abs, source_sheet, source_row)
|
2026-06-26 15:30:37 +08:00
|
|
|
|
);
|
2026-06-26 16:27:33 +08:00
|
|
|
|
|
|
|
|
|
|
CREATE INDEX idx_tasks_batch_stage_status ON tasks(batch_id, stage, status);
|
|
|
|
|
|
CREATE INDEX idx_tasks_alias ON tasks(alias);
|
|
|
|
|
|
CREATE INDEX idx_tasks_item ON tasks(item_id);
|
2026-06-26 15:30:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
关键事实:
|
|
|
|
|
|
|
|
|
|
|
|
- `alias` 是账号↔任务**唯一关联键**;找不到账号 → stage=skipped,error=别名未匹配,最后弹窗汇总。
|
|
|
|
|
|
- `account_name` 仅展示/参考;匹配以 `alias` 为准。
|
2026-06-26 16:27:33 +08:00
|
|
|
|
- `source_file_abs/source_sheet/source_row` 是回写 Excel 的权威定位;即使商品 ID 重复,也按原行回写。
|
|
|
|
|
|
- `row_key` 防止同一批次内重复导入同一行。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- `old_title/old_cover_path`:程序**采集阶段抓取**的快照(输出)。
|
2026-06-26 15:55:28 +08:00
|
|
|
|
- `new_title/new_cover_path`:**AI 生成**结果(输出),图片落本地;不设逐条确认阶段,生成即可进入 ③。
|
2026-06-26 16:27:33 +08:00
|
|
|
|
- `stage` 表示已完成到哪个业务阶段;`status` 表示当前处理结果。失败时 `stage` 保持在最后成功阶段,`status=failed`,错误写 `last_error`。
|
2026-06-26 15:55:28 +08:00
|
|
|
|
- `committed`:③ 用户确认批量更新弹窗后逐条点「更新」提交;该字段记录提交是否成功。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- 各字段**每阶段处理完立即写回 SQLite**(实时落库);阶段结束后批量回写 Excel。
|
|
|
|
|
|
|
2026-06-26 16:27:33 +08:00
|
|
|
|
### 5.2b SQLite 并发与连接规则
|
|
|
|
|
|
|
|
|
|
|
|
- `db.connect()` 统一设置:`PRAGMA foreign_keys=ON`、`PRAGMA journal_mode=WAL`、`PRAGMA busy_timeout=5000`、`PRAGMA synchronous=NORMAL`。
|
|
|
|
|
|
- SQLite connection 不跨线程共享;每个 PySide6 worker / 后台线程按需创建自己的 connection。
|
|
|
|
|
|
- 写操作使用短事务:单条任务完成即提交,避免长时间占用写锁。
|
|
|
|
|
|
- 写库遇到 locked/busy:按短间隔重试,超过 busy timeout 后返回明确错误并通过 signal 通知 GUI。
|
|
|
|
|
|
- GUI 不直接缓存“事实状态”;写库成功后由 worker 发 `row_updated`,GUI 再刷新对应行。
|
|
|
|
|
|
- Excel 回写以 SQLite 为事实来源;原 Excel 被锁时不回滚 SQLite,只提示关闭后重试或另存副本。
|
|
|
|
|
|
|
2026-06-26 15:30:37 +08:00
|
|
|
|
### 5.3 Excel 模板
|
|
|
|
|
|
|
2026-06-27 11:29:48 +08:00
|
|
|
|
标准空模板文件保留在仓库根目录:`shopee待处理任务模板.xlsx`,单工作表名为 `待处理任务`。运营使用时应复制一份再填写,填写后的业务 Excel 含商品数据,不提交版本库。
|
|
|
|
|
|
|
2026-06-26 15:30:37 +08:00
|
|
|
|
| 列 | 含义 | 输入/输出 |
|
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
|
| 账号名 | Shopee 账号名(展示) | 输入 |
|
|
|
|
|
|
| 别名 | 关联 accounts.alias(匹配键) | 输入 |
|
|
|
|
|
|
| 商品id | item_id | 输入 |
|
|
|
|
|
|
| 旧标题 | 采集到的旧标题 | 输出,回写 |
|
|
|
|
|
|
| 旧封面图片路径 | 采集到的旧封面本地路径 | 输出,回写 |
|
|
|
|
|
|
| 新标题 | AI 生成的新标题 | 输出,回写 |
|
|
|
|
|
|
| 新封面图片路径 | AI 生成的新封面本地路径 | 输出,回写 |
|
|
|
|
|
|
| 更新状态 | 处理结果(成功/失败/跳过+原因) | 输出,回写 |
|
|
|
|
|
|
|
|
|
|
|
|
- 输入列(账号名/别名/商品id)运营填;其余为程序各阶段输出,**回写到原 Excel**(被锁→提示重试/另存副本)。
|
|
|
|
|
|
- 别名以此列为匹配权威,不解析文件名。
|
2026-06-27 11:29:48 +08:00
|
|
|
|
- 标准模板推荐前三列表头固定为:`账号名 | 别名 | 商品id`。不要使用 `账号`、`店铺名`、`店名` 代替匹配列;`别名` 必须与 ④ 账号管理中的 `accounts.alias` 一致。
|
2026-06-26 16:37:58 +08:00
|
|
|
|
- 导入容错:缺少必需列(别名/商品id)时拒绝整个文件,记入 `file_errors`,不导入该文件任何行。
|
|
|
|
|
|
- 脏行处理:单行缺别名/商品id、商品id 格式错误等逐行跳过,计入 `invalid`,不阻塞同文件其他有效行。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
### 5.4 本地图片目录
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
images/<slug>/<item_id>_old.<ext> # 采集下载的旧封面
|
|
|
|
|
|
images/<slug>/<item_id>_new.<ext> # AI 生成的新封面
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
路径记入 DB;上传新封面用本地 `<item_id>_new` 文件(Windows 绝对路径传 setFileInputFiles)。
|
|
|
|
|
|
|
|
|
|
|
|
## 六、关键流程细节
|
|
|
|
|
|
|
2026-06-26 16:09:09 +08:00
|
|
|
|
### 6.0 GUI 线程模型(PySide6)
|
|
|
|
|
|
|
|
|
|
|
|
- 主线程只运行 `QApplication`、窗口、表格、弹窗和状态刷新;不得在主线程执行 CDP、AI、Excel 回写、图片下载等耗时任务。
|
|
|
|
|
|
- 每类耗时流程封装为 `QObject` worker:`CollectWorker`、`GenerateWorker`、`ApplyWorker`、`ImportWorker`、`WriteBackWorker`。
|
|
|
|
|
|
- Worker 通过 signal 向 GUI 汇报:`progress`、`row_updated`、`log`、`failed`、`finished`、`cancelled`;GUI 槽函数只做 UI 刷新和按钮状态切换。
|
|
|
|
|
|
- Worker 不直接操作任何 Qt widget,不弹窗;需要用户确认的动作(如 ③ 开始更新确认)必须在主线程先完成,再启动 worker。
|
|
|
|
|
|
- `停止` 使用协作式取消:GUI 调用 worker 的 cancel 标记;worker 在任务间/重试前检查,取消未开始项,正在执行的单条任务跑到安全边界后结束。
|
|
|
|
|
|
- SQLite connection 不跨线程共享;每个 worker/线程按需创建自己的连接,写库后发 signal 通知 UI 刷新。
|
|
|
|
|
|
|
2026-06-26 15:30:37 +08:00
|
|
|
|
### 6.1 采集(① Tab,只读)
|
|
|
|
|
|
|
|
|
|
|
|
- 用账号 Chrome 打开商品页,等就绪,读旧标题(标题输入框 value)。
|
|
|
|
|
|
- 旧封面:取第一张 itembox 的 `img.src`(CDN 链接),下载到 `images/<slug>/<item_id>_old`。
|
|
|
|
|
|
- 写 `old_title/old_cover_path`、stage=collected;批量回写 Excel 旧字段。
|
2026-06-27 14:37:58 +08:00
|
|
|
|
- T-204b 目标:采集任务结束后自动触发当前批次旧字段回写;原 Excel 被锁时不影响 SQLite 结果,提示关闭后重试,并保留手动「回写旧数据到 Excel」入口。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
### 6.2 AI 生成(② Tab)
|
|
|
|
|
|
|
|
|
|
|
|
单个「开始生成」按钮,**两段式、各自并发**(标题快、图片慢,分开并发更高效):
|
|
|
|
|
|
|
|
|
|
|
|
1. **并发生成标题**:线程池大小 = `title_concurrency`,用 `default_text_model` 调 `gen_title(标题提示词, old_title)` → new_title。
|
|
|
|
|
|
2. **接着并发生成图片**:线程池大小 = `image_concurrency`,用 `default_image_model` 调 `gen_cover(封面提示词, old_cover_path, resolution, jpg_quality)` → 新图存 `images/<slug>/<item_id>_new.jpg`。
|
|
|
|
|
|
- 连接超时取该模型 `connect_timeout_seconds`;**返回超时取 `resolution_timeouts[resolution]`**(512→180/1k→240/2k→360/4k→600)。
|
|
|
|
|
|
|
|
|
|
|
|
- 失败重试:每次调用失败按 `retry` 次重试,仍失败则记 error(不阻塞其余)。
|
|
|
|
|
|
- 每条/每张完成**立即 `set_generated` 写库**(实时落库,停止或崩溃不丢已生成的)。
|
|
|
|
|
|
- **「停止」**:取消未开始的任务,正在跑的少量完成或中断;停止后可再次「开始生成」对剩余继续。
|
|
|
|
|
|
- 进度:`标题 x/n · 封面 x/n · 失败 n`。
|
2026-06-26 15:55:28 +08:00
|
|
|
|
- 生成后 stage=generated;**不设逐条人工审核阶段**。双击任务弹窗查看新旧封面(纯查看),可选对某行 `重生成`;新标题直接用 AI 输出(不可编辑)。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- 并发数、重试、分辨率、jpg 质量、模型/Key 均来自 ⑤ 设置(`config.json` 的 `ai` 段)。
|
|
|
|
|
|
|
|
|
|
|
|
提示词管理:
|
|
|
|
|
|
|
|
|
|
|
|
- **标题提示词**:单个文本,「保存」写入 `title_prompt.txt`;软件启动时加载该文件回显到输入框(缺失则空)。
|
|
|
|
|
|
- **封面提示词**:多模板。下拉选模板(读 `prompts/cover/*.txt`),图标工具栏 新建/保存/另存为/重命名/删除;重名校验、删除二次确认、删空给默认。
|
|
|
|
|
|
- **变量**:封面提示词支持占位符 `{旧标题}`、`{新标题}`、`{商品id}`、`{店铺}`,生成前用该任务真实值替换(`render_prompt`)。「插入标题」= 在光标处插入 `{新标题}`;「预览」= 用某条任务的值替换变量后展示,确认实际发送给 AI 的内容。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.3 应用更新(③ Tab)
|
|
|
|
|
|
|
2026-06-26 15:55:28 +08:00
|
|
|
|
- ③ 顶部筛选确定本次作用范围;点击「开始更新」后弹窗展示筛选条件、任务数量和“将提交线上”的风险提示。
|
|
|
|
|
|
- 用户点「是/确认」才开始批量更新;点「否/取消」不执行、不改库。
|
|
|
|
|
|
- 对确认后的**已生成(generated)任务**:`open_product` → `change_title(new_title)`(如有)→ `replace_cover(new_cover_path)`(如有)→ `click_update` 提交。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- 串行、单条失败继续;每条立即写 SQLite;全部完成回写 Excel(新字段+状态)+ 弹窗汇总。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.4 登录检测
|
|
|
|
|
|
|
2026-06-27 12:05:33 +08:00
|
|
|
|
- 无 Shopee tab 时打开卖家中心根地址 `https://<region_host>/`(默认 `https://seller.shopee.tw/`),重定向到登录页或缺会话 Cookie(`SPC_ST`/`SPC_U`)→ 未登录;不自动登录,提示人工登录。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
## 七、CDP 已验证事实(务必遵守)
|
|
|
|
|
|
|
|
|
|
|
|
| 难点 | 已验证结论 |
|
|
|
|
|
|
| --- | --- |
|
|
|
|
|
|
| Chrome 启动参数 | 全关后带 `--remote-debugging-port=<port> --remote-allow-origins=* --user-data-dir=<dir>`;缺 allow-origins 则 WebSocket 403 |
|
|
|
|
|
|
| 代理干扰 | 清除 `*_proxy`(requests `trust_env=False`),否则连本地 CDP 超时 |
|
|
|
|
|
|
| WebSocket Origin | `websocket-client` `suppress_origin=True` |
|
|
|
|
|
|
| SPA 就绪 | 不用 load 事件;轮询“标题输入框 + 图片 itembox + 上传输入框”三者都在 |
|
|
|
|
|
|
| 标题输入框 | XPath `//input[@class='eds-input__input' and string-length(@modelvalue)>24]` |
|
|
|
|
|
|
| 写标题 | 原生 setter + 派发 `input`/`change`;`value`==`modelvalue`==新值 |
|
|
|
|
|
|
| 读旧封面 | 第一张 itembox 的 `img.src`(`susercontent` CDN),下载到本地 |
|
|
|
|
|
|
| 上传输入框 | `.shopee-image-manager__upload input[type=file]`;`DOM.setFileInputFiles` 传 Windows 路径 |
|
|
|
|
|
|
| 上传成功 | 张数 +1 且新图 src 为 `susercontent` |
|
|
|
|
|
|
| 封面=第一位 | `Input.dispatchMouseEvent` 拖到第一位,落点 `第一张.left - 0.30*宽` |
|
|
|
|
|
|
| 满 9 张 | 上限 9;换封面先删第一张(`.shopee-image-manager__icon--delete`,确认框待实测)|
|
2026-06-26 15:55:28 +08:00
|
|
|
|
| 更新按钮 | `button.eds-button` 中 `<span>更新</span>`;③ 批量确认后逐条点提交;禁用态(校验未过)记为失败 |
|
2026-06-26 15:30:37 +08:00
|
|
|
|
| 登录检测 | 重定向到登录页或缺 `SPC_ST` → 未登录 |
|
|
|
|
|
|
|
2026-06-26 15:55:28 +08:00
|
|
|
|
高风险动作(删满 9 张封面、点更新提交、AI 图上线)先在测试商品验证。注意:本设计无逐条人工审核阶段、无常驻提交开关;③ 点击「开始更新」后必须弹窗确认,确认后才把当前筛选结果中的 AI 标题/封面提交线上。新图本地留档+回写 Excel 是事后追溯手段。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
## 八、推荐开发顺序
|
|
|
|
|
|
|
2026-06-26 17:05:18 +08:00
|
|
|
|
1. **地基**:先建 `app/` 包、`app/cdp.py`、`app/__main__.py`、根目录 `main.py`;再做 `app/editor.py`(含采集)、`app/appconfig.py`+`config.json`、`app/db.py`、`.gitignore`。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
2. **账号与启动**(④):`config` 建目录、`chrome` 启动器/快捷方式、登录保活与检测、账号 CRUD。
|
|
|
|
|
|
3. **导入采集**(①):`excel` 导入、采集旧标题/旧封面、回写。
|
2026-06-26 15:55:28 +08:00
|
|
|
|
4. **AI 生成**(②):`ai` 模块、提示词、对照预览。
|
|
|
|
|
|
5. **更新 shopee**(③):对已生成任务弹窗批量确认后换标题+封面、提交、回写。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
6. **设置**(⑤)+ 首次未配账号引导保护。
|
|
|
|
|
|
|
|
|
|
|
|
## 九、项目结构建议
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
cmshopee/
|
|
|
|
|
|
├── docs/
|
2026-06-26 17:05:18 +08:00
|
|
|
|
├── app/
|
|
|
|
|
|
│ ├── __init__.py
|
|
|
|
|
|
│ ├── __main__.py # 支持 python -m app
|
|
|
|
|
|
│ ├── cdp.py # CDP 底座(由根目录 cdp.py 迁入)
|
2026-06-27 10:30:45 +08:00
|
|
|
|
│ ├── appconfig.py / db.py / excel.py / config.py / accounts.py / chrome.py
|
2026-06-26 17:05:18 +08:00
|
|
|
|
│ ├── editor.py / ai.py / prompts.py / gui.py / workers.py
|
|
|
|
|
|
├── main.py # GUI 启动入口:from app.gui import main
|
2026-06-27 11:29:48 +08:00
|
|
|
|
├── shopee待处理任务模板.xlsx # 标准空 Excel 模板,可提交;业务填写后的副本不提交
|
2026-06-26 15:30:37 +08:00
|
|
|
|
├── config.json # 应用配置(模型选择/生成参数/路径,gitignore)
|
|
|
|
|
|
├── config/ai_models.json # AI 模型清单(含密钥,必须 gitignore)
|
|
|
|
|
|
├── cmshopee.db # SQLite(账号/任务/结果,gitignore)
|
|
|
|
|
|
├── chrome_user_data_dir/ # 各账号 Chrome 配置(含登录态,gitignore)
|
2026-06-26 16:37:58 +08:00
|
|
|
|
├── images/ # 旧封面/新封面本地图片(gitignore)
|
2026-06-26 15:30:37 +08:00
|
|
|
|
├── title_prompt.txt # 标题提示词(单文件,启动回显)
|
|
|
|
|
|
├── prompts/cover/<名称>.txt # 封面提示词模板(多个)
|
|
|
|
|
|
└── prototypes/ # 已验证原型/探查脚本(demo/set_*/get_title/cookies/inspect_images/grab/1.py)
|
2026-06-26 17:05:18 +08:00
|
|
|
|
# 逻辑待并入 app/editor.py 后清理;见 prototypes/README.md
|
2026-06-26 15:30:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-27 11:29:48 +08:00
|
|
|
|
> `config.json`、`config/ai_models.json`、`cmshopee.db`、`chrome_user_data_dir/`、`images/` 含密钥/凭证/业务数据,必须 gitignore。`shopee待处理任务模板.xlsx` 是标准空模板,可以提交;运营填写后的 Excel 副本属于业务数据,不提交。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
|
|
|
|
|
|
## 十、架构纪律
|
|
|
|
|
|
|
|
|
|
|
|
- CDP 交互事实变化同步第七节。
|
2026-06-26 17:05:18 +08:00
|
|
|
|
- 正式代码只放 `app/` 包;根目录只保留 `main.py`、配置/数据目录、文档和原型目录,不新增正式业务模块。
|
|
|
|
|
|
- `app` 包内模块优先用相对导入(如 `from .cdp import CDP`);根入口 `main.py` 用 `from app.gui import main`。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- 存储边界:应用设置→config.json,账号/任务/结果→SQLite,图片→本地目录并记路径于 DB,登录态→user-data-dir;同一事实只存一处。
|
|
|
|
|
|
- 别名是账号↔任务唯一关联键。
|
2026-06-26 16:27:33 +08:00
|
|
|
|
- 密码与 AI Key 本地明文保存、UI 打码、不外传、不写日志;不自动登录。
|
2026-06-26 15:55:28 +08:00
|
|
|
|
- AI 生成内容直接进入 ③ 更新候选;③ 批量确认后提交,新图本地留档 + 回写 Excel 以备追溯。
|
2026-06-26 15:30:37 +08:00
|
|
|
|
- 高风险模块先单独验证,再接入流水线。
|
2026-06-26 16:09:09 +08:00
|
|
|
|
- GUI 只通过 signal/slot 接收 worker 进度;禁止后台线程直接操作 Qt widget 或共享 SQLite connection。
|