Files
cmshoppe/docs/tasks/T-630.md
T

135 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-630
title: ①采集关闭商品页后的登录检测竞态修复
phase: 2
deps: [T-620]
status: TODO
created: 2026-07-14
---
## 问题 / 背景
①「导入采集」按同一账号连续处理多个商品时,上一条商品采集成功并关闭程序自动创建的商品 tab 后,下一条任务开始前的登录检测偶尔会出现:
```text
step=login_check result=retry detail=账号 <别名> 第1/3次登录检测暂不确定,2秒后重试:
原因=NO_SESSION_COOKIE,URL=<上一条商品详情页>,Cookie名称=未读到登录Cookie
```
用户容易把这条日志理解为“上一条商品首次新增 tab 采集失败,重试后才成功”。实际运行日志已经确认这是跨任务时序问题:
- run 353 中,事件 5082 表示任务 70、商品 48663456321 已采集成功;
- 紧接着的事件 5083 实际绑定任务 71、商品 52313423890,但可见 message 没有任务信息;
- 该登录检测返回的 URL 仍是刚完成的商品 48663456321;
- 任务 71 随后直接进入 `db_write/open_product/wait_ready` 并采集成功;
- 任务 72 开始前再次出现同样现象,登录检测 URL 又停留在任务 71 的商品页。
已核实的代码原因:
1. `CollectWorker` 在每条 eligible 任务进入采集前调用 `_confirmed_login_status()`。
2. `editor.collect()` 完成后由 `_close_collected_product()` 调用 `/json/close/<target_id>` 关闭本轮自动创建的商品 tab,但 Chrome target 从 `/json` 列表消失是异步的。
3. 下一条任务立即进入 `editor.login_status()`;当前实现从 `/json` 中用 `next(...)` 选择第一个 Shopee page,可能选中正在销毁、但仍短暂可见的上一条商品 target。
4. `_wait_for_login_probe()` 调用 `Network.getAllCookies` 失败时吞掉异常并把 `cookie_names` 改为空集合,无法区分“Cookie API 调用失败”和“Cookie API 成功但确实没有会话 Cookie”,最终误报 `NO_SESSION_COOKIE`。
5. 外层登录确认等待约 2 秒后重新执行完整检测;此时旧 target 已消失,因此第二次检测成功并继续采集。第一次页面探测默认最多等待约 8 秒,加上 2 秒重试间隔,单条任务可能无意义增加约 10 秒。
6. 单条采集 `elapsed_ms` 从登录检测之后才开始计算,因此这段等待不会出现在“采集成功 elapsed_ms”中,进一步造成用户看到的总等待与日志耗时不一致。
T-563 已保证 `NO_SESSION_COOKIE` 属于不确定状态,不会因此把同账号后续任务批量略过;T-620 也不会把它归类成明确未登录。本任务继续解决误报、额外等待和日志歧义,不改变这两项既有容错语义。
## 方案
### 1. 区分 Cookie 缺失与 CDP 探测失败
- 修改 `app/editor.py` 的登录探测返回结构,使 `_wait_for_login_probe()` 或等价 helper 至少保留:
- 当前 URL;
- 已成功读取到的 Cookie 名称;
- Cookie API 是否至少成功调用一次;
- 最近一次探测错误类别,不保存异常中的敏感值。
- `Network.getAllCookies`、URL 读取或 websocket 调用因 target 关闭而失败时,不得把结果伪装成空 Cookie 列表。
- 只有满足以下条件时才能返回 `reason=NO_SESSION_COOKIE`:
- 至少一次 Cookie API 调用成功;
- 页面 target 在该次探测中仍可用;
- 返回的 Shopee Cookie 名称确实不包含 `SPC_ST` / `SPC_U`。
- 如果整个探测周期没有一次成功的 Cookie 读取,应返回不确定原因,例如 `LOGIN_CHECK_TARGET_UNAVAILABLE` 或 `LOGIN_CHECK_FAILED`;外围中文日志说明“登录状态暂时无法读取”,不得显示成账号退出登录。
- 继续只记录 Cookie 名称,不记录 Cookie 值。
### 2. 失效 target 快速重选
- `editor.login_status()` 不再把一次登录检测永久绑定到最先枚举到的 Shopee page。
- 连接 page 失败、websocket 已关闭、`Network.getAllCookies` 持续失败或 target 已不在最新 `/json` 列表时,应在本次登录检测预算内重新枚举 page targets,并改连其他仍有效的 Shopee 页面。
- 有多个 Shopee target 时优先选择可连接且 URL 稳定的卖家中心页面;不得因为列表第一项是正在关闭的商品页就等待完整 8 秒。
- 如果当前没有有效 Shopee page,可沿用既有规则打开卖家中心根地址进行检测;不自动登录、不填密码。
- 显式命中 `accounts.shopee.<区域>/seller/login` 等登录 URL 时,仍返回明确 `LOGIN_PAGE`,不得被本任务放宽成不确定状态。
- 可评估通过浏览器级 CDP `Storage.getCookies` 读取 Cookie,使 Cookie 检测不依赖具体 page target;只有在真实 Chrome/CDP 验证可用、账号隔离边界正确并同步架构事实后才能采用。第一版不以未经验证的浏览器级接口作为完成前提。
### 3. 商品 tab 关闭完成确认
- 在 `app/cdp.py` 增加有上限的 target 关闭确认 helper,或在既有关闭接口中提供可选等待能力:调用 `/json/close/<target_id>` 后轮询 `/json`,直到该 target ID 消失。
- 等待必须短且有明确上限,建议总预算不超过 2 秒、轮询间隔约 0.1~0.2 秒;不得使用固定 `sleep(2)` 让每条任务无条件变慢。
- ①采集和⑥商品套图只对“本轮程序自动创建并需要关闭”的只读商品 tab 使用该确认;复用用户原有 tab 仍只断开 CDP,不关闭、不等待其消失。
- 关闭确认超时不能覆盖原本已经成功的采集结果;记录不确定诊断后,下一条登录检测仍须依靠第 1、2 节的容错完成有效 target 重选。
- 不改变③更新蝦皮成功提交后已有的 2 秒观察窗口和前台 tab 策略。
### 4. 登录重试日志可追踪
- `CollectWorker._confirmed_login_status(account, context, task)` 在 `task` 非空时,用户可见 retry/uncertain/recovered 日志必须包含当前任务 ID 和商品 ID,例如:
```text
step=login_check result=retry detail=任务 71 商品 52313423890 账号 <别名> 登录状态暂时无法读取,正在重新检测
```
- 如果第一次不确定、后续检测恢复已登录,应增加一条简洁中文恢复日志,明确本次继续的是哪条任务;不得让 retry 后直接跳到 `db_write` 而没有恢复结果。
- 底层原因、URL、Cookie 名称和尝试次数继续写结构化运行日志/本地诊断日志;普通可见文案不得裸露 websocket、异常堆栈或 Cookie 值。
- 登录检测耗时可写 `elapsed_ms` 作为诊断字段,但不修改现有采集成功 `elapsed_ms` 的历史口径;界面 T-628 的“本条耗时”已经从 `task_started` 开始覆盖登录检测等待。
### 5. 测试与实机验证
- 更新 `tests/test_editor_login.py`:
- `/json` 第一项是正在关闭的旧商品 target、第二项是有效 Shopee target时,能快速改连有效 target并读取登录 Cookie;
- Cookie API 对旧 target 抛出“target closed”类错误时,不返回 `NO_SESSION_COOKIE`;
- Cookie API 成功返回但确实没有 `SPC_ST` / `SPC_U` 时,仍返回 `NO_SESSION_COOKIE`;
- 明确登录页仍返回 `LOGIN_PAGE`;
- 没有有效 Shopee page 时沿用打开卖家中心页的逻辑。
- 更新 `tests/test_cdp.py` 或对应 CDP 测试:
- target 很快消失时关闭确认成功;
- target 未在预算内消失时按超时结果返回,不无限等待;
- 不关闭用户原有 tab。
- 更新 `tests/test_gui.py` 的 `CollectWorker` 测试:
- 首次探测 target 失效、后续重选成功时,当前任务继续采集且不记略过/失败;
- retry 和 recovered 日志包含当前任务 ID、商品 ID;
- 连续不确定仍保持 T-563 的“继续尝试当前商品、不批量略过”语义;
- 明确 `LOGIN_PAGE` 仍整组进入需登录分类。
- 使用测试账号连续采集至少 3 个测试商品,确认:
- 上一条自动新建商品 tab 关闭后,下一条不会因旧 URL 产生假 `NO_SESSION_COOKIE`;
- 不再每条额外等待约 8~10 秒;
- 每条只读商品 tab 按既有规则关闭,Chrome 不残留程序新建的商品页;
- 采集标题、封面下载和 SQLite/Excel 结果不回归。
## 验收要点
- 连续采集同账号多个商品时,下一条登录检测不会连接刚关闭的上一条商品 target并误报 `NO_SESSION_COOKIE`。
- Cookie API 调用失败和成功返回空 Cookie 是两个不同状态;任何 CDP异常都不能伪装成账号缺少会话 Cookie。
- target 关闭竞态能在短预算内恢复,不等待完整登录超时再由外层重试。
- 上一条采集成功不会因关闭确认超时被改写为失败;下一条仍能继续。
- 日志能明确看出 retry/recovered 属于哪条任务和商品,不再与上一条采集成功日志混淆。
- `LOGIN_PAGE` 仍是明确未登录;`NO_SESSION_COOKIE` / target 暂不可用仍不批量略过后续任务。
- ①采集继续后台只读打开商品页;不切换 Chrome 前台,不影响③更新蝦皮的前台交互策略。
- 自动验证:
- `py -3.10 -m unittest tests.test_editor_login`
- `py -3.10 -m unittest tests.test_gui`
- `py -3.10 -m unittest discover -s tests`
- `python -m ruff check app tests main.py`
- `py -3.10 -m compileall app main.py`
- `git diff --check`
## 边界(不改什么)
- 不删除采集前预检或采集中途登录检测,只修正探测目标、状态语义和等待方式。
- 不通过增加固定 sleep、放大登录超时或把所有 `NO_SESSION_COOKIE` 当作已登录来掩盖问题。
- 不自动登录、不填写账号密码、不读取或记录 Cookie 值、不绕过验证码和风控。
- 不修改商品详情页选择器、采集标题/封面逻辑、Excel/SQLite schema、AI生成或③更新蝦皮流程。
- 不为解决本问题把①采集改回前台打开商品页;该竞态发生在登录检测选择已关闭 target,与前后台展示无关。
## 执行记录
- 待实现。