# -*- coding: utf-8 -*- # Author : Charley # Python : 3.12.10 # Date : 2026/08/19 """集物星球商品详情补全:拉 merchantGoodsId(免登录) 取详情页字段,供在售/已售两表补全。 详情字段在列表接口(index/top、corp/history)里没有,只在商品详情里,需逐商品补拉一次。 用 detail_fetched 状态位控制每商品只补一次(在售的列表 upsert 不动这些详情列,避免被覆盖)。 图片:取 resList 第一张图(商品主图)的相对路径 resAddr,拼 FILE_DOMAIN(逆向 EnvironmentManager 得) 成完整 URL。 """ import time import jiwu_core as core IMG_BASE_URL = "https://files.jiwustar.com/" # 文件/图片域名(EnvironmentManager RELEASE 环境 FILE_DOMAIN) # 详情补全的列(两表同名同序),值来源见 parse_detail_fields DETAIL_COLS = ["img", "goods_type", "price", "original_price", "plan_up_time", "off_shelf_time", "stock", "surplus_stock", "status", "collection_card_name", "gift_way", "random_way", "gift_series"] def fetch_detail(log, goods_id: int) -> dict | None: """拉商品详情 merchantGoodsId(免登录),返回 data 对象。 Args: log: 日志对象。 goods_id (int): 商品 goodsId。 Returns: dict | None: 详情 data 字典;失败返回 None。 """ j = core.do_request(log, "/search/app/merchantGoodsId", {"goodsId": str(goods_id), "systemBusinessType": 5}) # 免登录 if not j: return None return j.get("data") or None def parse_detail_fields(data: dict) -> dict: """从详情 data 抽取补全列(含图片拼完整域名;collection/gift/random 在 cardGoods 里)。 Args: data (dict | None): fetch_detail 返回的详情 data;None 时返回全 None 的列。 Returns: dict: 键为 DETAIL_COLS 的字典。 """ if not data: return {c: None for c in DETAIL_COLS} cg = data.get("cardGoods") or {} res_list = data.get("resList") or [] addr = res_list[0].get("resAddr") if res_list else None # resList 第一张=商品主图 return { "img": (IMG_BASE_URL + addr) if addr else None, "goods_type": data.get("goodsType"), "price": core.to_yuan(data.get("price")), "original_price": core.to_yuan(data.get("originalPrice")), "plan_up_time": data.get("planUpTime"), "off_shelf_time": data.get("offShelfTime"), "stock": data.get("stock"), "surplus_stock": data.get("surplusStock"), "status": data.get("status"), "collection_card_name": cg.get("collectionCardName"), "gift_way": cg.get("giftWay"), "random_way": cg.get("randomWay"), "gift_series": cg.get("giftSeries") or data.get("specifications"), # 赠品系列(App"系列"),兜底取顶层 specifications } def enrich_detail(log, pool, table: str, goods_ids: list) -> None: """对表内 detail_fetched=0 的商品补全详情列,成功后置 detail_fetched=1(每商品补一次)。 Args: log: 日志对象。 pool: 数据库连接池。 table (str): 目标表名(jw_onsale_product_record / jw_sold_product_record,受控常量)。 goods_ids (list): 本轮相关的商品 goods_id 列表。 """ ids = list({g for g in goods_ids if g}) if not ids: return ph = ",".join(["%s"] * len(ids)) pending = [r[0] for r in pool.select_all( f"SELECT goods_id FROM {table} WHERE goods_id IN ({ph}) AND detail_fetched=0", tuple(ids))] if not pending: return set_cols = ", ".join(f"`{c}`=%s" for c in DETAIL_COLS) done = 0 for gid in pending: data = fetch_detail(log, gid) if not data: # 取不到详情,不置位、下轮再补 continue f = parse_detail_fields(data) pool.update_one( f"UPDATE {table} SET {set_cols}, detail_fetched=1 WHERE goods_id=%s", tuple(f[c] for c in DETAIL_COLS) + (gid,)) done += 1 time.sleep(0.2) if done: log.info(f"{table} 详情补全 {done} 个商品")