fix(trading): cart count status=101 识别为空车,区分空车与获取失败
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"""cart count API 探针:实测「空购物车」与「获取失败」两种响应形态
|
||||
|
||||
背景:_query_cart_count 目前只认 status=="100",其余一律 CartOperationError。
|
||||
需要真账号确认:购物车为空时 cart-api.step.rakuten.co.jp 到底返回什么——
|
||||
如果空车响应与「请求被拒」长的不一样却被同一错误吞掉,就无法区分
|
||||
「车是空的」与「获取失败」。
|
||||
|
||||
只读探测,不做任何加购/删除操作。
|
||||
|
||||
用法:
|
||||
.venv/Scripts/python.exe scripts/probe_cart_count.py
|
||||
.venv/Scripts/python.exe scripts/probe_cart_count.py --headful
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from app.shared.config import get_settings # noqa: E402
|
||||
from app.trading.core import auth_site # noqa: E402
|
||||
|
||||
PROBE_DIR = Path(__file__).resolve().parent.parent / ".probe" / "cart_count"
|
||||
PROBE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
CART_COUNT_API = "https://cart-api.step.rakuten.co.jp/rms/mall/cart/count/all/jsonp/"
|
||||
CART_PAGE = auth_site.RAKUTEN_CART_URL
|
||||
|
||||
|
||||
def save(name: str, content: str) -> Path:
|
||||
path = PROBE_DIR / name
|
||||
path.write_text(content, encoding="utf-8")
|
||||
print(f" saved -> {path} ({len(content)} bytes)")
|
||||
return path
|
||||
|
||||
|
||||
async def call_count_api(context, *, referer: str | None, tag: str) -> None:
|
||||
headers = {"Referer": referer} if referer else {}
|
||||
resp = await context.request.get(CART_COUNT_API + "?sid=1010", headers=headers)
|
||||
body = await resp.text()
|
||||
print(f" [{tag}] http_status={resp.status} body={body!r}")
|
||||
save(f"{tag}.txt", f"http_status={resp.status}\n{body}")
|
||||
|
||||
|
||||
async def run(*, headful: bool) -> int:
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
settings = get_settings()
|
||||
state_path = settings.auth_state_path / "rakuten_state.json"
|
||||
if not state_path.exists():
|
||||
print(f"找不到登录态文件:{state_path}")
|
||||
return 1
|
||||
|
||||
async with async_playwright() as pw:
|
||||
browser = await pw.chromium.launch(
|
||||
headless=not headful,
|
||||
channel=settings.browser_channel or None,
|
||||
proxy=settings.playwright_proxy,
|
||||
args=["--no-first-run", "--disable-blink-features=AutomationControlled"],
|
||||
)
|
||||
context = await browser.new_context(
|
||||
storage_state=state_path,
|
||||
user_agent=auth_site.RAKUTEN_USER_AGENT,
|
||||
locale="ja-JP",
|
||||
timezone_id="Asia/Tokyo",
|
||||
viewport={"width": 390, "height": 844},
|
||||
is_mobile=True,
|
||||
has_touch=True,
|
||||
)
|
||||
|
||||
print("=== 1. 带正确 Referer 调 cart count API(生产路径)===")
|
||||
await call_count_api(context, referer=CART_PAGE, tag="10-with-referer")
|
||||
|
||||
print("=== 2. 不带 Referer 调 cart count API(观测「被拒」形态)===")
|
||||
await call_count_api(context, referer=None, tag="11-no-referer")
|
||||
|
||||
print("=== 3. 错误 sid 调 cart count API(另一种异常入参)===")
|
||||
resp = await context.request.get(
|
||||
CART_COUNT_API + "?sid=999999", headers={"Referer": CART_PAGE}
|
||||
)
|
||||
body = await resp.text()
|
||||
print(f" [bad-sid] http_status={resp.status} body={body!r}")
|
||||
save("12-bad-sid.txt", f"http_status={resp.status}\n{body}")
|
||||
|
||||
print("=== 4. 打开购物车页,看当前车状态(只读)===")
|
||||
page = await context.new_page()
|
||||
await page.goto(CART_PAGE, wait_until="domcontentloaded", timeout=30_000)
|
||||
await page.wait_for_timeout(5000)
|
||||
state = await page.evaluate(
|
||||
"""() => {
|
||||
const s = window.__INITIAL_STATE__;
|
||||
if (!s || !s.cart) return {has_state: !!s, keys: s ? Object.keys(s) : []};
|
||||
return {
|
||||
has_state: true,
|
||||
cart_keys: Object.keys(s.cart),
|
||||
shopUrlList_len: Array.isArray(s.cart.shopUrlList) ? s.cart.shopUrlList.length : null,
|
||||
};
|
||||
}"""
|
||||
)
|
||||
print(f" __INITIAL_STATE__.cart: {state}")
|
||||
html = await page.content()
|
||||
save("20-cart-page.html", html)
|
||||
delete_btns = await page.locator('button[aria-label^="削除"]').count()
|
||||
print(f" 削除按钮数={delete_btns} final_url={page.url}")
|
||||
|
||||
print("=== 5. 车页打开后再调一次 count API(贴近 clear_cart 末尾场景)===")
|
||||
await call_count_api(context, referer=CART_PAGE, tag="21-after-cart-page")
|
||||
|
||||
await browser.close()
|
||||
|
||||
print(f"\n探针输出目录:{PROBE_DIR}")
|
||||
return 0
|
||||
|
||||
|
||||
async def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--headful", action="store_true")
|
||||
args = parser.parse_args()
|
||||
return await run(headful=args.headful)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(asyncio.run(main()))
|
||||
@@ -0,0 +1,51 @@
|
||||
"""真账号验证:空车场景下 cart_status / clear_cart 不再被误判为获取失败
|
||||
|
||||
修复前行为(2026-08-16 探针实测):空车时 cart count API 返回 status=101,
|
||||
_query_cart_count 一律当失败抛 CartOperationError → cart_status 报错 5002、
|
||||
clear_cart 末尾校验拿 cart_count=-1(runner 据此误判「开单前清理未清空」)。
|
||||
修复后:status=101 识别为合法空车,count=0。
|
||||
|
||||
用法:
|
||||
.venv/Scripts/python.exe scripts/verify_cart_empty.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from app.shared.config import get_settings # noqa: E402
|
||||
from app.trading.services.auth_session import AuthSession # noqa: E402
|
||||
from app.trading.worker.site_interact import SiteInteractor # noqa: E402
|
||||
|
||||
|
||||
async def run() -> int:
|
||||
settings = get_settings()
|
||||
auth = AuthSession(settings)
|
||||
await auth.start()
|
||||
site = SiteInteractor(auth_session=auth, settings=settings)
|
||||
await site.start()
|
||||
try:
|
||||
status = await site.cart_status()
|
||||
print(f"cart_status -> {status}")
|
||||
assert status["logged_in"] is True
|
||||
assert status["count"] == 0, f"预期空车 count=0,实际 {status['count']}"
|
||||
assert status["raw_status"] == "101", f"预期 raw_status=101,实际 {status['raw_status']}"
|
||||
|
||||
cleared = await site.clear_cart()
|
||||
print(f"clear_cart -> {cleared}")
|
||||
assert cleared["cart_count"] == 0, (
|
||||
f"空车 clear 后 cart_count 应为 0(修复前是 -1),实际 {cleared['cart_count']}"
|
||||
)
|
||||
|
||||
print("\n验证通过:空车按正常结果返回(count=0 / raw_status=101),未误判为获取失败")
|
||||
return 0
|
||||
finally:
|
||||
await site.close()
|
||||
await auth.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(asyncio.run(run()))
|
||||
Reference in New Issue
Block a user