"""收货地址确认页真实 HTML 探针 走 SiteInteractor 的真实生产代码路径(add_to_cart → verify_cart → enter_checkout), 在 _confirm_default_address 被调用前把该页 HTML 落盘,验证/纠正选择器是否与真实页面 一致。**不调用 submit_order / pay**——enter_checkout 落地到确认页就返回,这里直接 丢弃该页,不产生下单动作。 用法: .venv/Scripts/python.exe scripts/probe_address_confirm.py .venv/Scripts/python.exe scripts/probe_address_confirm.py --item-url https://item.rakuten.co.jp/... """ 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.services.auth_session import AuthSession # noqa: E402 from app.trading.worker.models import LeaseTask # noqa: E402 from app.trading.worker.site_interact import SiteInteractor # noqa: E402 PROBE_DIR = Path(__file__).resolve().parent.parent / ".probe" / "address_confirm" PROBE_DIR.mkdir(parents=True, exist_ok=True) # fafachai/10000033 已下架/不可购买;改用 2026-08-13 真实下单时用过的商品 # (data/evidence/checkout-live-20260813/i00-cart-initial.html 里的 itemUrl) DEFAULT_ITEM_URL = "https://item.rakuten.co.jp/moccasin/ds001iwrgesaaa2/" async def _start_visible(site: SiteInteractor, settings) -> None: """复制 SiteInteractor.start() 但 headless=False,供本地诊断肉眼看浏览器 不改动生产代码(生产 start() 硬编码 headless=True),直接绕过它、把结果 塞进 site 的私有属性——只在这个一次性诊断脚本里这么干。 """ from playwright.async_api import async_playwright from app.trading.core import auth_site state_path = settings.auth_state_path / auth_site.profile("rakuten").state_filename storage_state = str(state_path) if state_path.exists() else None site._playwright = await async_playwright().start() site._browser = await site._playwright.chromium.launch( headless=False, channel=settings.browser_channel or None, proxy=settings.playwright_proxy, args=["--no-first-run", "--disable-blink-features=AutomationControlled"], ) site._context = await site._browser.new_context( storage_state=storage_state, 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(f"[visible] 浏览器已启动(headless=False),storage_state={storage_state or '(none)'}") async def run(item_url: str) -> int: settings = get_settings() auth = AuthSession(settings) await auth.start() site = SiteInteractor(auth_session=auth, settings=settings) await _start_visible(site, settings) captured: dict[str, str] = {} orig_confirm = site._confirm_default_address async def spy_confirm(page, *, task_id: str) -> None: html = await page.content() captured["html"] = html captured["url"] = page.url path = PROBE_DIR / "address-confirm-page.html" path.write_text(html, encoding="utf-8") print(f"[捕获] 收货地址确认页 -> {path} ({len(html)} bytes), url={page.url}") await orig_confirm(page, task_id=task_id) site._confirm_default_address = spy_confirm orig_session_upgrade = site._complete_session_upgrade async def spy_session_upgrade(page, *, task_id: str) -> None: html = await page.content() path = PROBE_DIR / "session-upgrade-before.html" path.write_text(html, encoding="utf-8") print(f"[捕获] session upgrade 页(点提交前)-> {path} ({len(html)} bytes), url={page.url}") try: await orig_session_upgrade(page, task_id=task_id) finally: html_after = await page.content() (PROBE_DIR / "session-upgrade-after.html").write_text(html_after, encoding="utf-8") site._complete_session_upgrade = spy_session_upgrade task = LeaseTask( task_id="probe-addr-1", site="rakuten", intent={"item_url": item_url, "quantity": 1}, ) try: print("=== add_to_cart ===") cart_add_html = await site.add_to_cart(task) if cart_add_html: (PROBE_DIR / "01-cart-add.html").write_text(cart_add_html, encoding="utf-8") print(f" cart-add 页已存盘 ({len(cart_add_html)} bytes)") print("=== verify_cart ===") verify_html = await site.verify_cart(task) if verify_html: (PROBE_DIR / "02-cart-verify.html").write_text(verify_html, encoding="utf-8") print(f" cart-verify 页已存盘 ({len(verify_html)} bytes)") print("=== enter_checkout(落地到确认页或撞上未捕获的中间步骤为止)===") try: html = await site.enter_checkout(task) print(f"enter_checkout 返回,长度={len(html)}") (PROBE_DIR / "03-enter-checkout-final.html").write_text(html, encoding="utf-8") except Exception as exc: print(f"enter_checkout 抛出:{type(exc).__name__}: {exc}") finally: # 不调用 submit_order/pay;enter_checkout 若成功会把 page 留存在 # _checkout_pages,这里直接丢弃,不产生下单动作 page = site._checkout_pages.pop(task.task_id, None) if page is not None: print(f" 最终落地 url={page.url}") await page.close() if "html" not in captured: print("\n没有触发 _confirm_default_address——账号可能没走到这一步" "(比如没有电话补录/地址确认步骤,或选择器判断条件本身有问题)") finally: try: await site.clear_cart() except Exception as exc: # noqa: BLE001 print(f"清理购物车失败(忽略):{type(exc).__name__}: {exc}") await site.close() await auth.close() print(f"\n探针输出目录:{PROBE_DIR}") return 0 async def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--item-url", default=DEFAULT_ITEM_URL) args = parser.parse_args() return await run(args.item_url) if __name__ == "__main__": raise SystemExit(asyncio.run(main()))