158 lines
6.4 KiB
Python
158 lines
6.4 KiB
Python
"""收货地址确认页真实 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()))
|