AutoRpa/rpa_selection/search_ybm.py

128 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 药帮忙按条形码搜索,收成选品候选。无货的价格记 0,不参与拆量。
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,
}
# 取平台价。price 空了再用 fob。
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
# 把药帮忙商品收成候选。库存为 0 时价格记 0,后面拆量不会吃它。
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