455884401f
Co-authored-by: chenshuobo <1119780489@qq.com> Reviewed-on: #38
5.4 KiB
5.4 KiB
meituan_coupon — 美团 CPS 券本地缓存
模型
app/models/meituan_coupon.py· ETLscripts/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(PGON CONFLICT DO UPDATE),last_seen每轮刷新。 - R(读):
- rec(智能推荐):
WHERE commission_percent>=3.0→DISTINCT ON(dedup_key)取佣金最高 → 按sale_volume_num降序分页。 - sales(销量最高):
WHERE sale_volume_num IS NOT NULL→DISTINCT ON(dedup_key)取销量最高 → 按销量降序分页。
- rec(智能推荐):
- 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:
source、city_id、brand_name、sale_volume_num、commission_percent、dedup_key、last_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 接口报错。prodDATABASE_URL必须postgresql+psycopg://。 dedup_key不含城市:md5(brand|name|price),单城没问题;将来多城会把异城同品合并,需把city_id纳入 key。product_view_sign跨渠道会变,不能当商品全局唯一 id:存储按(source, sign)、查询按dedup_key。