# softwarer-selfmedia **Repository Path**: queue001/softwarer-selfmedia ## Basic Information - **Project Name**: softwarer-selfmedia - **Description**: 批量发布助手 | 用 PyQt6 + Playwright 把「视频 + 封面 + 标题 + 简介」一键发布到微信视频号 / Bilibili / 抖音。桌面端操作,支持模板复用与多平台串行批量;通过真实 Chromium 窗口模拟人工投稿,平台改版时只需在界面替换定位符,无需改动代码。内置登录态持久化、失败自动截图、可单独关闭发布动作的安全阀,支持先演练再真发。 - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-10-16 - **Last Updated**: 2026-10-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 批量发布助手(softwarer-selfmedia) **PyQt6 桌面端 + Playwright 浏览器自动化**:把「视频 + 封面 + 标题 + 简介」一次性发到 **微信视频号 / Bilibili / 抖音**,支持模板复用与多平台串行批量。 不是"上传 HTTP 接口"的脚本——它是**开一个真实 Chromium 窗口**,像人手一样点页面、 填表单、传封面,因此平台改版时只需改定位符,不用等官方接口。 ![主界面](docs/screenshots/main_batch.png) --- ## 功能特性 - **三平台覆盖**:微信视频号(channels.weixin.qq.com)、Bilibili(member.bilibili.com)、 抖音(creator.douyin.com),各平台字段独立建模。 - **两套操作模式** - **批量发布**:选一个模板 + 一个视频 → 勾选要发的平台 → **串行**逐个平台跑, 上一个结束 400ms 后自动开始下一个。 - **单条投稿**:切到平台 tab,逐项填写后点「开始发布」。 - **发布模板**:一次配置、多视频复用。模板按 `platforms = {wx/bili/dy}` 分平台存, 标题和简介**不进模板**(每条视频都不一样),其余可复用项全部记住。 - **封面机制完整对齐各平台规格**(比例、先后顺序、封面编辑器裁剪提交),见下文对照表。 - **登录态持久化**:每个平台一个独立的浏览器 profile 目录(`.data/*_profile`), 扫码一次长期免登。 - **运行日志面板**:带级别(info/head/step/ok/warn/error)彩色行,每步实时落库。 - **可观测的失败点**:任何一步失败自动截图存到 `.data/*_shots/`,文件名带 tag。 - **安全阀**:引擎有 `enabled_steps` 字典,可单独关掉"发布"这一步做演练, 程序会显式跳过并打 warn,绝不静默漏发。 --- ## 环境要求 | 项 | 版本 | |---|---| | OS | Windows(依赖 Win 任务栏 AppUserModelID;macOS/Linux 未适配) | | Python | **3.12+**(`pyproject.toml` 的 `requires-python`) | | 依赖 | `PyQt6>=6.6`、`playwright>=1.40` | --- ## 安装 推荐用 [uv](https://docs.astral.sh/uv/)(本项目已带 `uv.lock`): ```bat :: 1. 建虚拟环境并安装依赖 uv sync :: 2. 下载 Chromium(playwright 自带的浏览器,~150MB,务必执行) uv run playwright install chromium ``` 用标准 venv 也行: ```bat python -m venv .venv .venv\Scripts\activate pip install -r requirements.txt playwright install chromium ``` ### 启动 ```bat run.bat :: 启动程序(推荐,脚本内已固定 python 解释器路径) ``` 或者手动: ```bat python main.py ``` 首次启动时 `main.py` 会自检 Playwright 与 Chromium,缺失则弹窗提示,不会静默失败。 > `run.bat` / `debug.bat` 里写死了本机的解释器路径 > (`C:\Users\Administrator\.workbuddy\binaries\python\envs\default\Scripts\python.exe`), > 换机器时脚本会自动回退到系统 `python`。 --- ## 使用流程 ### 批量发布(推荐日常用法) 1. **发布模板** tab → 新建模板 → 勾选要覆盖的平台 → 填好封面路径、合集、分区等 → 保存。 2. **批量发布** tab → ① 下拉选模板(**会自动勾选模板包含的平台**)→ ② 选视频(可直接 拖进来,标题默认取文件名)→ ③ 填共用描述 → ④ 点「开始发布」。 3. 右侧看日志;任一平台失败会截图,进 `.data/<平台>_shots/` 找对应 png。 ### 单条投稿 切到目标平台 tab(视频号 / Bilibili / 抖音),填表 → 「开始发布」。 首次使用某个平台会弹出扫码窗口,登录态之后写入该平台 profile。 --- ## 界面结构 五个 tab,右侧固定为运行日志 + 进度条 + 状态栏: | Tab | 内容 | |---|---| | 批量发布 | 模板选择 + 平台勾选 + 单视频 + 共用描述 + 串行调度 | | 视频号 | 竖/横封面(3:4 与 4:3)、短标题、简介、标签、合集、原创声明、可见范围 | | Bilibili | 4:3 首页封面 + 16:9 空间封面、标题、简介、标签、分区、自制/转载 | | 抖音 | 横封面 4:3(主)+ 竖封面 3:4(可选)、标题、简介、合集、位置、允许下载、自主声明 | | 发布模板 | 模板列表(右键编辑/删除)、保存到当前平台、应用到当前平台 | ![抖音投稿页](docs/screenshots/tab_douyin.png) ### 各平台封面规格(易记错,代码按此实现) | 平台 | 封面位 | 比例 | 字段 | |---|---|---|---| | 视频号 | 个人主页卡片 | **3:4**(1080×1440) | `cover_path` | | 视频号 | 分享卡片(**仅横视频**) | **4:3**(1440×1080) | `share_cover_path` | | Bilibili | 首页推荐 | **4:3** | `cover_path` | | Bilibili | 个人空间 | **16:9** | `cover_169_path` | | 抖音 | 横封面(**主角**) | **4:3** | `cover_path` | | 抖音 | 竖封面(可选,留空=抖音自动推导) | **3:4** | `cover_h_path` | > **命名包袱**:`cover_h_path` 里的 `h` **不是 horizontal**,是「额外的竖封面」。 > 只传横封面时抖音会自动推导竖封面;**先传竖图再传横图,竖图会被覆盖**(实测同 src 前缀)。 --- ## 目录结构 代码按「**功能 / 测试 / 探针 / 工具**」四类分离,根目录只放功能代码。 ``` . ├── 根目录 = 功能代码(全部是程序运行必需) │ ├── main.py 入口(Playwright 自检 → 应用主题 → 起窗口) │ ├── config.py 全局配置 + 定位符默认值 + DATA_DIR │ ├── task_model.py PublishTask 数据模型(三平台字段统一在此) │ ├── publisher.py 视频号发布引擎(基类逻辑在 PublisherEngine) │ ├── bili_publisher.py Bilibili 发布引擎 │ ├── douyin_publisher.py 抖音发布引擎(最复杂,封面/合集/声明都在这) │ ├── ui_main.py 主窗口 + 视频号投稿页 + 日志 + 拖放 + 扫码 │ ├── ui_titlebar.py 无边框窗口基类 FramelessWindow + 自绘标题栏 │ ├── ui_theme.py 配色 PALETTE + QSS 唯一来源 │ ├── ui_bili.py Bilibili 投稿表单 │ ├── ui_douyin.py 抖音投稿表单 │ ├── ui_batch.py 批量发布面板 │ ├── ui_templates.py 模板列表面板 │ └── ui_template_edit.py 模板编辑对话框(字段表格驱动) │ ├── probes/ 探针(49 个)—— 一次性调试脚本 ├── tests/ 测试(5 个 + 汇总器) ├── tools/ 辅助工具(6 个) ├── assets/ 图标素材(png / svg) ├── docs/ 调研笔记 + 界面截图 └── .data/ 运行时数据(配置、profile、截图、日志、模板)— 不入版本库 ``` ### probes/ —— 探针(**不是**测试) 一次性调试脚本,用来摸清各平台页面的真实 DOM / 选择器 / 时序。 典型形态:启动 Chromium → 走一段真实交互 → 截图 + dump DOM → 退出。 **数量多、互不依赖、可以随手删**,只有排查线上问题、或平台又改版时才会翻。 > 沿用 `probe_douyinN.py` 编号,方便按时间顺序回忆「当时试了什么」。 ### tests/ —— 测试 | 文件 | 性质 | 说明 | |---|---|---| | `run_tests.py` | 汇总器 | 一键跑默认批次;`--all` 连 E2E 一起跑 | | `test_ui_theme.py` | 离线 | 界面渲染 / 配色 / 标题栏 / 拖放 | | `test_batch_smoke.py` | 离线 | 模板字段 → 三平台表单回填、标题截断 | | `test_cover_logic.py` | 半离线 | 封面控件检测(起无头 Chromium,不上传) | | `test_douyin_engine.py` | **E2E** | 封面落地验色(需登录态) | | `test_douyin_ui_path.py` | **E2E** | UI→Worker→引擎 完整链路(需人工扫码) | E2E 两个**故意不在默认批次**:需要真实登录态,且有可能真的发作品。 ```bat python tests\run_tests.py :: 默认 3 个离线测试 python tests\run_tests.py --list :: 看清单 python tests\run_tests.py --all :: 连 E2E 一起(谨慎) python tests\test_ui_theme.py :: 单跑某个 ``` ### tools/ —— 辅助工具 造测试素材、调试封面、清理草稿、渲染预览图等,跟主程序运行无关。 | 文件 | 用途 | |---|---| | `make_test_media.py` / `make_douyin_test_media.py` | 造测试视频 | | `make_douyin_test_cover.py` | 造横竖两种比例的封面 | | `debug_cover.py` | 封面逻辑调试(`debug.bat` 调它) | | `clean_bili_drafts.py` | 清理 B站草稿箱 | | `render_batch_preview.py` | 渲染批量页预览图到 `tools/output/` | ### assets/ —— 图标素材 - `最小化.png` / `最大化.png` / `最大化 细.png` / `关闭.png`:标题栏按钮 (★ 映射按**图形**定,不是按文件名:`最大化.png` 画的其实是**双方框**=还原态) - `check.svg` / `chevron-down.svg` / `radio-on.svg`:QSS 引用 --- ## 运行时数据(`.data/`) **整个目录不入版本库**——它有 900MB+,且三个浏览器 profile 里存着**登录态 Cookie**。 配置默认值写在 `config.py`,首次运行 `ensure_dirs()` 会自动重建目录与默认配置。 | 路径 | 内容 | |---|---| | `config.json` | 视频号定位符覆盖、引擎参数 | | `bili_config.json` / `douyin_config.json` | 同上,各平台独立 | | `templates.json` | 发布模板(**v2 格式**,`platforms` 按平台分份;旧格式自动升级) | | `browser_profile/`、`bili_profile/`、`douyin_profile/` | 登录态(**敏感**) | | `*_shots/`、`douyin_debug/`、`ui_shots/` | 失败截图与调试产物 | | `ui_shots/` | 界面截图(README 里那几张的来源) | > ⚠️ **`.data/` 是用户真实数据**。测试脚本碰它必须**先备份再还原** > (`test_batch_smoke.py`、`test_douyin_ui_path.py` 内置了这种保护)。 > 换机器时你手工调过的定位符覆盖值会丢失,需要在新机重调或手改 json 拷过去。 --- ## 常用命令 ```bat run.bat :: 启动程序 debug.bat :: 跑封面调试(tools\debug_cover.py) ``` --- ## 定位符系统 平台改版是常态,所以每个交互点都配了**可覆盖的定位符**,默认值在 `config.py::DEFAULT_SELECTORS` (抖音/B站在各自引擎文件里)。语法: | 前缀 | 含义 | |---|---| | `css=` | CSS 选择器 | | `xpath=` | XPath | | `text=` | 精确文本匹配 | | `text*=` | 包含文本匹配 | | `placeholder=` | 按 placeholder 找输入框 | | `role=` | ARIA role(可配 `name`) | 每个键给一个**列表**,引擎按顺序尝试,命中第一个可用的即停止。 界面右上角「定位符设置」可直接改,改完写回 `.data/*_config.json`。 --- ## ⚠️ 改代码前必读 几条已踩过的坑(完整版在 `.workbuddy/memory/MEMORY.md`,改代码前建议扫一遍): - **PyQt 渲染** - 控件上**不要写局部 QSS**(`setStyleSheet`):Fusion 会改走 QStyleSheetStyle, 既吞掉 `setIcon` 的图标,又用 QSS 的 `min-height` 覆盖 `setFixedHeight`。 正解:只 `setObjectName()`,样式统一写进 `ui_theme.build_qss()`;状态差异用动态属性。 - `devicePixelRatioF()` 在控件**未显示**时返回 0,喂给 `QPixmap(0,0)` 会让窗口 `show()` 时原生层致命崩溃(exit 127,**faulthandler 抓不到**)。凡是要喂 dpr 给 QPixmap/QSize 的地方一律钳到 `[1,4]`。 - QSS 不支持 8 位十六进制色 `#RRGGBBAA`,半透明必须写 `rgba(r,g,b,a)`。 - **配色**:项目基调是**冷静低饱和**。主色 `#3f6ea8`、底色 `#eef1f5`, 语义色全部降饱和(不用刺眼纯红纯绿)。**改色只改 `ui_theme.PALETTE`**, 主程序里不要另设 QSS。 - **滚动区里不要给控件 `stretch>0`**:会被无限拉高并把下方控件顶出可视区。 批量页所有外层布局 `stretch=0` + 末尾 `addStretch(1)`。 - **批量页包了 `DropForwardScrollArea`**:滚动区会把 `dropEvent` 收走, 必须由它转发给内部 panel(滚动区 + viewport 都要 `setAcceptDrops(True)`), 改动时别把转发逻辑删了。 - **抖音封面编辑器**:新版是**内嵌主页面**的向导(不是弹窗),**没有「保存」按钮**。 正确顺序是选封面 → 上传 → 等「封面上传中」消失 → **在画布上做一次覆盖式拖拽** → 点「完成」。少那次拖拽,React 裁剪状态不会 commit,封面不生效。 - **Semi 下拉是虚拟列表**:DOM 只渲染可见项(25 个合集第一屏只有 6 个), 必须边滚边找,只扫当前 DOM 会对深处的项误报"未找到"。 --- ## 许可 内部项目,未指定公开许可。