 q792602257andClaude Opus 5
|
3284e086ad
|
feat(trading): 浏览器掉线兜底——任务边界自愈重建,中途掉线转 needs_human
SiteInteractor 的 Chromium 是进程级单例,此前启动后即假定永远活着:全仓唯一的
is_connected() 探活在 scraping 侧,交易侧既不探活也不重启。容器里 Chromium 崩溃
是有真实前提的(/dev/shm 不足、OOM kill、seccomp 挡 sandbox,docker-compose.yml
里已有相关注释),一旦发生,进程还活着但之后每一单都会失败,且 /health 恒返回
ok,restart: unless-stopped 永远不会被触发。
更隐蔽的一条:clear_cart / enter_checkout / pay 等处的 new_page()、context.request
写在 try 之外,掉线抛的 TargetClosedError 不是 AppError,会穿过 runner 的
except AppError 落到主循环那个只记日志的兜底里——任务一次都不上报,网关侧要干等
整个 lease_ttl(默认 300s)才被 sweep 置 stale。clear_cart 是 execute() 的 step 0,
浏览器死时最先撞上的正是它。
分三层处理:
- 任务边界自愈。_launch() 从 start() 抽出,_context_options() 统一 context 参数
(重建必须与启动完全一致,指纹漂移就是一次风控事件);_refresh_context_if_stale
改名 _ensure_context_ready(),先探活重建再做原有的 storage_state mtime 检查
(顺序不可换,mtime 重建要用 self._browser)。重建前丢弃 _checkout_pages 里的
残留确认页并记 warning——那些 Page 已随浏览器一起没了。
- 中途掉线不重建,抛 BrowserDeadError(新增,5006)。新增 _new_page() 与
_request() 两个壳收口裸异常;_request() 只在确认浏览器真死了时才改写异常,
站点 5xx 这类正常业务失败原样抛出。submit_order / pay 入口用
_require_live_browser() 直接拒绝:这两步复用 enter_checkout 留存的 Page,
重建救不回服务端订单草稿,而 pay 跑的时候订单已经真的提交了。顺带修掉一个
误诊——浏览器死时 submit_order 原先报「未找到确认按钮」,把「浏览器崩了」
说成「站点改版了」,两者的处置方式完全不同。
- runner 把 BrowserDeadError 转 needs_human 而非 failed,except 分支排在
except AppError 之前(子类,顺序反了就报 failed)。掉线发生在动作中途,
站点侧生效与否无从判断,不能给上游「明确失败」的结论。
/health 暴露 browser 状态,掉线时 degraded + HTTP 503 + code 5006,让
Dockerfile.trading 的 HEALTHCHECK 探到并重启容器。重启不会导致重复下单:网关侧
任务绝不自动重投,租约过期只置 stale 等人工 reclaim(docs/order-gateway.md §5),
重启只是恢复领新任务的能力。启动窗口期 started=False 不算掉线。
README 错误码表补 5005(此前遗漏)与 5006。
新增 15 个用例覆盖探活三态、is_connected() 自身抛错、边界重建/不重建/丢弃残留页/
重建失败、两个包装壳的分支、submit/pay 拒绝、runner 转 needs_human、/health 503。
真实 Chromium 崩溃无法在离线测试里制造,用例模拟的是 is_connected() 返回 False
这个唯一可观测信号,覆盖的是代码对该信号的反应而非崩溃本身;容器 HEALTHCHECK
真的触发重启这条链路尚未实跑验证。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-08-20 16:01:47 +08:00 |
|
q792602257
|
f31e127ba7
|
feat(trading): 下单流程每步证据带整页截图(html + png + meta 三件套)
- site_interact 新增 PageSnapshot{html, screenshot} 证据载体:
add_to_cart 截商品页、verify_cart 截 cart 页、enter_checkout 截落地
确认页、pay 截付款检查后的完成页;截图全部 best-effort,失败记
warning 留空,不掩盖动作结果
- submit_order 改返回 SubmitOutcome{site_order_id, evidence}:完成页
HTML+截图首次随 step 4 落盘(此前只有 meta)
- fetch_order_detail 详情页截图挂在 OrderStatusSnapshot.screenshot,
监控步骤 write_step 带 png;只读查询通道按字段挑出,不受影响
- runner._run_step 认 PageSnapshot(旧 str 契约保留兼容),step 0/3/4
显式传 png
- 测试:桩同步新契约;新增每步三件套齐全的全流程断言;clear_cart
场景单测补截图断言
- 真账号复验 verify_cart_clear.py:两场景截图均合法 PNG,落盘
.probe/cart_clear/ 并目检为空车页渲染
|
2026-08-17 08:56:11 +08:00 |
|
q792602257
|
086ab82614
|
feat(trading): 清空购物车落步骤证据,补空车/有商品两场景测试
- clear_cart 返回清理后 cart 页最终 HTML(best-effort,抓取失败不掩盖清理结果)
- runner step 0 落 00-cart-clear.{html,meta.json} 并登记 evidence_index,
证据先于闸门判断落盘(清理未净被拦时这份现场就是排查依据);
仍是本机卫生步骤:不上报 gateway、不记 order_events
- /api/cart/clear 响应不携带 html(路由显式挑字段,与 cart_add 同风格)
- 单测:fake page 覆盖空车/有商品/HTML 抓取失败;runner 层覆盖证据落盘
与闸门拦截场景
- 真账号复验 scripts/verify_cart_clear.py:空车 removed=0/count=0;
加购 1 件后 clear removed=1/count=0,status 复核 101
|
2026-08-17 08:32:11 +08:00 |
|
q792602257
|
daa5555fd4
|
fix(trading): cart count status=101 识别为空车,区分空车与获取失败
|
2026-08-17 01:42:08 +08:00 |
|
q792602257
|
3b53b8f28b
|
@
feat(trading): 详情页派送到真实样本,补结构化配送状态并修 stepper 回归
用真账号实测爬取订单详情页(scripts/probe_order_detail.py,样本落盘
.probe/order_detail/),首次拿到 pageType="ph-detail" 的真实 __INITIAL_STATE__,
此前「详情页结构从未有样本、只原样透传」的缺口由此闭合:
- _parse_order_detail_status:新增,优先从 orderData.shippingList[].deliveryInfo
.deliveryStatus 结构化枚举判配送阶段;映射遵循「只映射实测值」,目前仅
CHECKING_ORDER,未识别枚举交回 stepper 兜底(防掐掉 SHIPPED/DELIVERED 上报)。
- _ORDER_STEPPER_ITEM_PATTERN:修回归——不锚定 <li class="item--3gWCU"> 时面包屑
li 会抢走进度条第0项、导致 -active-- 永远判不出当前阶段(真实样本复现)。
- OrderStatusSnapshot 增 delivery_status 字段;query_runner order_detail 透传。
- docs/order-gateway.md §11 更新实测边界;新增详情页样本回溯测试。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@
|
2026-08-16 23:04:59 +08:00 |
|
 q792602257andClaude Opus 5
|
c03158488b
|
feat(gateway): 账号只读查询通道——从已登录账号取真实订单
上游要的不只是网关记的任务状态镜像,还有「已登录账号在站点上的真实订单」,
但账号只在 NAT 后本地机上,只能经网关队列走。新增独立查询通道(§11):
- gateway 单开 account_queries 表 + QueryStatus 状态机,接口
POST /api/account/queries(幂等)/ lease / {id}/result / {id}
- 不复用下单任务队列:查询是只读,租约过期可安全重投(与下单「绝不自动
重投」相反),且不该被全局并发度 1 堵死、task_reports 是订单镜像不能污染
- 本地交易服务起第二条常驻循环 query_runner,领到即调 SiteInteractor 真读:
order_list 复用已实测的 list_recent_orders(规范化字段 + 站点
orderListData 原文),order_detail 复用 fetch_order_detail(配送阶段 +
页面 __INITIAL_STATE__ 原样透传,结构未经真实样本,不抽字段)
- 账号级串行仍由 SiteInteractor 的锁保证;每次执行套超时按失败回报
- 错误码 6005/6006(查询通道,可重试只读区别于 6001-6004);/health 暴露
queued_query_count;结果体积上限先丢原始 JSON
openapi.json 重导,docs/order-gateway.md §11、README、.env.example 补全
配置与实测边界。全量测试 404→454 通过。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-08-16 22:46:01 +08:00 |
|
 q792602257andClaude Opus 5
|
e2875f0c00
|
自动重登修两处:并发去重读错缓存、只读订单查询掉登录被静默吞掉
- try_relogin 的并发去重原本读 status().logged_in,但重登成功后的 reload()
会把它重置成 None,排队在 site 锁上的调用方一律判「还没人登上」,N 个并发
调用会串行触发 N 次真实登录(各自最长 relogin_timeout)。改用 _relogin_epochs
计数:等锁期间 epoch 变过就真探测一次,已登录即跳过;仍未登录说明这轮站点侧
就是登不上(验证码/密码错/风控),直接失败,不在同一波并发里重复触发。
原并发测试的桩自相矛盾(login_one 返回成功、探针页始终回未登录),断言只能
松到 count >= 1;桩改为登录成功时翻转探针页,断言收紧到 count == 1。
- check_order_status / list_recent_orders 执行中掉登录此前会被静默吞掉:订单页
被踢到 SSO 后既不报错也没订单号,_parse_order_status 返回 found=False 被
_monitor_order 当成「订单还没反映出来」继续轮询(默认 3 小时一轮),
_parse_order_list 则退化成空列表让 verify_on_site 转 unknown 卡住等人工。
新增 SiteInteractor._read_with_relogin_retry 外壳:只读操作中途判定掉登录时
重登一次并整个重跑,第二次仍失败抛 NotLoggedInError。只给读操作用——写操作
中途掉登录不能重跑(上次动作可能已在站点侧生效),这条边界在两边文档里写明。
- 判据是新增的 auth_site.looks_logged_out:与探针页上权威的 is_logged_in 分开,
它是业务页上的单边启发式(返回 False 不代表登录着),只用于「判错最多多花一次
重登」的重试决策。刻意排除 session/upgrade——那是已登录时的站点风控复核密码,
不是 cookie 过期,误判会把风控当掉登录去重登。
判据里「掉登录会跳到 SSO 域」这一步没有真实探测证据(要复现得先让一份真实登录态
过期),是按站点通行行为的推断,已在常量注释标注;新增测试用替身页面,不是真实
站点 HTML。399 测试全绿(仓库未配 ruff/flake8/mypy,只跑了 pytest)。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
2026-08-14 14:05:47 +08:00 |
|
 q792602257andClaude Sonnet 5
|
295fc7ad69
|
结算失败关键节点落调试快照(HTML+截图),方便排查 needs_human 场景
enter_checkout/submit_order/pay 抛错前尽力落一份页面快照到
evidence_dir/{task_id}/debug-*,与已有编号步骤证据同目录;快照方法自身
失败只记日志,不会掩盖原始异常。之前失败时只有日志文字,要复现现场只能
靠临时探针脚本重新触发一遍真实流程。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
2026-08-14 02:07:12 +08:00 |
|
q792602257
|
93c1a83406
|
实现租约恢复核对:verify_on_site 接真实订单列表反查
新增 SiteInteractor.list_recent_orders(分页拉 order.my.rakuten.co.jp 订单列表,
按商品 URL 反查)+ GatewayClient.get_task,替换掉恒返回 UNKNOWN 的桩。
NOT_ORDERED 分支目前只有逻辑验证、没有真实多单数据支撑,刻意仍路由到
needs_human,不自动重新下单。全程只读查询,未触发任何真实付款操作。
|
2026-08-13 23:58:54 +08:00 |
|
 q792602257andClaude Sonnet 5
|
51e2538438
|
实现付款后订单监控:真实探测 order.my.rakuten.co.jp 配送阶段
用真实订单号 306087-20260813-0863947697 探测 order.my.rakuten.co.jp(订单列表/
详情页),拿到真实 DOM 结构后实现 SiteInteractor.check_order_status:
- 详情页 URL 可直接从 site_order_id 构造(shop_id 是订单号第一段)
- 配送阶段用「进度条」组件的 4 个固定阶段(ショップ/出荷/配達店/配達完了),
当前阶段的 class 带 -active-- 中缀,映射到 OrderState.SHIPPED/DELIVERED
- 查不到订单号、进度条解析不出新阶段都不算错误,交给轮询循环继续重试
runner.WorkerRunner 新增 _monitor_order 后台轮询:付款成功上报后以
asyncio.create_task 起后台任务(不阻塞主循环领下一单,因为 SiteInteractor 的
Playwright 操作全程持锁串行化),状态变化时用 terminal=False 追加 report;
新增 cancel_monitors() 在服务关闭时于 SiteInteractor.close() 之前收尾。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
2026-08-13 23:01:35 +08:00 |
|
 q792602257andClaude Sonnet 5
|
5f7921b788
|
结算流程用真实下单证据修正:新卡代填 iframe vault、订单号正则、登录态判据
- 新卡代填改用真实 DOM 结构:卡号/有效期分别托管在 Rakuten PCI 代付 vault 的
跨域 iframe 里,此前按 autocomplete/name 猜的 selector 在主文档里根本找不到
元素;持卡人姓名字段改用「名義人」标签相对定位,不用 placeholder 示例文案
(TARO RAKUTEN 只是示例用户名,不是稳定标识)
- 订单号正则修正 实体导致的分隔符匹配失败(2026-08-13 真实下单验证)
- login_runner._is_logged_in 改用与 AuthSession 一致的 __INITIAL_STATE__ 判据,
修掉此前误判「已登录」导致保存无效 storage_state 的问题
- 新增 CheckoutBlockedError:站点风控拦截(session upgrade/3DS)转 needs_human,
不当普通失败重试
- account.yaml 支持 phone / payment.credit-card 字段
- data/evidence/(真实 PII)加入 .gitignore
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
2026-08-13 22:36:49 +08:00 |
|
 q792602257andClaude Opus 4.6
|
18cf7ae079
|
实现 site_interact 加购与购物车校验(Playwright 通道)
探针实测发现 httpx 因 TLS/HTTP2 指纹被 Rakuten 拒认(cookie 有效但
站点不识别 session),site_interact 改为全程 Playwright + storage_state。
add_to_cart 与 verify_cart 已实测可用:从商品页 __INITIAL_STATE__ 抽
purchase 块、调用方补 variant_id 与 choice、POST basketDomain、用 cart
count JSONP API 与 SPA 渲染后的 cart 页校验。enter_checkout 及之后仍
NotImplementedError:Rakuten 对 checkout 要求 session upgrade(重输密码),
是自动 checkout 的硬墙,未实测过墙方案前留接口缝。
SiteInteractor 类持有 Playwright BrowserContext,由 WorkerRunner 实例
持有、container/main 接线、lifespan 管理生命周期。测试侧 23 个用例,
全套件 333 passed,架构边界守住。
附 5 个探针脚本(scripts/probe_*.py),记录实测路径与响应结构。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
2026-07-27 17:59:30 +08:00 |
|