AutoRpa/选品流程/search_ybm.py

121 lines
4.1 KiB
Python

from __future__ import annotations
from typing import Any
import requests
from http_util import post_json_with_retry
from mall_config import YBM_MALL_NAME, YBM_SEARCH_API_URL, YBM_SORT_STRATEGY
from rules import Candidate, normalize_vendor, parse_expiry_months
from util import normalize_text, parse_float_or_none, parse_int_or_none
def build_ybm_search_payload(query_key: str) -> dict[str, Any]:
return {
"queryWord": query_key,
"searchScene": 1,
"nearEffect": "",
"minPrice": "",
"maxPrice": "",
"drugClassificationsStr": "",
"isThirdCompany": 0,
"isEffectPeriodOverOneYear": 1,
"sortStrategy": YBM_SORT_STRATEGY,
}
def pick_ybm_platform_price(product: dict[str, Any]) -> Any:
price = product.get("price")
if normalize_text(price) != "":
return price
fob = product.get("fob")
if normalize_text(fob) != "":
return fob
return ""
def query_ybm_products(
session: requests.Session,
query_key: str,
) -> list[dict[str, Any]]:
headers = {
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/json",
"Origin": "https://www.ybm100.com",
"Referer": "https://www.ybm100.com/new/",
"X-Requested-With": "XMLHttpRequest",
"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"
),
}
payload = build_ybm_search_payload(query_key)
payload_json = post_json_with_retry(
session,
YBM_SEARCH_API_URL,
platform_name=YBM_MALL_NAME,
query_key=query_key,
json=payload,
headers=headers,
)
if not payload_json.get("success"):
error_msg = normalize_text(payload_json.get("msg")) or "未知错误"
raise RuntimeError(f"药帮忙查询失败:{error_msg}")
data_section = payload_json.get("data") or {}
rows = data_section.get("rows") or []
if not isinstance(rows, list):
raise RuntimeError(f"药帮忙接口返回 data.rows 结构异常:{type(rows)}")
return rows
def map_ybm_products(rows: list[dict[str, Any]]) -> list[Candidate]:
candidates: list[Candidate] = []
for row in rows:
product = row.get("productInfo") or {}
available_qty = product.get("availableQty", "")
available_num = parse_int_or_none(available_qty)
no_stock = available_num == 0 or normalize_text(available_qty) == "0"
raw_price = 0 if no_stock else pick_ybm_platform_price(product)
vendor_raw = normalize_text(product.get("shopName"))
expiry_raw = normalize_text(product.get("nearEffect"))
candidates.append(
Candidate(
mall_name=YBM_MALL_NAME,
sku_id=normalize_text(product.get("skuId", row.get("skuId", ""))),
name=normalize_text(product.get("showName")),
spec=normalize_text(product.get("spec")),
price=parse_float_or_none(raw_price),
expiry_raw=expiry_raw,
expiry_months=parse_expiry_months(expiry_raw),
vendor_raw=vendor_raw,
vendor_norm=normalize_vendor(vendor_raw),
stock=parse_float_or_none(available_qty),
extra={"库存说明": "无货" if no_stock else "有货"},
)
)
return candidates
def search_candidates(
cookies: dict[str, str],
query_key: str,
session: requests.Session | None = None,
cache: dict[str, list[Candidate]] | None = None,
) -> list[Candidate]:
store = cache if cache is not None else {}
if query_key in store:
return store[query_key]
own_session = session or requests.Session()
try:
if session is None:
for name, value in cookies.items():
own_session.cookies.set(name, value)
rows = query_ybm_products(own_session, query_key)
mapped = map_ybm_products(rows)
finally:
if session is None:
own_session.close()
store[query_key] = mapped
return mapped