Bladeren bron

feat(buy_record_spider): 优化快车高发时段在售发现频率

- 新增快车高发时段(20:00~次日06:00)全站在售发现间隔为20秒
- 常规时段保持全站在售发现间隔为60秒
- 通过_current_ingest_interval函数,根据当前时间动态调整发现间隔
- 修正时段判断逻辑,支持窗口跨午夜判断快车时段
- 主循环中使用动态间隔替代固定间隔执行全站在售发现
- 日志输出中新增快车时段发现频率信息提示
charley 4 weken geleden
bovenliggende
commit
187f2c25b7
1 gewijzigde bestanden met toevoegingen van 32 en 5 verwijderingen
  1. 32 5
      deca_spider/buy_record_analysis/buy_record_spider.py

+ 32 - 5
deca_spider/buy_record_analysis/buy_record_spider.py

@@ -49,7 +49,7 @@ import os
 import re
 import sys
 import time
-from datetime import datetime
+from datetime import datetime, time as dtime
 
 # 挂靠项目根:复用核心签名/token/请求/代理层,让 application.yml、token.json 生效
 _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -69,7 +69,15 @@ core.USE_PROXY = True               # 详情接口(groupbuy/detail)走快代理
 
 MERCHANT_ID = "881226408"           # 监控商家:采其在售商品的购买记录(WATCH_CODES 为空时生效)
 WATCH_CODES = []                    # 指定商品白名单:非空则只盯这些 code、跳过在售发现;空则走 MERCHANT_ID 全在售(默认)
-ONSALE_INGEST_SEC = 60             # 全站在售落库 + 刷新监控队列间隔(1 分钟,复用 daily 免 token home/search)
+ONSALE_INGEST_SEC = 60             # 全站在售落库 + 刷新监控队列间隔(常规时段 1 分钟,复用 daily 免 token home/search)
+# 快车高发时段(直播拆卡,通常 20:00~次日 06:00)专用:这类小批量拼团(本商家 31 份)上架后 2~3 分钟即售罄,
+# 寿命只比 60s 发现节奏大一个数量级,偶尔整场卡在两次全站扫描的缝隙里被漏掉(实测 2026/08/22 01:45 一辆 31 份车漏采,
+# 从没进过 deca_onsale_product_record → 从没纳入监控 → deca_buy_record 0 条 → 战报「0 人参与拆卡」)。
+# 故该时段把「全站在售落库 + 刷新监控队列」的节奏压到 FAST_WINDOW_INGEST_SEC 密探,给快车 6~9 次被发现的机会;
+# 时段外恢复 ONSALE_INGEST_SEC,不额外增加白天的全站请求量。全站 home/search 免 token 走直连,加密只抬直连请求量、不涉登录态。
+FAST_WINDOW_INGEST_SEC = 20        # 快车时段全站在售发现间隔(20s,与快车轮询上限 FAST_LANE_MAX_INTERVAL_SEC 对称)
+FAST_WINDOW_START = dtime(20, 0)   # 快车高发时段开始:20:00
+FAST_WINDOW_END = dtime(6, 0)      # 快车高发时段结束:次日 06:00(窗口跨午夜)
 SHOP_DISCOVER_SEC = 600            # 商家发现 + 今日新增补详情间隔(10 分钟,变化慢无需每分钟)
 MIN_INTERVAL_SEC = 0               # 单商品购买记录最小采集间隔(测试期设 0:让实测数据决定是否要抬)
 MAX_INTERVAL_SEC = 600             # 单商品购买记录最大采集间隔(10 分钟)
@@ -602,6 +610,23 @@ def _refresh_monitored(log, pool, monitored: dict, merchant_id: str, now: float)
     log.info(f"当前监控商品数:{len(monitored)}")
 
 
+def _current_ingest_interval(now: datetime) -> int:
+    """按当前时刻取「全站在售发现」间隔:快车高发时段(20:00~次日06:00)加密,其余用常规节奏。
+
+    快车寿命仅 2~3 分钟,60s 的全站发现节奏偶尔会整场错过;故该时段压到 FAST_WINDOW_INGEST_SEC
+    密探,更快把新车纳入监控。窗口跨午夜,故「晚于开始 或 早于结束」即算命中。
+
+    Args:
+        now (datetime): 当前时间。
+
+    Returns:
+        int: 本轮应使用的全站在售发现间隔(秒):命中快车时段返回 FAST_WINDOW_INGEST_SEC,否则 ONSALE_INGEST_SEC。
+    """
+    t = now.time()
+    in_fast_window = t >= FAST_WINDOW_START or t < FAST_WINDOW_END
+    return FAST_WINDOW_INGEST_SEC if in_fast_window else ONSALE_INGEST_SEC
+
+
 @retry(stop=stop_after_attempt(100), wait=wait_fixed(3600), after=after_log)
 def main_task(log):
     """常驻主流程:每分钟落全站在售(免 token) + 采本商家在售商品的购买记录。
@@ -614,7 +639,8 @@ def main_task(log):
     """
     mode = f"白名单模式 codes={WATCH_CODES}" if WATCH_CODES else f"商家模式 merchant={MERCHANT_ID}"
     log.info(f"购买记录+在售常驻采集启动 | {mode} | 购买记录频率 [{MIN_INTERVAL_SEC}s, {MAX_INTERVAL_SEC}s] "
-             f"| 在售落库每 {ONSALE_INGEST_SEC}s | 全链路免 token | 详情代理={core.USE_PROXY}")
+             f"| 在售发现常规每 {ONSALE_INGEST_SEC}s、快车时段({FAST_WINDOW_START:%H:%M}~次日{FAST_WINDOW_END:%H:%M})每 {FAST_WINDOW_INGEST_SEC}s "
+             f"| 全链路免 token | 详情代理={core.USE_PROXY}")
     pool = MySQLConnectionPool(log=log)
     if not pool.check_pool_health():
         log.error("数据库连接池异常")
@@ -627,8 +653,9 @@ def main_task(log):
     while True:
         now = time.time()
 
-        # 1) 每 ONSALE_INGEST_SEC:商家模式先落全站在售(免token)再查库刷新监控;白名单模式只按固定 code 刷新
-        if now - last_ingest >= ONSALE_INGEST_SEC:
+        # 1) 每(动态)间隔:商家模式先落全站在售(免token)再查库刷新监控;白名单模式只按固定 code 刷新。
+        #    快车高发时段(20:00~次日06:00)把发现节奏加密到 FAST_WINDOW_INGEST_SEC,更快把 2~3 分钟即售罄的快车纳入监控。
+        if now - last_ingest >= _current_ingest_interval(datetime.now()):
             try:
                 if not WATCH_CODES:
                     discover = (now - last_discover >= SHOP_DISCOVER_SEC)