从比价工具到自建价格追踪:原理、选型与到手价计算 上个月帮朋友配一台显示器同一个型号三个平台标价差了将近两百块等把平台券、店铺满减、运费和支付立减全算进去到手价的排序又翻了个方向。这种事我碰上太多次了所以这些年手上一直攒着一份比价网站和比价app的清单后来嫌手动查太累干脆自己写了一套价格追踪脚本每天定点跑降价就推消息。这篇文章就把这套东西完整拆一遍市面上的比价工具分几类、各自靠什么原理工作、什么情况下用现成的、什么情况下值得自己动手、自建的话每一步怎么落地、到手价到底怎么算才不吃亏、以及我踩过的那些坑。不管你是偶尔网购想少花点冤枉钱的普通用户还是想自己搭一套价格监控的技术同学下面这些内容应该都能直接拿去用。我会把工具形态、技术实现、参数取值、避坑经验都摊开讲能抄作业的地方直接给代码和配置。1. 先把比价这件事拆开你到底需要比什么很多人一提比价就想到打开一个网站输入商品名出来一堆价格。这个理解太窄了实际需求至少分三个维度而这三个维度对应的工具形态完全不一样。搞清楚自己属于哪一类能省掉一大半试错时间。1.1 三种比价需求对应三类完全不同的工具第一种是跨平台同款比价。你想买的那件东西有明确的品牌和型号比如某个具体容量的固态硬盘、某个具体型号的耳机。你要解决的是同一个东西在哪家买最便宜。这类需求的核心难点是商品匹配——不同平台的标题写法千奇百怪同一个 SKU 可能被写成XX 1TB 固态 硬盘 NVMe和XX品牌 1T SSD M.2 接口机器要靠字段抽取和相似度算法才能认出来是同一个东西。第二种是同平台历史价格比对。你要买的东西只在某一家有或者你已经决定在哪家买了但不确定现在是不是好时机。这时候你需要的不是横向比价而是纵向看曲线——过去三个月这个价格是什么水平现在是不是在低点。这类需求的核心是长期稳定的数据采集一次抓取没意义得连续跑几十上百天。第三种是品类内横向性价比比。你还没定型号只知道自己要一台三千块左右的扫地机器人想看不同型号之间谁更值。这类需求本质上是参数对比价格只是其中一个维度工具要做的是把规格参数结构化然后做加权评分。这类是最难做的也最容易做出偏差。我自己的经验是八成的人其实只需要前两种。第三种看着美好但权重怎么设、参数怎么归一化主观性太强做出来的榜单往往跟真实需求对不上。所以我自建的那套系统只做前两种第三种靠人工看评测补。提示别一上来就想着做一个全能比价工具。先用现成工具跑两周记录下你实际查了多少次、卡在哪个环节再决定要不要自建。很多人自建完之后发现自己一周只用一次纯属浪费。1.2 比价工具的能力边界它算不出你真正付的钱这一点必须提前说清楚否则你会对比价结果产生错误信任。任何比价工具展示的价格都只是页面上的标价或者某一时刻抓到的价格快照它跟你最后实际付出去的钱之间至少隔着五层变量平台券、店铺券、满减门槛、支付方式立减、运费与保价条款。这五层里前两层有时能抓到如果页面上有接口暴露后三层基本抓不到因为它们跟你的账户状态、收货地址、支付渠道强绑定。更要命的是个性化展示。同一个商品链接不同账号打开看到的券不一样新客和老客不一样不同地区库存和运费不一样甚至同一账号在手机端和电脑端看到的活动入口都不一样。所以你会经常遇到比价工具显示 A 平台更便宜我点进去发现贵了二十块的情况——不是工具错了是它抓的是公开快照你看到的是你的账户视图。线下门店、会员专属价、企业采购价、以旧换新补贴折算这些统统不在覆盖范围内。所以我一直把比价工具定位成决策辅助不是决策依据。它帮你把候选范围从二十个缩小到三个最后那一步必须自己点进去确认。1.3 为什么大多数人其实不需要自建先把成本摆出来。一套能稳定跑的价格追踪系统最小可用版本大概需要一台常开的机器家用小主机或者一台一直开着的旧笔记本都行、一个定时任务调度、一套数据存储、一个告警通道。听起来不多但维护成本在长期——页面结构改版你得跟着改解析规则反爬策略升级你得跟着调请求节奏商品下架你得清理脏数据。我最初那版大概两百行代码三个月后涨到八百多行其中六成是在处理各种异常。如果你只是偶尔买点东西现成的浏览器插件加一个历史价格查询页面就够了五分钟能上手零维护。什么时候值得自建我总结了三个信号一是你追踪的商品数量长期超过三十个二是你需要的数据维度现成工具不给比如你想同时看价格、库存状态、优惠券面额三条曲线三是你有技术底子并且享受这个过程。三条里满足两条以上再考虑动手。2. 比价工具的四种形态与选型逻辑市面上能用的比价工具按技术形态可以分成四类。每一类的数据来源、覆盖范围、时效性和维护方式都不一样选型的时候对照自己的需求往下套就行。2.1 聚合型比价站覆盖面广但时效性最差聚合站的原理是把多个渠道的商品数据抓过来做统一清洗和匹配再用一个搜索框对外提供查询。它的优势是一次搜索横向铺开你不用一个个平台去翻劣势是数据新鲜度不可控因为它是批量抓的热门品类可能一天更新几次冷门品类可能一周才轮到一次。用这类站的时候有个技巧看它的更新时间戳。靠谱的聚合站会在每条结果旁边标价格更新于X小时前超过二十四小时的直接当参考价看别当准数。另外注意它的排序逻辑有些站默认按佣金排而不是按价格排你得手动切成价格从低到高再看。这类站还有一个隐性问题它匹配错了你不知道。因为它把不同平台的商品聚在一起靠算法判断是不是同一个东西判断错了就会把 A 型号的价格安到 B 型号头上。判断方法很简单点进去核对 SKU 和关键参数容量、颜色、版本对不上就是匹配错了。2.2 浏览器插件型最轻的落地方案适合九成用户插件是我最推荐的入门方案。它的工作方式是在你打开商品详情页的时候往页面里注入一小段脚本读取当前价格然后去查它自己的历史价格库直接在页面上画一条曲线。你不需要改变任何购物习惯该在哪买还在哪买只是多看一眼曲线。插件类的核心价值在于历史价格曲线这是判断现在是不是好价的唯一可靠依据。看曲线有两个要点一是看时间跨度只看七天没意义至少要看九十天最好能看到去年的同期二是看价格分位数把过去九十天的价格排序当前价格落在百分之二十以下就算低位落在百分之八十以上就别急。插件也有明显短板。第一它只在你打开页面时才工作没法主动提醒你第二不同插件覆盖的平台数量差别很大主流平台覆盖得好小众渠道基本没有第三插件读的是页面渲染后的 DOM遇到懒加载或者异步刷新的价格可能读到占位符。2.3 价格追踪与降价提醒把查价变成盯价这类工具解决的是我不想天天看降价了告诉我的需求。你提交一个商品链接和一个心理价位它定时去抓跌破阈值就推消息给你。相比插件它多了主动性和持续性适合那些不着急买、等个好价的场景。用这类工具最关键的一个设置是阈值怎么定。我的做法是先把历史价格拉出来取过去九十天的第十百分位作为目标价再往上浮百分之三作为触发线。为什么留百分之三的余量因为价格波动经常是一瞬间的抓取频率不够就会错过留点余量能提高命中率。另外要设置冷却时间同一个商品一天最多推一次否则遇到价格来回跳的时候你会被消息淹没。用现成提醒工具还有一个坑它抓的是标价不是到手价。标价降了二十可能同时撤了一张三十的券实际到手反而贵了十块。所以收到提醒之后还是要手动点进去核一遍券的状态。2.4 自建方案适合需要多维数据和长期积累的人自建的核心价值不在省钱而在数据自主权和维度自由。你可以同时存价格、库存、券面额、评论数、销量排名可以做任意角度的分析也可以把数据导出来自己做报表。而且数据一旦积累起来价值是复利的——跑了半年之后你对某个品类什么价位算低点会形成很准的直觉。下面是四类方案的横向对比我按实际使用体验打分形态上手成本覆盖广度数据新鲜度维护负担适合人群聚合比价站极低高中低无临时查价、比价新手浏览器插件低中高实时无日常网购主力用户降价提醒工具低中中高低不着急、等好价的人自建追踪系统高取决于你写多少完全可控高追踪量大、要多维数据的人选型逻辑很简单先用最低成本的方式跑起来遇到天花板再升级。我见过太多人一上来就要自建折腾两周之后项目烂尾最后连个插件都没装。3. 自建价格追踪系统的实操过程这一节是给真打算动手的人看的。我会按数据模型、抓取、匹配、告警四层往下拆每层给出可直接运行的代码骨架和参数取值的理由。需要说明的是下面涉及的页面选择器都是示例结构实际落地时要按你面对的页面自行调整。3.1 环境与数据模型设计先说环境。最小依赖就四个requests负责请求、parsel或beautifulsoup4负责解析、sqlite3负责存储数据量不大时够用上十万条再换 PostgreSQL、apscheduler或者系统自带的 cron 负责调度。Python 3.10 以上就行不用搞太复杂。数据模型我建议分四张表这个结构是我改了三版之后定下来的-- 商品主表一个逻辑商品一行跨平台共享 CREATE TABLE product ( id INTEGER PRIMARY KEY, brand TEXT NOT NULL, -- 品牌归一化后的值 model_key TEXT NOT NULL, -- 型号归一化后的值 spec_json TEXT, -- 关键规格JSON 存储 created_at TEXT DEFAULT (datetime(now,localtime)), UNIQUE(brand, model_key) ); -- 报价表一个商品在一个渠道的一个报价 CREATE TABLE offer ( id INTEGER PRIMARY KEY, product_id INTEGER NOT NULL REFERENCES product(id), channel TEXT NOT NULL, -- 渠道代号别写真实平台名 url TEXT NOT NULL, shop_name TEXT, -- 店铺名脱敏后存 status INTEGER DEFAULT 1, -- 1 在售 / 0 下架 UNIQUE(product_id, channel, url) ); -- 价格历史只追加不修改这是核心资产 CREATE TABLE price_history ( id INTEGER PRIMARY KEY, offer_id INTEGER NOT NULL REFERENCES offer(id), price REAL NOT NULL, -- 标价 coupon REAL DEFAULT 0, -- 可抓到的券面额 shipping REAL DEFAULT 0, -- 运费 captured_at TEXT DEFAULT (datetime(now,localtime)) ); CREATE INDEX idx_ph_offer_time ON price_history(offer_id, captured_at); -- 提醒规则 CREATE TABLE alert_rule ( id INTEGER PRIMARY KEY, offer_id INTEGER NOT NULL REFERENCES offer(id), threshold REAL NOT NULL, cooldown_h INTEGER DEFAULT 24, last_fired TEXT, enabled INTEGER DEFAULT 1 );为什么把product和offer拆开因为同一个商品在五个渠道有五个链接价格历史要挂在链接上每个渠道的价格曲线是独立的但商品维度的信息品牌、型号、规格是共享的。如果不拆你后面想做跨渠道最低价曲线就得写一堆聚合查询很难受。price_history只追加不更新这一点是刻意设计的。价格数据一旦被覆盖就没法回溯而历史数据恰恰是这套系统最值钱的部分。我一般按季度归档超过一年的数据压缩后单独存一份。3.2 抓取层页面解析与请求节奏控制抓取层的核心不是能不能抓到而是能不能长期稳定地抓到。这两件事差距很大。前者写二十行代码就能做到后者需要考虑请求节奏、失败重试、结构变更、数据校验四件事。先给一个最简骨架用来说明结构import time, random, sqlite3 import requests from parsel import Selector HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9, } def fetch(url: str, retry: int 3) - str | None: for i in range(retry): try: # 间隔打散避免固定节奏形成明显特征 time.sleep(random.uniform(2.0, 5.0)) r requests.get(url, headersHEADERS, timeout15) if r.status_code 200 and len(r.text) 2000: return r.text except requests.RequestException: pass time.sleep(2 ** i) # 退避2s, 4s, 8s return None def parse_price(html: str) - dict | None: sel Selector(texthtml) # 下面三个选择器是示例实际要按页面结构调整 price_txt sel.css(span.price-now::text).get() coupon_txt sel.css(div.coupon-amount::text).get(0) stock_txt sel.css(div.stock-state::text).get() if not price_txt: return None return { price: float(price_txt.replace(¥, ).replace(,, ).strip()), coupon: float(coupon_txt.replace(¥, ).strip() or 0), in_stock: 无货 not in stock_txt, }几个参数取值的理由说明一下。time.sleep(random.uniform(2.0, 5.0))里的区间不是随便定的——间隔太短会显著提高被限流的概率间隔太长则跑不完一轮。按我的实测单个渠道一天追踪三十个商品2 到 5 秒的随机间隔加上串行执行一轮大概三到五分钟完全够用。退避用2 ** i的指数形式是因为失败往往是临时性的网络抖动、服务端短时限流等一会儿再试成功率很高。关键的一条抓取失败不要重试太多次。我最初设了五次重试结果遇到页面结构改版的时候每个商品都在那儿死磕五次一轮跑下来几十分钟日志全是报错。后来改成三次失败就记一条parse_failed跑完之后看失败率超过百分之十再人工介入。还有一个必须做的环节是数据合理性校验。抓回来的价格要在合理区间内比如你追踪的是一台笔记本抓回来一个九块九那百分之百是解析错了可能是抓到了配件价或者分期月供。我的做法是给每个商品存一个基准价首次抓到的价格后续抓取值低于基准价的百分之十五或者高于百分之三百直接标记为异常不入库。这个规则拦住了我遇到过的绝大多数脏数据。3.3 商品匹配怎么把两个链接认成同一个东西这是整套系统里最容易翻车的一环也是最值得花时间打磨的一环。匹配做得不准后面的价格曲线全是错的——你会看到一条曲线突然腰斩点进去发现是不同容量版本混进来了。我的做法是两级匹配先规则后算法。第一级是字段抽取。从标题里用正则把品牌、型号、关键规格抠出来。型号识别是最关键的一步因为同一品牌下型号是唯一的。规则可以这么写import re BRANDS [示例品牌甲, 示例品牌乙, 示例品牌丙] # 实际按你的品类补 SPEC_PATTERNS { capacity: r(\d)\s*(?:GB|G|TB|T)\b, color: r(黑|白|银|灰|蓝|红)色?, version: r(标准版|pro版|青春版|旗舰版), } def extract(title: str) - dict: t re.sub(r\s, , title).lower() brand next((b for b in BRANDS if b.lower() in t), None) m re.search(r([a-z]{1,4}[-]?\d{2,5}[a-z]{0,3}), t) # 型号形态字母数字 model m.group(1) if m else None spec {} for k, p in SPEC_PATTERNS.items(): hit re.search(p, t) if hit: spec[k] hit.group(1) return {brand: brand, model: model, spec: spec}第二级是相似度兜底。有些标题写得实在太随意型号规则抠不出来这时候退回到基于标题的相似度计算。我用的是分词后的 Jaccard 相似度阈值定在0.82。为什么是 0.82我拿自己的历史数据做过一轮标注0.7 以下误合并明显增多0.9 以上又会漏掉大量本该合并的0.82 是在这两端之间取的平衡点。这个值跟品类有关电子类标题规范度高可以调到 0.85服饰类标题噪声大建议降到 0.75。匹配完成之后一定要做人工抽检。我一般每周随机抽二十条合并记录人工核对错误率超过百分之五就回头调规则。这一步看着麻烦但能省掉后面无数次这个价格怎么不对的困惑。3.4 调度与告警让系统自己跑起来调度我用的是系统 cron简单可靠不用额外依赖。频率怎么定我的经验是一天两到三次就够早上九点、下午三点、晚上九点各一轮。原因是大促期间的价格变动集中在整点和特定时段太密的抓取除了增加被封风险没有别的收益。cron 配置大概长这样# 每天 09:05、15:05、21:05 各跑一轮 5 9,15,21 * * * cd /opt/price-tracker /usr/bin/python3 main.py logs/run.log 21 # 每天 22:00 生成当日降幅榜推到手机 0 22 * * * cd /opt/price-tracker /usr/bin/python3 report.py logs/report.log 21告警的触发逻辑要写清楚避免消息风暴def check_and_alert(conn, offer_id: int) - bool: rule conn.execute( SELECT threshold, cooldown_h, last_fired FROM alert_rule WHERE offer_id? AND enabled1, (offer_id,) ).fetchone() if not rule: return False threshold, cooldown_h, last_fired rule row conn.execute( SELECT price, coupon, shipping FROM price_history WHERE offer_id? ORDER BY captured_at DESC LIMIT 1, (offer_id,) ).fetchone() if not row: return False final_price row[0] - row[1] row[2] if final_price threshold: return False # 冷却检查同一商品一天最多推一次 if last_fired: from datetime import datetime, timedelta if datetime.now() - datetime.strptime(last_fired, %Y-%m-%d %H:%M:%S) \ timedelta(hourscooldown_h): return False # 推送逻辑省略落到具体的通知通道即可 conn.execute(UPDATE alert_rule SET last_fireddatetime(now,localtime) WHERE offer_id?, (offer_id,)) conn.commit() return True这里有个我踩过的坑告警要推到手价不能推标价。前面提过标价降了但券撤了的情况很常见。上面代码里final_price 标价 - 券 运费就是这个原因。另外建议在消息里带上较七日均价低多少这样你一眼就能判断值不值得动手不用再点进去查曲线。4. 比价结果怎么读把到手价算明白前面全是数据采集这一节讲怎么把采到的数字变成决策。九成的人比价出错不是工具不好是到手价算错了。同一件商品标价低的渠道到手价可能更贵这种情况我每个月都要碰上几次。4.1 到手价的完整公式与逐项拆解我把到手价拆成一个公式按这个走基本不会再漏项到手价 标价 − 平台券 − 店铺券 − 满减分摊 − 支付立减 − 返现/红包 运费 必需配件逐项说清楚。标价就是详情页展示的价格注意有些页面展示的是划线价或者起售价比如某配置的最低配价格要看清是不是你选的那个 SKU 的价格。平台券是平台发的、跨店可用的通常有门槛。店铺券是店里发的一般能和平台券叠加但有些店会设定不与平台券同享。满减分摊是最容易算错的部分——满三百减四十你只买了一件二百八为了凑单又加了个二十的东西那么真实的分摊是按比例来的你真正想要的那件只摊到 40×(280/300)≈37.3 元。很多人直接把四十全算到主商品头上导致对省了多少钱产生误判。支付立减跟你的银行卡和支付渠道绑定比价工具抓不到只能自己确认。返现/红包要打个折扣看——有些是下单后返、下次才能用实际价值要按使用概率折算我个人是按五折估的。必需配件这一项最容易被忽略某渠道标价便宜三十但标配不含某个配件加上配件反而贵二十这种情况在电子产品上特别常见。举个具体例子说明这个公式怎么用项目渠道甲渠道乙标价12991259平台券−60−30店铺券−0−20满减分摊−46.7满1500减50凑单0运费012必需配件039不含电源到手价1192.31260表面差价甲比乙贵40实际甲便宜67.7这种表我建议在决定下单前花三十秒手填一遍。填过十次之后你会有直觉看到优惠结构就知道大概谁更便宜。4.2 识别几种常见定价套路比价工具有一个天然的局限它看到的是现在看不到商家的策略。所以有几类情况必须靠人工识别。先涨后降。这是大促期间最普遍的现象。判断方法很简单调出九十天价格曲线看大促前两周有没有明显抬价。如果抬过那么现在的降后价可能还没回到抬价前的水平。我给自己定的规则是——当前的到手价必须低于过去九十天的第二十百分位才算真降否则一律按营销降价处理。券门槛陷阱。券面额大不代表好要看门槛。一张满两千减三百的券听起来比满三百减五十划算但你实际只买八百块的东西前一张用不上等于零。所以比价的时候要看**我能用上的券**不是页面上写的券。赠品折算陷阱。有些渠道不降价改送一堆东西然后宣传总价值XXX。我的处理方式是只把赠品按二手市场流通价的五折折算且总量不超过主商品价格的百分之二十超过的部分不认。因为绝大多数赠品你其实用不上或者用不上那么多个。版本混淆。这个坑在存储、显示器、笔记本上最常见。同名商品可能有不同容量、不同刷新率、不同接口版本价格差好几百。比价的时候必须严格核对型号后缀和关键参数别被同名骗了。保价条款。有些渠道支持价格保护买了之后十五天内降价可以退差。这是个被严重低估的省钱手段。我把这条也纳入了决策如果两个渠道到手价差在百分之三以内选保价政策更宽松的那个。因为大促期间降价概率很高保价相当于给自己买了个免费的保险。5. 常见问题与排查技巧实录这一节是我这几年踩坑的汇总。系统跑得越久你越会发现问题不在能不能抓到而在抓到的东西能不能信。5.1 抓不到、抓错、抓到脏数据抓不到通常有三种原因。一是页面需要登录态公开请求拿回来的是登录页长度明显短很多——我在fetch()里加了len(r.text) 2000的判断就是这个目的。二是数据是异步加载的HTML 里只有骨架真实价格靠后续接口填充这时候得去找那个接口或者换成能执行脚本的方案。三是触发了限流返回状态码可能是 200 但内容是验证页这种情况只能降频。抓错最典型的是抓到月供价或者起售价。月供价往往只有几十块跟真实价格差一个数量级用前面说的基准价校验就能拦住。起售价的问题更隐蔽——页面标1999起你抓了个 1999但你实际要买的配置是 2499。这种情况必须绑定具体 SKU 的链接去抓不能用搜索页或者列表页的价格。脏数据还有一个来源是币种和单位。有些页面显示1,299.00带上千分位逗号和小数点直接float()会报错有些显示1299元起多一个汉字。我的清洗规则是先re.sub(r[^\d.], , txt)去掉所有非数字非点字符再处理多个小数点的情况。这条规则救了我好几次。5.2 告警失灵与误报告警不出先查三件事规则表里enabled是不是 1、库存状态是不是变成了下架下架商品的新价格压根抓不到自然不触发、以及阈值是不是设得不合理。我最初犯的错是把阈值设成了比当前价低百分之十结果那个商品早就跌到那个位置以下了规则一直处于已触发状态反而再不推送。误报多半是数据抖动引起的。比如页面偶尔返回一个9.9的运费模板占位被当成商品价格存了进去立刻触发告警。解决办法是连续两次抓取都满足条件才推送用两个数据点做确认能过滤掉九成以上的抖动。还有一种情况是时区问题。如果你服务器是 UTC数据库里存的是本地时间字符串两边对不上冷却时间计算就会出错导致要么一直不推要么反复推。我现在统一用本地时间字符串存SQLite 的datetime(now,localtime)直接解决省心。5.3 一份可以直接对照的排查速查表现象最可能原因快速验证方式处理办法某个商品价格一直是旧值页面结构改了解析返回 None手动打开链接比对选择器更新选择器加parse_failed计数价格曲线突然腰斩抓到了配件价或月供价看原始响应里该字段上下文加基准价区间校验两个不同商品被合并相似度阈值太低看合并记录的标题对提高阈值到 0.85补型号规则同一商品拆成多条标题写法差异大抽检合并日志补同义词典降阈值到 0.75告警一天推十几次没设冷却时间查last_fired字段设cooldown_h24告警一个都不推阈值已长期满足查阈值与当前价关系重置阈值改按分位数设一轮跑不完重试次数太多 串行看日志耗时分布重试降到 3 次失败即跳过数据落库时间混乱时区不一致比对系统和数据库时间统一用本地时间6. 把比价变成习惯一套轻量的日常工作流工具搭好只是开始真正省钱的是把比价嵌进购物流程。我现在的做法是三步走全程不超过五分钟但一年下来确实省了不少冤枉钱。6.1 买之前的三个动作第一个动作是建一个待买清单。我不再想到就买而是把所有想买的东西先扔进一个清单里标上心理价位和最晚需要的时间。这个动作的意义在于把冲动消费和真实需求分开——清单放两周之后你会发现至少三分之一的条目你自己就划掉了。第二个动作是给清单里的每项查一次九十天曲线。看当前价在什么分位如果已经在百分之二十以下直接买如果在百分之五十以上挂个提醒等如果在中间看急不急。这一步用插件三十秒能完成。第三个动作是下单前手算一遍到手价。就用第 4 节那个表格填一遍。刚开始觉得麻烦填十次之后你会发现大部分情况下看一眼就知道只有优惠结构复杂的才需要认真算。6.2 长期维护的几个实用建议系统跑起来之后最怕的是悄悄失效。我有一次出差两周回来发现某个渠道的抓取全挂了原因是那个渠道改了个字段名。所以我后来加了一个健康度日报每天早上把昨日抓取成功率、新增商品数、异常条数推一条消息给我成功率低于百分之九十就标红。这条消息很短扫一眼就知道系统还活着没有。另一个建议是定期清理。下架的商品、失效的链接、半年没动过的提醒规则每季度清一次。不清的话你的数据库会越来越臃肿查询越来越慢最后你自己都不想打开它。我现在的做法是给每个offer加一个last_seen字段超过九十天没抓到有效价格的自动标记为休眠不参与日常轮询。最后分享一个我自己觉得最有用的心得不要迷信任何一个数字。比价工具给的是快照商家给的是营销话术你自己算的到手价才是真的。我在实际操作中的体会是凡是需要凑单叠加限时才能拿到的低价八成最后都拿不到反而因为赶时间下了个更贵的单。所以现在我给自己定了个规矩——只买结构简单、一眼能算清的低价复杂的优惠结构一律放弃。这个规矩让我少省了一点钱但也让我少生了很多气。