排查 spiders/shop_test.py(临时测试:抓单店铺历史成交存 deca_product_record_copy1)时发现:某店铺历史成交只抓到 60 条就停,而 App 显示 1943 条。
实测确认根因是服务端接口硬限,不是采集逻辑漏采:
/api/v1/app/groupbuy/merchant/sold-list:pageSize 锁死 20(传其他值报 code=10001 "PageSize不能小于或大于20"),且 page 翻页只开放前 3 页(60 条),page=4(offset≥60)返回 total=0、空 list。响应里的 total(如 1943)只是统计展示值,不代表可拉取条数。lastCompletedAt/beforeCompletedAt/cursor/lastCode/lastId 等)全被服务端忽略;App 端手动下滑同样是 3 页到底——确认无隐藏翻页方式。/api/v1/app/groupbuy/merchant/on-sale-list 同源、同样 pageSize≤20;实测库内在售最多的商家仅 29 个(远 <60),触不到上限。结论:这类列表接口天花板就是最近 3 页,遂把在售/已售所有列表翻页上限统一收敛到 3 页,并清理因此失效的「连续无新页早停」逻辑。
统一目标:所有在售/已售列表翻页上限 = 3 页;去重一律靠唯一键 INSERT IGNORE / upsert,不依赖翻页早停。
| 文件 | 函数 | 改动 |
|---|---|---|
common/deca_sold_core.py |
get_sold_list |
MAX_SOLD_PAGES 500→3;删除 STOP_AFTER_DUPE_PAGES 常量与 _filter_new_codes 函数;去掉增量早停分支,统一翻 3 页 + INSERT IGNORE;incremental 参数保留仅兼容 run_pipeline,不再影响翻页 |
spiders/buy_record_spider.py |
get_monitored_onsale |
ON_SALE_MAX_PAGES 20→3(去重 = save_products upsert,原有 total/末页早停保留) |
reports/onsale_report/deca_on_sale_report.py |
full_onsale_sweep |
ONSALE_MAX_PAGES 30→3(去重 = save_products upsert,原有早停保留) |
spiders/onsale_alert_spider.py |
fetch_onsale |
死常量 MAX_PROD_PAGES 100→3,并用它替换硬编码 while page <= 20(去重 = product_code 字典去重 + INSERT IGNORE) |
run_pipeline 及 sold_daily_spider.py / sold_history_spider.py 的 incremental= 调用未改(保持最小影响面)。
common/deca_sold_core.py get_sold_list 核心逻辑:
# 改动前:分全量/增量两条路径,增量靠 _filter_new_codes + STOP_AFTER_DUPE_PAGES 连续无新页早停
while page <= MAX_SOLD_PAGES: # 500
...
if incremental:
new_codes = _filter_new_codes(pool, [...]) # 查库筛新 code
rows = [r for r in rows if r["product_code"] in new_codes]
pool.insert_many(table=T_PROD, data_list=rows, ignore=True)
if incremental:
if new_on_page == 0:
dupe_pages += 1
if dupe_pages >= STOP_AFTER_DUPE_PAGES: break # 连续无新页早停
# 改动后:单一路径,翻满 3 页,去重全交给唯一键 INSERT IGNORE
while page <= MAX_SOLD_PAGES: # 3
...
if rows:
pool.insert_many(table=T_PROD, data_list=rows, ignore=True) # 靠唯一键 product_code 去重
if len(items) < PAGE_SIZE: break # 不足一页=末页
sold_history/sold_daily 对每个店铺同样只能采到最近 60 条历史成交,这是接口上限不是漏采。要积累更完整历史,只能靠每日增量长期跑(每天把新产生的成交追进库),不能指望一次性深翻。total/末页早停停下,3 页上限不会触发也不会漏采。incremental 参数已成兼容摆设:get_sold_list 保留它只为不改 run_pipeline 调用链;全量/增量翻页行为现已完全一致。若后续要彻底清理,需连带改 run_pipeline 与两个 spider 的调用。py_compile;STOP_AFTER_DUPE_PAGES / _filter_new_codes 全库无残留引用。MAX_CARD_PAGES(卡密清单)、MAX_SHOP_PAGES(商家列表)为不同接口,本次未涉及,保持原值。60 条上限本质是按 completedAt 倒序的滑动窗口:接口只吐最近 60 笔成交。若某商家在相邻两次采集间隔内成交 >60 笔,最早那批会被挤出前 3 页,永久漏采。而该店铺 13:00~06:00(实测真正密集在 22:00~04:00)售卖集中,每天一次 08:00 采集无法覆盖这种密集段。
在 spiders/sold_daily_spider.py 内新增高频轻量任务(不新建脚本):
hourly_task(log):13:00~06:00 每整点跑一次,只对 HOURLY_MERCHANT_IDS 里的目标商家调 core.get_sold_list(翻 3 页 INSERT IGNORE 占坑入库),不跑详情/随机团/卡密/报告等精加工。main_task(完整 run_pipeline)不变:负责详情补抓 / 随机团回补 / 拆卡报告等精加工。解耦是方案成立的关键:run_pipeline 里"步骤 2 占坑入库"与"步骤 3~5 精加工"完全解耦——精加工均由扫库存量驱动(fill_details 扫未补详情的行、backfill_team_amount 扫 team 为 NULL 的行、fill_reports 扫无报告的行)。所以高频只需保证 product_code 尽早进库不丢,08:00 完整流程会自动扫到新行补齐;INSERT IGNORE 幂等,高频重复抓不会重复入库。
统计库内 881226408 的成交(1912 条,跨 8/3~9/11)按「日期+小时」分桶:
| 文件 | 改动 |
|---|---|
spiders/sold_daily_spider.py |
新增常量 HOURLY_MERCHANT_IDS、HOURLY_HOURS(13~23、0~6);新增 hourly_task;schedule_task 注册 18 个整点跑 hourly_task;修正模块/main_task 已失准的"连续无新页早停"注释 |
main_task 的 @retry(wait_fixed(3600)) 是同步阻塞——若 08:00 完整流程失败进入重试,会 sleep 一小时并阻塞整个 schedule 单线程,期间 hourly_task 也无法触发。要让高频不受完整流程故障拖累,需将 main_task 失败重试改为非阻塞(独立线程/进程)。HOURLY_MERCHANT_IDS 追加 ID 即可。