| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- # -*- coding: utf-8 -*-
- # Author : Charley
- # Python : 3.12.10
- # Date : 2026/08/04
- """得卡 DECA 商品卡牌清单(checklist)采集:拉某商品 groupbuy/card/list 的全部卡牌,入 deca_onsale_kami_record。
- 翻页「翻到底」(靠 data.total 判断),uuid 唯一键 INSERT IGNORE 去重。
- 本接口需 Authorization Bearer,复用 deca_on_sale_daily_spider 的签名与 token 续期逻辑。
- 字段对应(列表项):
- sortOrder 序号 / aliasName 别名 / teamNameEn·teamNameZh 球队 / playerNameEn·playerNameZh 球员 /
- cardType 卡种 / limitNumber 限编 / cardNumber 卡编 / uuid 卡牌唯一标识
- 用法:
- python km_spdier.py <商品code> # 采集指定商品的全部卡牌清单,默认用示例 code
- 或作为模块:from km_spdier import get_card_list; get_card_list(log, code, pool)
- """
- import sys
- import time
- from loguru import logger
- from mysql_pool import MySQLConnectionPool
- import deca_on_sale_daily_spider as deca # 复用 do_request / ensure_token / make_signature
- # 覆盖 deca 导入时装的 logger handler,本任务日志独立到 kami_*.log
- logger.remove()
- logger.add("./logs/kami_{time:YYYYMMDD}.log", encoding="utf-8", rotation="00:00",
- format="[{time:YYYY-MM-DD HH:mm:ss.SSS}] {level} {message}",
- level="DEBUG", retention="7 day")
- PAGE_SIZE = 20 # 每页条数(抓包实测 20)
- MAX_PAGES = 600 # 翻页保护上限(单商品清单可上万张,20/页约需数百页)
- def parse_kami(item: dict, product_code: str) -> dict | None:
- """把卡牌清单项解析成 deca_onsale_kami_record 一行。
- Args:
- item (dict): card/list 的 data.list 项。
- product_code (str): 所属商品编码(列表项不含,由外部带入)。
- Returns:
- dict | None: 与 deca_onsale_kami_record 列对应的数据字典;无 uuid 时返回 None。
- """
- uuid = item.get("uuid")
- if not uuid:
- return None
- return {
- "uuid": uuid,
- "product_code": product_code,
- "sort_order": item.get("sortOrder"),
- "alias_name": item.get("aliasName"),
- "team_name_en": item.get("teamNameEn"),
- "team_name_zh": item.get("teamNameZh"),
- "player_name_en": item.get("playerNameEn"),
- "player_name_zh": item.get("playerNameZh"),
- "card_type": item.get("cardType"),
- "limit_number": item.get("limitNumber"),
- "card_number": item.get("cardNumber"),
- }
- def get_card_list(log, product_code: str, pool) -> int:
- """翻页拉取某商品的全部卡牌清单(翻到底),INSERT IGNORE 写入 deca_onsale_kami_record。
- Args:
- log: 日志对象。
- product_code (str): 商品编码。
- pool (MySQLConnectionPool): MySQL 连接池。
- Returns:
- int: 本商品实际写入的卡牌数(已去重)。
- """
- page = 1
- saved = 0
- total = None
- while page <= MAX_PAGES:
- body = {"code": product_code, "page": page, "pageSize": PAGE_SIZE}
- try:
- log.info(f"商品 {product_code} 卡牌清单请求第 {page} 页")
- resp = deca.do_request(log, "/api/v1/app/groupbuy/card/list", body, need_auth=True)
- except Exception as e:
- log.error(f"商品 {product_code} 卡牌清单第 {page} 页请求失败: {e}")
- break
- if not resp or resp.get("code") != 0:
- log.info(f"商品 {product_code} 卡牌清单返回异常: {resp.get('msg') if resp else None}")
- break
- data = resp.get("data") or {}
- if total is None:
- total = data.get("total")
- log.info(f"商品 {product_code} 卡牌清单共 {total} 张,开始翻页入库")
- items = data.get("list") or []
- if not items:
- break
- rows = [r for r in (parse_kami(it, product_code) for it in items) if r]
- if rows:
- pool.insert_many(table="deca_onsale_kami_record", data_list=rows, ignore=True)
- saved += len(rows)
- # 翻到底:本页不足一页(<PAGE_SIZE)即最后一页。
- # total 是动态值(清单持续新增),仅作日志参考,不用于终止判断。
- if len(items) < PAGE_SIZE:
- break
- page += 1
- if page % 50 == 0:
- log.info(f"商品 {product_code} 已翻到第 {page} 页,累计入库 {saved}")
- # time.sleep(1) # 每页间隔 1s,降低请求频率
- log.info(f"商品 {product_code} 卡牌清单入库完成 {saved} 张(total={total})")
- return saved
- def main(product_code: str):
- """采集单个商品的全部卡牌清单入库。
- Args:
- product_code (str): 商品编码。
- """
- pool = MySQLConnectionPool(log=logger)
- if not pool.check_pool_health():
- logger.error("数据库连接池异常")
- return
- deca.ensure_token(logger) # 预取 token(后续请求自动续期)
- get_card_list(logger, product_code, pool)
- if __name__ == "__main__":
- code = sys.argv[1] if len(sys.argv) > 1 else "GB26080614002"
- main(code)
|