9.8 KiB
9.8 KiB
id, title, phase, deps, status, created
| id | title | phase | deps | status | created | |
|---|---|---|---|---|---|---|
| T-630 | ①采集关闭商品页后的登录检测竞态修复 | 2 |
|
TODO | 2026-07-14 |
问题 / 背景
①「导入采集」按同一账号连续处理多个商品时,上一条商品采集成功并关闭程序自动创建的商品 tab 后,下一条任务开始前的登录检测偶尔会出现:
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 的商品页。
已核实的代码原因:
CollectWorker在每条 eligible 任务进入采集前调用_confirmed_login_status()。editor.collect()完成后由_close_collected_product()调用/json/close/<target_id>关闭本轮自动创建的商品 tab,但 Chrome target 从/json列表消失是异步的。- 下一条任务立即进入
editor.login_status();当前实现从/json中用next(...)选择第一个 Shopee page,可能选中正在销毁、但仍短暂可见的上一条商品 target。 _wait_for_login_probe()调用Network.getAllCookies失败时吞掉异常并把cookie_names改为空集合,无法区分“Cookie API 调用失败”和“Cookie API 成功但确实没有会话 Cookie”,最终误报NO_SESSION_COOKIE。- 外层登录确认等待约 2 秒后重新执行完整检测;此时旧 target 已消失,因此第二次检测成功并继续采集。第一次页面探测默认最多等待约 8 秒,加上 2 秒重试间隔,单条任务可能无意义增加约 10 秒。
- 单条采集
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,例如:
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 结果不回归。
- 上一条自动新建商品 tab 关闭后,下一条不会因旧 URL 产生假
验收要点
- 连续采集同账号多个商品时,下一条登录检测不会连接刚关闭的上一条商品 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_loginpy -3.10 -m unittest tests.test_guipy -3.10 -m unittest discover -s testspython -m ruff check app tests main.pypy -3.10 -m compileall app main.pygit diff --check
边界(不改什么)
- 不删除采集前预检或采集中途登录检测,只修正探测目标、状态语义和等待方式。
- 不通过增加固定 sleep、放大登录超时或把所有
NO_SESSION_COOKIE当作已登录来掩盖问题。 - 不自动登录、不填写账号密码、不读取或记录 Cookie 值、不绕过验证码和风控。
- 不修改商品详情页选择器、采集标题/封面逻辑、Excel/SQLite schema、AI生成或③更新蝦皮流程。
- 不为解决本问题把①采集改回前台打开商品页;该竞态发生在登录检测选择已关闭 target,与前后台展示无关。
执行记录
- 待实现。