| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- # -*- 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} 个商品")
|