From 063818e086eda1963f7d48906e1d85654c3dd48a Mon Sep 17 00:00:00 2001 From: butubb <1422726308@qq.com> Date: Mon, 12 Oct 2026 11:04:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=88=87=E5=8F=B7):=20=E5=88=87=E5=AE=8C?= =?UTF-8?q?=E5=8F=AA=E7=AD=89=EF=BC=88=E9=BB=98=E8=AE=A4=2015=20=E7=A7=92?= =?UTF-8?q?=EF=BC=89=E4=B8=8D=E5=86=8D=E5=8E=BB=E7=82=B9=E3=80=8C=E6=88=91?= =?UTF-8?q?=E3=80=8D=E5=88=B7=E6=96=B0=20=E2=80=94=E2=80=94=20wait=5Fafter?= =?UTF-8?q?=20=E5=8F=AF=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户实测要求:"去掉切完账号下拉刷新这一步,直接改成等待 15 秒"。 原来的做法是"回读不到就再点一次「我」页签"—— 在设备上看着像去点刷新,还可能打断 抖音换号后的整体重载。 · tasks/generic/task.py:切完后按 wait_after(默认 15s,步骤参数可调)静等,等完只回读、 **不再点任何东西**(读不到就多读几次,页面渲染有零点几秒抖动) · 参数落到位:STEP_TYPES / 骨架 build_switch_step / 编辑器面板 / 发布页就地编辑字段表 · 测试:用例里 wait_after=0.2(不然一跑就一分多钟) · 文档:TASK_DEV §3 的 switch_account 行 + research/U2_ELEMENT_SELECTORS §七(记下"只能等"这条实测教训) --- core/video_plan.py | 2 +- doc/TASK_DEV.md | 2 +- doc/research/U2_ELEMENT_SELECTORS.md | 1 + scripts/tests/test_switch_account.py | 4 +++- static/admin/editor.js | 2 +- static/admin/release.js | 1 + tasks/generic/task.py | 22 ++++++++++++---------- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/core/video_plan.py b/core/video_plan.py index a8e57c9..4427cdb 100644 --- a/core/video_plan.py +++ b/core/video_plan.py @@ -496,7 +496,7 @@ def build_switch_step(label="切换账号(切到本次要发的那个名字) "switch_selector_value": DOUYIN_SWITCH_SELECTOR_VALUE, "name_selector_type": "", "name_selector_value": "", "me_selector_type": "descriptionContains", "me_selector_value": "我", - "timeout": 8, "verify": True, "max_attempts": 2, + "timeout": 8, "wait_after": 15, "verify": True, "max_attempts": 2, "on_login_page": "stop", "probability": 100}} diff --git a/doc/TASK_DEV.md b/doc/TASK_DEV.md index 43e0b45..da07c57 100644 --- a/doc/TASK_DEV.md +++ b/doc/TASK_DEV.md @@ -141,7 +141,7 @@ from .generic import task # 触发 @register_task(当前唯一任务类型) | 23 | `push_release` | 推送发布视频 | `date_source`(today/fixed)、`fixed_date` | `account`(name/phone)、`account_phone`、`album_dir`(/sdcard/DCIM/Camera)、`to_clipboard`(True)、`retry_failed`(True)、`max_same_run`(1) | — | 取**本次要发的那个名字**名下的「发布计划」→ **只把素材推到手机**(+ 触发相册刷新 + 标题写进剪贴板)。**不发布** —— 抖音流程你自己在后面画。`account=name`(默认)时**推送前会重读昵称核对**,对不上就不推。幂等靠原子占位(多设备/重跑不会重复推)。见 §4.7 | | 24 | `mark_release` | 标记发布结果 | `result`(published/failed/unknown)、`why` | `capture_link`(True)、`delete_phone`(True)、`album_dir` | — | 放在**你自己发布流程的末尾**:把"发出去了没有"回写到计划行(成功时顺手抓作品分享链接、删手机上的素材)。拿不准就填 `unknown` —— 平台不会自动重发它。见 §4.7 | | 25 | `read_account_name` | 读取本机账号名字 | `selector_value`(昵称控件;值填在 `.env` 的 `DOUYIN_NAME_SELECTOR_VALUE`,40.6.0 实测值见 [research/U2_ELEMENT_SELECTORS.md](research/U2_ELEMENT_SELECTORS.md) §七) | `selector_type`(xpath)、`timeout`(8)、`on_missing`(fail) | — | 读抖音「我」页面上的**昵称** → 清洗后存进运行期变量(`account_name`),并记下用的选择器供 `push_release`/`switch_account` 核对。**读不到 = 本机没有号**:条件判断走 `else`、推送跳过(都不发)。`on_missing=fail`(默认) 让这一步直接失败、`skip` 只记一句继续。见 §4.7 | -| 26 | `switch_account` | 切换账号 | `switch_selector_value`(进「账号切换」列表那一下;填 `.env` 的 `DOUYIN_SWITCH_SELECTOR_VALUE`,实测值同上) | `target`(`var:target_name`)、`target_value`、`name_selector_*`、`me_selector_*`(descriptionContains=我)、`timeout`(8)、`verify`(True)、`max_attempts`(2)、`on_login_page`(stop) | — | **只在本机已登录过的号之间切**:当前已是目标→直接成功;否则点「我」→ 点切换入口 → **在列表里按名字找控件点击**(**禁 `[N]` 序号**:序号型会随界面变形错位)→ 等重载 → **回读校验**。列表里没这个名字 = 本机没登这个号 → 立刻 False(不重试、不算故障)。停在登录/验证码页 → 通知 + 默认停本设备(平台不做登录/滑块)。见 §4.7 | +| 26 | `switch_account` | 切换账号 | `switch_selector_value`(进「账号切换」列表那一下;填 `.env` 的 `DOUYIN_SWITCH_SELECTOR_VALUE`,实测值同上) | `target`(`var:target_name`)、`target_value`、`name_selector_*`、`me_selector_*`(descriptionContains=我)、`timeout`(8)、**`wait_after`(15)**、`verify`(True)、`max_attempts`(2)、`on_login_page`(stop) | — | **只在本机已登录过的号之间切**:当前已是目标→直接成功;否则点「我」→ 点切换入口 → **在列表里按名字找控件点击**(**禁 `[N]` 序号**:序号型会随界面变形错位)→ 等重载 → **回读校验**。列表里没这个名字 = 本机没登这个号 → 立刻 False(不重试、不算故障)。**切完先等 `wait_after` 秒(默认 15)再回读** —— 换号会触发抖音整体重启,中途去点任何东西(包括点「我」页签那一下『刷新』)都可能打断它加载。停在登录/验证码页 → 通知 + 默认停本设备(平台不做登录/滑块)。见 §4.7 | | 27 | `foreach_account` | 循环本机待发的号 | — | `date_source`(today/fixed)、`fixed_date`、`retry_failed`(True)、`max_accounts`(0=不限) | `children` | **一轮把本机待发的号跑完**:数据源 = 计划表里还有待发内容的名字(`pending_names`,**不需要账号台账**),逐个写进运行期变量 `target_name` 后跑 children;试过的名字不回头(防死循环);内层"这个名字还有待发就一直发",跑一轮没进展(切号失败/守卫没过)就换下一个。三个干净出口:停止 / 任务时长上限 / `max_accounts`。见 §4.7 | ### 3.2 录制手势:**纯录制、纯回放**(`core/gesture.py`) diff --git a/doc/research/U2_ELEMENT_SELECTORS.md b/doc/research/U2_ELEMENT_SELECTORS.md index 9a6936b..0d13a52 100644 --- a/doc/research/U2_ELEMENT_SELECTORS.md +++ b/doc/research/U2_ELEMENT_SELECTORS.md @@ -218,6 +218,7 @@ ### 实测记录 - 冷启动(`app_stop` → `app_start`)后切号:**17.2s** 成功;热态切号 12.9s +- ⚠ 切完之后**只能等**:实测『回读不到就再点一次「我」』会被当成去点刷新、还可能打断抖音重载,用户直接要求『别刷新,等 15 秒』 → 现在是 `wait_after`(默认 15s,步骤参数可调)等完再回读 - ⚠ **第一次实现踩的坑**:把"进列表"后的等待写成**固定 sleep(1.2s) 再找一次**, 冷启动时列表还没渲染出来 → 判成"本机没登这个号" → **漏发**(还不会报错,只是不发)。 现在两处都改成**轮询到 deadline**(`max(6s, timeout)`):等面板出目标行、等回读变成目标名。 diff --git a/scripts/tests/test_switch_account.py b/scripts/tests/test_switch_account.py index 1b73098..fc5b331 100644 --- a/scripts/tests/test_switch_account.py +++ b/scripts/tests/test_switch_account.py @@ -117,7 +117,9 @@ class StubD: PARAMS = {'target': 'var:target_name', 'switch_selector_type': 'xpath', 'switch_selector_value': '//switch', 'name_selector_type': 'xpath', - 'name_selector_value': '//name', 'timeout': 1, 'max_attempts': 2} + 'name_selector_value': '//name', 'timeout': 1, 'max_attempts': 2, + # 切完后等它重载的秒数(真机默认 15s)—— 用例里调小,不然一跑就是一分多钟 + 'wait_after': 0.2} notes = [] orig_notify = gt.notifier.notify diff --git a/static/admin/editor.js b/static/admin/editor.js index 5078df4..9605638 100644 --- a/static/admin/editor.js +++ b/static/admin/editor.js @@ -22,7 +22,7 @@ var STEP_LIB=[ {type:'if_el',label:'条件判断',icon:'❓',cat:'flow',params:{selector_type:'xpath',selector_value:'',timeout:3,ocr_click:false,cmp_op:'',cmp_value:'',cmp_source:'',cmp_group:'',ident_type:'text',ident_value:'',then:[],else:[]}}, {type:'mark_done',label:'记为已做',icon:'✅',cat:'flow',params:{selector_type:'text',selector_value:''}}, {type:'read_account_name',label:'读取本机账号名字',icon:'🏷',cat:'flow',params:{selector_type:'xpath',selector_value:'',timeout:8,on_missing:'fail'}}, - {type:'switch_account',label:'切换账号',icon:'🔁',cat:'flow',params:{target:'var:target_name',target_value:'',switch_selector_type:'xpath',switch_selector_value:'',name_selector_type:'',name_selector_value:'',me_selector_type:'descriptionContains',me_selector_value:'我',timeout:8,verify:true,max_attempts:2,on_login_page:'stop'}}, + {type:'switch_account',label:'切换账号',icon:'🔁',cat:'flow',params:{target:'var:target_name',target_value:'',switch_selector_type:'xpath',switch_selector_value:'',name_selector_type:'',name_selector_value:'',me_selector_type:'descriptionContains',me_selector_value:'我',timeout:8,wait_after:15,verify:true,max_attempts:2,on_login_page:'stop'}}, {type:'foreach_account',label:'循环本机待发的号',icon:'🔂',cat:'flow',params:{date_source:'today',fixed_date:'',retry_failed:true,max_accounts:0,children:[]}}, {type:'notify',label:'发通知',icon:'🔔',cat:'flow',params:{title:'',message:'',level:'info'}}, {type:'stop_self',label:'停止本设备',icon:'⛔',cat:'flow',params:{reason:''}}, diff --git a/static/admin/release.js b/static/admin/release.js index 9bbc156..5102d76 100644 --- a/static/admin/release.js +++ b/static/admin/release.js @@ -751,6 +751,7 @@ var _RP_FIELDS = { ['switch_selector_value', '选择器值(进「账号切换」列表那一下)', 'selval'], ['me_selector_value', '「我」页签的定位描述', 'text'], ['timeout', '超时(秒)', 'num'], + ['wait_after', '切完后等几秒再回读(默认 15)', 'num'], ['verify', '切完回读校验(对不上就不发)', 'chk'], ['max_attempts', '最多试几次', 'num'], ['on_login_page', '遇到登录页', 'sel', [['stop', '停本设备(推荐)'], diff --git a/tasks/generic/task.py b/tasks/generic/task.py index 8d129a0..973c226 100644 --- a/tasks/generic/task.py +++ b/tasks/generic/task.py @@ -138,7 +138,7 @@ STEP_TYPES = [ "switch_selector_type": "xpath", "switch_selector_value": "", "name_selector_type": "", "name_selector_value": "", "me_selector_type": "descriptionContains", "me_selector_value": "我", - "timeout": 8, "verify": True, "max_attempts": 2, + "timeout": 8, "wait_after": 15, "verify": True, "max_attempts": 2, "on_login_page": "stop"}}, {"type": "foreach_account", "label": "循环本机待发的号", "icon": "🔂", "params": {"date_source": "today", "fixed_date": "", "retry_failed": True, @@ -1254,23 +1254,25 @@ class GenericStepsWorker(BaseWorker): if not want_verify: self.set_action(f"已点击『{target}』(未校验)") return True - # 等切换生效:切换会触发抖音**整体重启**(等于换了个号重新登录),十几秒才回到 - # 「我」页是正常的(实测:整步跑了 77s,其中大部分就是在等它重启完)。而且重启后 - # 落的地方**可能是信息流**(不是「我」页)—— 所以窗口放到 30s,并且**读不到就重新点 - # 一次「我」**再读(踩过两次:窗口太短 → 明明切成功却判失败 → 整轮什么都不发)。 + # 切完之后**先等它自己重载**(默认 15 秒,`wait_after` 可在步骤参数里调)。 + # 抖音换号会整体重启,中途去点任何东西(包括再点一次「我」页签那一下"刷新") + # 都可能打断它加载 —— 实测用户的要求就是:"别刷新,等 15 秒就行"。 + wait_after = float(params.get("wait_after") or 15) + end = time.time() + max(0.0, wait_after) + while time.time() < end and not self.stopped(): + time.sleep(0.5) + # 等完再回读;**不再点任何东西**,只多读几次(页面渲染有零点几秒的抖动) last_got = "" - vdeadline = time.time() + max(30.0, timeout) - while time.time() < vdeadline: + for _ in range(5): if self.stopped(): return False last_got = self._read_name_now(d, params) if last_got == target: - _log.info(f"[{self.serial}] 切换账号成功:『{target}』(第 {i} 次)") + _log.info(f"[{self.serial}] 切换账号成功:『{target}』(第 {i} 次," + f"等了 {wait_after:.0f}s)") self.set_action(f"已切到『{target}』") self._vars["account_name"] = target return True - if not last_got: - self._click_sel(d, me_type, me_val, timeout=1) time.sleep(0.8) hint = self._page_has_text(d, self._LOGIN_HINTS) if not last_got else "" if hint: