加 /api/cart/* 接口;加购链路完全归 trading
trading 新增 4 条购物车接口(POST /api/cart/{add,status,clear,remove}),
全部 Bearer 鉴权、走 SiteInteractor(Playwright + storage_state)。同步把
SiteInteractor 从 gateway URL 解耦——lifespan 总是构造与启停,container
字段 worker_site → site,加 asyncio.Lock 让 HTTP 与 worker 共用同一把锁
(同账号串行硬约束)。clear/remove 用 UI 点击 button[aria-label="削除"],
探针回报这是稳定 selector;真账号实测前先用此路径。
抽 ichiba 加购字段解析到 app/shared/purchase_contract.py(常量 +
inventory_flag_for + basket_domain_of + base_form_fields),原本 scraping
与 trading 重复实现同一段 __INITIAL_STATE__.purchase 解析。进一步发现
README 写的「purchase 块是两服务契约」实际未落地——trading 必须 Playwright
开页(httpx 被 TLS 指纹拦死),本地抽比再调 /api/item_detail 更快更新鲜。
删除 scraping 端 PurchaseInfo/PurchaseOption/PurchaseOptionValue 模型、
各站 _purchase_info 函数、tests/test_purchase.py。ItemDetailData 保留
purchase_condition / is_sold_out / purchase_unit / sku 等商品状态字段。
README「加购与下单」段重写。
328 测试全绿(含架构测试守住三方互不 import)。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,6 @@ from app.shared.errors import ScrapeParseError
|
||||
from app.scraping.models.scrape import (
|
||||
Breadcrumb,
|
||||
ItemDetailData,
|
||||
PurchaseInfo,
|
||||
ShopSummary,
|
||||
SkuAttribute,
|
||||
SkuAxis,
|
||||
@@ -37,44 +36,6 @@ SHOP_NAME = "Rakuten Fashion"
|
||||
_COLOR_AXIS = "カラー"
|
||||
_SIZE_AXIS = "サイズ"
|
||||
|
||||
# cart_info.cart_url_type → 加购端点。站点前端用同名映射表(resolveCartUrl)解析;
|
||||
# 若该字段本身已经是一个 URL,则直接使用。
|
||||
_CART_URL_BY_TYPE = {
|
||||
"1": "https://ts.basket.step.rakuten.co.jp/rms/mall/bs/cartadd/set",
|
||||
"2": "https://ts.sp.basket.step.rakuten.co.jp/rms/mall/bss/cartadd/set",
|
||||
"3": "https://t2.basket.step.rakuten.co.jp/rms/mall/bs/cartadd/set",
|
||||
"4": "https://t2.sp.basket.step.rakuten.co.jp/rms/mall/bss/cartadd/set",
|
||||
"5": "https://basket.step.rakuten.co.jp/rms/mall/bs/cartadd/set",
|
||||
"6": "https://sp.basket.step.rakuten.co.jp/rms/mall/bss/cartadd/set",
|
||||
}
|
||||
_DEFAULT_PURCHASE_EVENT = "ES01_003_001"
|
||||
|
||||
|
||||
def _resolve_cart_url(cart_url_type: str) -> str:
|
||||
if cart_url_type.startswith("http"):
|
||||
return cart_url_type
|
||||
return _CART_URL_BY_TYPE.get(cart_url_type, "")
|
||||
|
||||
|
||||
def _purchase_info(product: dict) -> PurchaseInfo:
|
||||
"""加购字段与市场是同一套契约,差别只在端点由 cart_url_type 映射得到"""
|
||||
cart_info = as_dict(as_dict(product.get("rms_info")).get("cart_info"))
|
||||
if not cart_info:
|
||||
return PurchaseInfo()
|
||||
return PurchaseInfo(
|
||||
cart_url=_resolve_cart_url(as_str(cart_info.get("cart_url_type"))),
|
||||
form_fields={
|
||||
"shop_bid": as_str(cart_info.get("shop_bid")),
|
||||
"item_id": as_str(cart_info.get("item_id")),
|
||||
"inventory_flag": as_str(cart_info.get("inventory_type")),
|
||||
"__event": as_str(cart_info.get("event")) or _DEFAULT_PURCHASE_EVENT,
|
||||
"encode": "utf8",
|
||||
},
|
||||
quantity_field="units",
|
||||
variant_field="variant_id",
|
||||
)
|
||||
|
||||
|
||||
def validate(html: str) -> str | None:
|
||||
if "window.__INITIAL_STATE__" in html:
|
||||
return None
|
||||
@@ -206,5 +167,4 @@ def parse(page: SubsitePage) -> ItemDetailData:
|
||||
genre_id=as_str(rms.get("genre_id")),
|
||||
breadcrumbs=_breadcrumbs(product),
|
||||
sku=_sku(product, include_variants=page.include_sku_variants),
|
||||
purchase=_purchase_info(product),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user