Files
shaguabijia-app-server/docs/database/meituan_coupon.md
T

5.4 KiB

meituan_coupon — 美团 CPS 券本地缓存

模型 app/models/meituan_coupon.py · ETL scripts/pull_meituan_coupons.py · 接口 meituan-feed(rec/distance)/ meituan-top-sales(sales)· ← 索引 · 总览

把美团联盟 CPS 的券定时抓进本地库,供「智能推荐(佣金≥3%)」「销量最高」从库里捞、本地去重排序,不每次实时打美团(美团对销量排序支持差、有 402 限流和召回上限)。北京单城试点。

用在哪 / 增删改查

  • C/U(ETL upsert):scripts/pull_meituan_coupons.py 每小时全量抓 3 路(search_waimai 搜「外卖」/ search_meishi 搜「美食」/ store_supply 到店多业务线供给),按 (source, product_view_sign) upsert(PG ON CONFLICT DO UPDATE),last_seen 每轮刷新。
  • R(读):
    • rec(智能推荐):WHERE commission_percent>=3.0DISTINCT ON(dedup_key) 取佣金最高 → 按 sale_volume_num 降序分页。
    • sales(销量最高):WHERE sale_volume_num IS NOT NULLDISTINCT ON(dedup_key) 取销量最高 → 按销量降序分页。
  • D(清理):见下「过期清理策略」。

字段

类型 约束 / 默认 说明
id Integer PK, autoincrement
source String(16) index, NOT NULL 召回来源:search_waimai / search_meishi / store_supply
platform Integer NOT NULL 1 外卖/到家, 2 到店
biz_line Integer nullable 到店子类:1到餐 2到综 3酒店 4门票
city_id String(32) index, NOT NULL 城市 id(当前恒为北京 WKV2HMXUEK634WP64CUCUQGM64)
product_view_sign String(128) NOT NULL 换链主键;按召回渠道生成、跨渠道会变,不能当商品全局 id
sku_view_id String(128) nullable
name String(256) nullable 商品名(解析截断 [:256])
brand_name String(128) index, nullable 品牌名([:128])
sell_price_cents Integer nullable 现价(分)
original_price_cents Integer nullable 原价(分)
head_url String(512) nullable 头图(去 @ 后缀,[:512])
sale_volume String(32) nullable 原始销量档:如「热销1w+」
sale_volume_num Integer index, nullable 销量排序数值(取下界):1w+ → 10000
commission_percent Float index, nullable 佣金比例,数值即百分数(1.4 = 1.4%);美团原值 140 ÷ 100
commission_amount_cents Integer nullable 预估佣金(分)
poi_name String(128) nullable 最近门店名([:128])
available_poi_num Integer nullable 可用门店数
delivery_distance_m Float nullable 距离(米);相对抓取时的城市默认点,对用户位置无意义,rec 接口置空不返
dedup_key String(64) index, NOT NULL 跨源去重键 = md5(brand_name|name|sell_price_cents)
raw JSON / JSONB NOT NULL, default {} 整条原始返回(避免漏字段重抓);PG 上为 JSONB
first_seen DateTime(tz) server_default now() 首次抓到
last_seen DateTime(tz) index, server_default now() 最近一轮抓到(过期清理依据)
updated_at DateTime(tz) server_default now(), onupdate now()

索引与约束

  • 唯一约束 uq_meituan_coupon_source_sign(source, product_view_sign)—— ETL upsert 的冲突目标。
  • 单列 index:sourcecity_idbrand_namesale_volume_numcommission_percentdedup_keylast_seen
  • 字段核对(2026-06-10):模型 ↔ 迁移(meituan_coupon_table)↔ ETL 解析三处一致;解析截断宽度均 ≤ 列宽(name256 / brand128 / poi128 / head512 / sign128,dedup_key = md5 32 字符 ≤ 64)。
  • 索引评估:rec / sales 查询的理想索引是复合 (dedup_key, sale_volume_num DESC)(dedup_key, commission_percent DESC)(匹配 DISTINCT ON);但当前单城几千行,seq scan 即亚毫秒,现有单列索引已够用,复合索引留作多城 / 放量时再加,避免过早优化。

过期清理策略

  • ETL 每轮(每小时)末尾按 last_seen 的 TTL 清理:DELETE WHERE last_seen < now() - prune_hours(默认 24h,--prune-hours 可调,0=不清)。
  • 仍在架的券每小时被刷新 last_seen,永不被清;只有下架 / sign 轮换的残留超 24h 宽限才删。宽限期用于吸收几次抓取失败。
  • 券自身到期无需单独处理:到期后美团不再返回 → 自然 last_seen 老化被清。
  • 护栏(2026-06-10):prune 仅在**本轮确有入库(total>0)**时执行。美团整体故障时本轮可能 0 入库(脚本不抛异常、只是抓回空),若仍照常 prune,连续故障会按 last_seen 把全表删空;加 total>0 护栏后,0 入库轮跳过清理并打日志。
  • 表只增量 upsert + 少量 delete,PG autovacuum 足以回收死元组,当前规模无需手动 VACUUM。

注意

  • 仅 PostgreSQL:ETL upsert(postgresql.insert(...).on_conflict_do_update)与读取的 DISTINCT ON 都是 PG 专用语法;库若是 sqlite,ETL 灌不进、rec/sales 接口报错。prod DATABASE_URL 必须 postgresql+psycopg://
  • dedup_key 不含城市:md5(brand|name|price),单城没问题;将来多城会把异城同品合并,需把 city_id 纳入 key。
  • product_view_sign 跨渠道会变,不能当商品全局唯一 id:存储按 (source, sign)、查询按 dedup_key