|
|
@@ -24,9 +24,21 @@ logger.add("./logs/fan_{time:YYYYMMDD}.log", encoding='utf-8', rotation="00:00",
|
|
|
level="DEBUG", retention="7 day")
|
|
|
|
|
|
"""
|
|
|
-expansion_series ->
|
|
|
+expansion_series ->
|
|
|
"""
|
|
|
|
|
|
+# 稀有度筛选值 → 稀有度标签
|
|
|
+# 来源:tw 列表页搜索表单「稀有度」区块的 rarity[] 复选框(value → label),共 21 种
|
|
|
+# 用法:GET list/?rarity[]=<value>;不带 expansionCodes 表示跨全部扩充包,
|
|
|
+# 不带 regulation 表示全赛制(实测 == regulation=all,不会漏掉旧赛制卡)
|
|
|
+RARITY_MAP = {
|
|
|
+ "1": "C", "2": "U", "3": "R", "4": "RR", "5": "RRR",
|
|
|
+ "6": "PR", "7": "TR", "8": "SR", "9": "HR", "10": "UR",
|
|
|
+ "11": "無標記", "12": "K", "13": "A", "14": "AR", "15": "SAR",
|
|
|
+ "16": "S", "17": "SSR", "18": "ACE", "19": "BWR", "20": "MUR",
|
|
|
+ "21": "MA",
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
def after_log(retry_state):
|
|
|
"""
|
|
|
@@ -212,6 +224,97 @@ def get_data_list(log, sql_pool, cate_tuple):
|
|
|
page += 1
|
|
|
|
|
|
|
|
|
+# ----------------------------------------------------------------------------------------------------------------------
|
|
|
+@retry(stop=stop_after_attempt(5), wait=wait_fixed(1), after=after_log)
|
|
|
+def get_rarity_single_page(log, rarity_value, page):
|
|
|
+ """请求指定稀有度、指定页码的卡片列表,返回本页所有 card_id。
|
|
|
+
|
|
|
+ :param log: logger 对象
|
|
|
+ :param rarity_value: 稀有度筛选值(RARITY_MAP 的 key,如 '8' 表示 SR)
|
|
|
+ :param page: 页码,从 1 开始
|
|
|
+ :return: (card_id 列表, 本页卡片数量)
|
|
|
+ """
|
|
|
+ log.debug(f'Request get_rarity_single_page rarity_value={rarity_value} page={page}')
|
|
|
+ url = "https://asia.pokemon-card.com/tw/card-search/list/"
|
|
|
+ # rarity[] 为列表页搜索表单的稀有度复选框参数;不带 expansionCodes=跨全部扩充包,
|
|
|
+ # 不带 regulation=全赛制(实测与 regulation=all 一致,能覆盖旧赛制卡)
|
|
|
+ params = {
|
|
|
+ "rarity[]": rarity_value,
|
|
|
+ "pageNo": page,
|
|
|
+ }
|
|
|
+ response = requests.get(url, headers=headers, params=params, timeout=10)
|
|
|
+ response.raise_for_status()
|
|
|
+
|
|
|
+ selector = Selector(response.text)
|
|
|
+ tag_li_list = selector.xpath('//*[@id="searchForm"]//ul/li')
|
|
|
+
|
|
|
+ card_ids = []
|
|
|
+ for tag_li in tag_li_list:
|
|
|
+ detail_url_str = tag_li.xpath('./a/@href').get()
|
|
|
+ # 只取指向卡片详情页的 li,过滤掉搜索表单内其它无关 li
|
|
|
+ if not detail_url_str or '/detail/' not in detail_url_str:
|
|
|
+ continue
|
|
|
+ card_id = detail_url_str.split('/')[-2]
|
|
|
+ card_ids.append(card_id)
|
|
|
+ return card_ids, len(card_ids)
|
|
|
+
|
|
|
+
|
|
|
+def get_rarity_list(log, sql_pool, rarity_value, rarity_label):
|
|
|
+ """翻页抓取某个稀有度下的全部卡片,并把 rarity 回填到已入库的繁中卡片。
|
|
|
+
|
|
|
+ 只按 card_id + crawler_language 定位更新已存在的行,不新增卡片;
|
|
|
+ 库中没有的 card_id(如尚未爬到的卡)更新 0 行,无副作用。
|
|
|
+
|
|
|
+ :param log: logger 对象
|
|
|
+ :param sql_pool: MySQL 连接池
|
|
|
+ :param rarity_value: 稀有度筛选值(RARITY_MAP 的 key)
|
|
|
+ :param rarity_label: 稀有度标签,写入 rarity 字段(如 'SR')
|
|
|
+ """
|
|
|
+ page = 1
|
|
|
+ max_page = 500 # 上限保护:普通卡(C/U)跨全部扩充包页数较多
|
|
|
+ total = 0
|
|
|
+ while page <= max_page:
|
|
|
+ try:
|
|
|
+ card_ids, len_items = get_rarity_single_page(log, rarity_value, page)
|
|
|
+ except Exception as e:
|
|
|
+ log.error(
|
|
|
+ f"{inspect.currentframe().f_code.co_name} get_rarity_single_page rarity={rarity_label} page={page} error: {e}")
|
|
|
+ break
|
|
|
+
|
|
|
+ if card_ids:
|
|
|
+ # 逐行回填 rarity:data_list 与 condition_list 等长一一对应
|
|
|
+ data_list = [{"rarity": rarity_label} for _ in card_ids]
|
|
|
+ condition_list = [{"card_id": cid, "crawler_language": crawler_language} for cid in card_ids]
|
|
|
+ try:
|
|
|
+ sql_pool.update_many(table="pokemon_card_record", data_list=data_list, condition_list=condition_list)
|
|
|
+ total += len(card_ids)
|
|
|
+ except Exception as e:
|
|
|
+ log.error(
|
|
|
+ f"{inspect.currentframe().f_code.co_name} update_many rarity={rarity_label} page={page} error: {e}")
|
|
|
+
|
|
|
+ if len_items < 20:
|
|
|
+ log.debug(f'--------------- rarity={rarity_label} page {page} has {len_items} items, break ---------------')
|
|
|
+ break
|
|
|
+ page += 1
|
|
|
+
|
|
|
+ log.info(f'稀有度 {rarity_label}(value={rarity_value}) 采集完成,累计回填 {total} 张卡')
|
|
|
+
|
|
|
+
|
|
|
+def get_all_rarity(log, sql_pool):
|
|
|
+ """遍历 RARITY_MAP 全部稀有度,逐个筛选并回填繁中卡片的 rarity 字段。
|
|
|
+
|
|
|
+ :param log: logger 对象
|
|
|
+ :param sql_pool: MySQL 连接池
|
|
|
+ """
|
|
|
+ log.debug(f'........... 获取并回填稀有度 ..........')
|
|
|
+ for rarity_value, rarity_label in RARITY_MAP.items():
|
|
|
+ try:
|
|
|
+ log.info(f'正在处理稀有度 {rarity_label} (value={rarity_value}) .........')
|
|
|
+ get_rarity_list(log, sql_pool, rarity_value, rarity_label)
|
|
|
+ except Exception as e:
|
|
|
+ log.error(f"{inspect.currentframe().f_code.co_name} get_rarity_list rarity={rarity_label} error: {e}")
|
|
|
+
|
|
|
+
|
|
|
# ----------------------------------------------------------------------------------------------------------------------
|
|
|
@retry(stop=stop_after_attempt(5), wait=wait_fixed(1), after=after_log)
|
|
|
def get_details(log, sql_id_detail_url: tuple, sql_pool):
|
|
|
@@ -286,6 +389,14 @@ def fz_pokemon_main(log):
|
|
|
except Exception as e:
|
|
|
log.error(f"Request get_details error: {e}")
|
|
|
|
|
|
+ # 回填稀有度:跨全部扩充包按 rarity[] 逐个筛选,将 rarity 写入已入库卡片
|
|
|
+ # 既补历史数据,也让后续每轮采集自动带上最新稀有度(列表 upsert 不动 rarity 列,不会被覆盖)
|
|
|
+ log.debug(f"........... 回填稀有度 ..........")
|
|
|
+ try:
|
|
|
+ get_all_rarity(log, sql_pool)
|
|
|
+ except Exception as e:
|
|
|
+ log.error(f"{inspect.currentframe().f_code.co_name} Request get_all_rarity error: {e}")
|
|
|
+
|
|
|
except Exception as e:
|
|
|
log.error(f'{inspect.currentframe().f_code.co_name} error: {e}')
|
|
|
finally:
|