# 1 号药城加入购物车。 from __future__ import annotations import json import time from typing import Any import requests import urllib3 from cart_add import KIND_OK, KIND_SYSTEM, AddOutcome, classify_mall_message from cart_match import CartOffer urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) GATEWAY = "https://gateway-b2b.fangkuaiyi.com" YAOEX_ADD_URL = f"{GATEWAY}/api/cart/add" # PC 搜索页加车写死的来源码,所有登录账号同一套。 YAOEX_ADD_TYPE = "1" YAOEX_SOURCE_TYPE = 17052 YAOEX_FROMWHERE_CART = 0 # 按搜索结果的 spu 和仓,把数量加入 1 号药城购物车。缺 spu 或仓当系统失败。 def add_yaoex_cart(auth, offer: CartOffer, qty: int, *, session: requests.Session | None = None) -> AddOutcome: ids = _ids(offer) if not ids["spuCode"] or ids["supplyId"] in ("", None): return AddOutcome(kind=KIND_SYSTEM, reason="缺少 sku") common, payload = _form(auth, ids, qty) own = session is None client = session or requests.Session() try: response = client.post( YAOEX_ADD_URL, params=common, data=payload, headers=_headers(), timeout=30, verify=False, ) response.raise_for_status() return _outcome_from_json(response.json()) except Exception as exc: return AddOutcome(kind=KIND_SYSTEM, reason=str(exc)) finally: if own: client.close() # 从报价里取出加车要的 spu 和仓。仓优先 storeId / frontSellerCode,各账号字段名一样。 def _ids(offer: CartOffer) -> dict[str, Any]: raw = offer.extra.get("raw") if isinstance(offer.extra.get("raw"), dict) else offer.extra if not isinstance(raw, dict): raw = {} spu = str(raw.get("spuCode") or offer.sku_id or "").strip() supply = _supply_id(raw) return {"spuCode": spu, "supplyId": supply} # 仓号收成网关要的值。纯数字就当整数,避免各账号字符串/数字不一致。 def _supply_id(raw: dict[str, Any]): value = raw.get("storeId") if value in (None, ""): value = raw.get("frontSellerCode") if value in (None, ""): value = raw.get("supplyId") if value in (None, ""): value = raw.get("sellerCode") if value in (None, ""): return "" text = str(value).strip() if text.isdigit(): return int(text) return text # 拼 1 号药城加车表单:query 和 body 都带登录 token,itemList 是页面同款 JSON。 def _form(auth, ids: dict[str, Any], qty: int) -> tuple[dict[str, Any], dict[str, Any]]: common = { "traderName": "yaoex_pc", "trader": "pc", "closesignature": "yes", "signature_method": "md5", "signature": "****", "timestamp": str(int(time.time() * 1000)), "token": auth.token, "userToken": auth.token, "userId": getattr(auth, "user_id", ""), "buyerCode": getattr(auth, "buyer_code", ""), } item: dict[str, Any] = { "productNum": int(qty), "spuCode": ids["spuCode"], "supplyId": ids["supplyId"], } payload = { **common, "addType": YAOEX_ADD_TYPE, "sourceType": YAOEX_SOURCE_TYPE, "fromwhere": YAOEX_FROMWHERE_CART, "itemList": json.dumps([item], ensure_ascii=False), } return common, payload # 1 号药城加车请求头。来源页是搜索加车页,所有账号同一套。 def _headers() -> dict[str, str]: return { "Content-Type": "application/x-www-form-urlencoded", "X-Requested-With": "XMLHttpRequest", "X-Request-Agent": "Axios", "traderName": "yaoex_pc", "trader": "pc", "Origin": "https://mall.yaoex.com", "Referer": "https://mall.yaoex.com/v2/pages/", "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36" ), "Accept": "application/json, text/plain, */*", } # 把加车回包收成成功、业务失败或系统失败。看不懂的回包当系统失败。 def _outcome_from_json(payload: object) -> AddOutcome: if not isinstance(payload, dict): return AddOutcome(kind=KIND_SYSTEM, reason="加购返回异常") if payload.get("success") is True or payload.get("code") in (0, "0", 200, "200"): return AddOutcome(kind=KIND_OK) rtn = payload.get("rtn_code") if rtn not in (None, 0, "0"): msg = str(payload.get("rtn_msg") or payload.get("rtn_tip") or "加购失败") if "登录" in msg: return AddOutcome(kind=KIND_SYSTEM, reason=msg) return classify_mall_message(msg) data = payload.get("data") if not isinstance(data, dict): data = payload blob = _result_text(payload, data) status = data.get("statusCode") if status in (0, "0") and not blob: return AddOutcome(kind=KIND_OK) if blob: return classify_mall_message(blob) if status in (0, "0"): return AddOutcome(kind=KIND_OK) if status in (2, "2"): return AddOutcome(kind=KIND_BUSINESS, reason="限购") return AddOutcome(kind=KIND_SYSTEM, reason="加购失败") # 从 data.message 和 cartResponseList[].message 拼出商城回文。 def _result_text(payload: dict, data: dict) -> str: parts: list[str] = [] for row in data.get("cartResponseList") or data.get("addCartItemList") or []: if isinstance(row, dict) and row.get("message"): parts.append(str(row["message"])) for key in ("message", "msg", "errorMsg"): value = data.get(key) or payload.get(key) if value: parts.append(str(value)) return " ".join(parts).strip()