尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

7x24小时优惠雷达:用Python+SQLite搭建自动化价格监控系统

7x24小时优惠雷达:用Python+SQLite搭建自动化价格监控系统 朋友圈里时不时就有人晒出“神单”几块钱的蓝牙耳机、半价的戴森、凑单后几乎白拿的粮油大礼包。每次看到这种截图第一反应通常是“又是营销号剧本吧”但点进去仔细一看下单记录、物流信息都在实打实是真人真单。你再翻翻自己打开过的App同样的商品价格差了两三倍。问题出在哪儿不是你不会领券也不是你运气差而是你根本不知道那个“神单通道”什么时候开启。等到朋友晒出来再冲过去早就恢复原价了。“神单”的生命周期通常只有几分钟到几十分钟是一种典型的强时效性信息。指望人肉盯盘、手动刷新、隔三差五刷一下优惠频道几乎不可能抓住。真正靠谱的做法是建一套自动化的监控系统让机器7x24小时帮你盯着目标商品的价格、优惠券和活动状态一旦命中你设定的“神价”条件立刻把消息推到你手机上。这就是标题里说的「7x24小时优惠雷达」。这篇文章不讲玄学直接从实际项目出发把整套系统的设计思路、核心代码、部署方式和避坑经验都拆开讲。不管你是想认真省钱的技术党还是想拿这个当自动化练手项目的学生都能照着抄。1. 为什么你总在错过“神单”1.1 神单的时间窗口到底有多短先别急着写代码先搞清楚我们到底在和什么东西赛跑。“神单”的形成机制很多但绝大多数都和时间强相关。平台大促的限时限量秒杀、品牌方半夜偷偷上的清仓价、直播间专属券叠加平台满减、某个凑单公式恰好触发多重优惠这些都属于“过了这村就没这店”的短时效信息。我做过一个简单的抽样统计拿某电商平台上一款经常出现神价的蓝牙耳机来说从价格跌破历史低点到恢复原价平均时间大概是25分钟。更夸张的是某些叠加券场景几分钟内库存就被清空。这里面的核心变量不是价格本身而是“你能多快知道这个消息”。如果你靠手动刷新或者刷朋友圈信息到你眼前的时候大概率已经凉了。有人可能会说那我是不是只要多关注几个购物群、羊毛博主就行了答案是可以但你会遇到两个麻烦。第一信息噪音极大。那些群里每分钟几十条消息真正有用的神单可能只有一两条你要么开着通知被烦死要么关掉通知继续错过。第二人的精力是有限的。你可以坚持三天但坚持不了三个月。人肉盯盘本质上不可持续而优惠雷达这类工具的价值就是把“持续性”交给机器你只负责在收到通知的时候做一个决策买还是不买。1.2 人肉盯盘和自动化监控的差距这张表我整理了很久把两种方式的差别列得比较直接对比维度人肉盯盘自动化监控监控时长受限于个人精力每天最多几小时7x24小时不间断反应速度看到信息到下单可能要10-30分钟秒级检测、秒级推送监控商品数量同时盯着3-5个就很容易乱几十上百个都能稳定跟踪判断标准靠感觉“差不多便宜了”设定好历史低价/目标价机械执行可复盘性基本靠记忆每次价格变动都有记录可追溯成本时间成本极高一次性搭建成本后续几乎零成本这里说的“自动化监控”并不是什么黑科技本质就是三件事定期抓取目标商品的价格信息、和预设的“神价线”做对比、条件触发时用推送服务把提醒发到你手机上。真正做到位整套系统的技术门槛不高一个Python脚本加一台能长期开机的设备就够把每一个“神单”从概率事件变成可预期的事件。2. 方案设计与工具选型2.1 把需求拆成四个模块任何项目开始之前先拆需求。优惠雷达看起来很神秘实际上拆开就四个模块。数据采集模块负责定时抓取目标商品的价格、标题、优惠活动等信息。这个模块的难点不在“抓”而在“怎么稳定地抓”。电商页面改版频繁、有反爬机制如果直接用最基础的requests去拉HTML解析很可能用不了几天就挂。所以这个模块里要预留接口适配层尽量优先走公开API没有API再考虑页面解析。数据处理模块负责把抓到的原始数据清洗成结构化数据再和本地的历史价格做对比。清洗的目的是去掉价格文本里的干扰项比如“券后价”“到手价”“满199减100”这类促销文案提取出用户实际需要支付的最低价格。然后和设定好的期望值做比较判断是否命中“神价条件”。通知推送模块负责把命中结果推送给用户。现在国内可用的免费推送渠道很多比如Server酱、PushPlus、钉钉群机器人、企业微信应用消息等。这个模块最好设计成可插拔的方便切换渠道。调度与存储模块负责让整个系统按固定频率循环运行并把历史数据存储下来。存储不需要大数据库SQLite这种轻量的就够了后续做趋势分析也用得上。2.2 工具选型为什么是Python SQLite cron先回答一个常见问题为什么用Python很简单生态成熟、上手快、写爬虫和处理文本都省事。requests、BeautifulSoup、lxml这些库足够搞定绝大多数抓取任务。你要是熟悉Node.js或Go也能做但没必要在这个项目里给自己增加难度。存储为什么用SQLite而不是MySQL因为这个项目的单机属性非常强数据量也不大。SQLite单文件、零配置、查起来方便一个文件搞定所有数据备份需求。你总不能为了盯价格专门装一个MySQL服务吧完全是大炮打蚊子。调度为什么用cron而不是写死循环加sleep因为cron是系统级的定时方案到点自动拉起脚本、跑完自动退出不吃内存、不会越跑越卡。而如果你在Python里写while True sleep一旦脚本异常退出没有人帮它重新拉起整个监控就断了。用cron哪怕脚本崩溃了下一分钟系统还会再拉起一个新的天生自愈。这个区别很关键后面部署的时候你会有体感。2.3 推送渠道怎么选推送是整个系统的“最后一公里”选不好等于白干。我的建议是优先用企业微信应用消息或钉钉群机器人原因三句话稳定、免费、能封装成简单的Webhook调用。Server酱和PushPlus也可以注册就能拿到一个API地址代码里只要几十行就能调用。用哪个取决于你平时手机上的App打开率推送发过去你能第一时间看到才是王道。有一点要提醒不要用邮箱推送当主力。国内很多邮箱App的推送是有延迟的极端情况下延迟十几分钟等你看到价格早就变了。同样的道理短信推送虽然及时但按条收费不适合消息量大的场景。微信/钉钉这类即时通讯工具是最合适的强烈推荐。3. 核心实现搭建一台自己的7x24小时优惠雷达3.1 第一步先梳理监控清单动手写代码前先建一张“监控清单”。不要贪多刚开始建议控制在10个商品以内。每个监控项包含四个字段商品名称、商品链接、期望价、监控频率。期望价的设置很关键要根据商品的历史价格走势来定不要拍脑袋。比如一款标价399的蓝牙耳机近三个月的日常价格稳定在259左右历史最低到过169那你可以把期望价设为189给自己留一点冗余避免因为几块钱的波动产生过多无效通知。监控清单我建议用JSON文件维护简单直观后续增删改都方便。格式大概是这样的[ { name: 某品牌蓝牙耳机, url: https://item.example.com/100234, expect_price: 189.0, interval_minutes: 5 }, { name: 某品牌智能手环, url: https://item.example.com/100567, expect_price: 129.0, interval_minutes: 10 } ]字段含义一目了然。interval_minutes是每个商品单独的监控频率大促前可以调密一点日常就松松地挂着。3.2 第二步写价格抓取与解析模块这个模块是整个系统的“眼睛”。先说一个结论能走公开接口就不要解析HTML。很多平台都有移动端的H5接口返回的是JSON数据解析起来比HTML稳定得多。但具体接口的鉴权方式、参数签名五花八门这篇文章里我就用最通用的方式写个示例方便你理解整体逻辑。核心思路是这样带上用户代理等基础请求头请求商品页面或H5接口拿到HTML后用BeautifulSoup定位价格节点抽取出数字。这里的关键技巧是不要只抓页面源码里的第一个价格数字要优先找带有特定class或data属性标记的“成交价”节点。import requests from bs4 import BeautifulSoup import re def fetch_price(url): 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, } resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) # 思路优先从类名包含 price 的节点里提取数字 # 不同平台节点结构差异很大需要按实际情况调整选择器 price_node soup.select_one(.price, .current-price, [class*price]) if not price_node: return None match re.search(r(\d(?:\.\d{1,2})?), price_node.get_text()) if not match: return None return float(match.group(1))这段代码看着简单实际使用时你多半要改选择器。我给你的建议是先用浏览器开发者工具手动找到页面上那个“真实到手价”对应的DOM节点再回头写select表达式。千万不要指望一段选择器走天下不同平台的结构千差万别改版也是家常便饭。如果你要监控的商品很多记得给requests加一个Session复用的逻辑别每次都新建连接一是慢二是有可能被平台的防火墙误判成异常流量。3.3 第三步完成价格判断与历史记录抓到价格后紧接着就要做判断。判断逻辑听起来简单——“当前价小于期望价就通知”但真这么写你的手机一天能被通知轰炸到没电。因为电商的价格是动态的偶尔会有一个瞬时低价但库存只有一两件你看到的时候早没了。所以判断逻辑要带条件。我实际用的是“三重判断”当前价低于期望价 当前价接近历史最低价比如不高于历史最低价的1.05倍 同一个商品在设定的冷却时间内没有发过重复通知。前两个条件保证“这是真神价”第三个条件保证“你不被骚扰”。import sqlite3 import time DB_PATH price_watch.db def init_db(): conn sqlite3.connect(DB_PATH) conn.execute( CREATE TABLE IF NOT EXISTS price_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price REAL NOT NULL, created_at INTEGER NOT NULL ) ) conn.execute( CREATE TABLE IF NOT EXISTS notify_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price REAL NOT NULL, notified_at INTEGER NOT NULL ) ) conn.commit() return conn def is_worthy_notify(conn, name, price, expect_price): # 条件1价格达到期望值 if price expect_price: return False # 条件2接近历史最低价历史最低价从 price_history 里取 row conn.execute( SELECT MIN(price) FROM price_history WHERE name ?, (name,) ).fetchone() history_min row[0] if row and row[0] else None if history_min is not None and price history_min * 1.05: return False # 条件33小时内没有通知过该商品 last conn.execute( SELECT MAX(notified_at) FROM notify_log WHERE name ?, (name,) ).fetchone() if last and last[0] and time.time() - last[0] 3 * 3600: return False return True这段代码里请注意冷却时间的设计3小时是我自己调出来的经验值。大促期间可以手动把冷却调短到30分钟日常挂在后台这个3小时刚刚好。通知次数少了不烦人但又不至于漏掉持续降价的商品。3.4 第四步接入推送通知模块判断命中之后下一步就是把消息推送出来。这里用Server酱的接口做示例逻辑很简单向sctapi.ftqq.com发送一个POST请求把title和desp两个参数带上消息就会推到你的微信上。import requests SERVERCHAN_KEY 你的Server酱SendKey def send_notification(title, content): url https://sctapi.ftqq.com/{}.send.format(SERVERCHAN_KEY) data { title: title, desp: content, } resp requests.post(url, datadata, timeout10) resp.raise_for_status() return resp.json()推送文案要带什么信息我踩过坑只发“价格已降”这种标题你看到消息后还要打开App去翻商品详情非常耽误事。比较好的做法是把商品名称、当前价格、期望价格、历史最低价、购买链接原文链接全部塞进推送内容里。手机收到消息的当下看一眼就能决定要不要出手不用再跳转。这几十秒的时间差在秒杀场景里有时候就是天壤之别。钉钉群机器人和企业微信机器人的调用方式大同小异都是往Webhook地址POST一个JSON你搜一下官方文档就能搞定。建议你把推送函数再封装一层def notify(name, current_price, expect_price, history_min, url): title f神单雷达{name} 降到 {current_price} content ( f### {name}\n\n f- 当前价格{current_price}\n f- 你的期望价{expect_price}\n f- 历史最低{history_min}\n f- [去购买]({url})\n ) send_notification(title, content)3.5 第五步用cron把系统变成7x24小时到这里脚本本身已经能跑了。接下来要解决的是“怎么让它一直跑”。这里就是不折腾Docker、不折腾K8s直接用Linux自带cron的落地方式。打开crontab配置crontab -e加入下面这行*/5 * * * * cd /home/yourname/price-radar /usr/bin/python3 radar.py radar.log 21意思是每5分钟执行一次radar.py。radar.py内部会读取监控清单逐个处理商品跑完就退出。这样就算某次运行因为网络波动卡死cron下个周期还会重新拉起一个进程不需要你人工干预。日志输出到radar.log方便事后排查。如果你的监控策略比较复杂比如白天5分钟一次、凌晨30分钟一次可以拆成多个cron条目用不同参数启动同一个脚本*/5 * * * * /usr/bin/python3 /home/yourname/price-radar/radar.py --mode daytime radar.log 21 */30 0-6 * * * /usr/bin/python3 /home/yourname/price-radar/radar.py --mode nighttime radar.log 21凌晨平台补数据的概率低30分钟一次的粒度足够了还能给目标站点的反爬策略减少压力。4. 常见问题与排查技巧实录4.1 价格抓错了或者完全抓不到怎么办这个坑基本每个跑过采集任务的人都会遇到。页面结构改版、价格节点类名变化、接口加了动态参数签名、请求频率太高压根不返回数据原因千奇百怪。我的排查套路是先在浏览器里打开同样的链接确认页面本身能访问然后在开发者工具里找到真实的价格节点如果你的选择器选出来的内容和浏览器里显示的不一致那就是选择器写错了最后看日志里requests返回的状态码和页面长度如果返回200但内容长度异常多半是触发了平台的反爬被导向了验证码页或空壳页。反爬这块要主动认怂。把抓取频率控制在最低可用水平对同一个站点请求间隔不要低于1秒。不要并发抓同一个域名不要用同一个IP高频访问。如果监控的商品数量特别多可以给requests加上一个简单的限速器比如每个请求之间sleep一个随机数import random import time time.sleep(random.uniform(1.0, 2.5))另外要把解析失败的商品单独记录到一个error_log里不要因为一个商品解析不了就让整个脚本崩掉。用try/except把每个商品的抓取包起来单个失败不影响其他商品。4.2 通知轰炸怎么破第一次跑起来的时候如果某个商品正在大降价你的手机可能五分钟内收到二十条通知。这不是好事便宜再多也没人想被骚扰。通知频率控制要做在判断逻辑里不要依赖推送端去限制。我建议在notify_log表的基础上增加两个维度的限制单个商品冷却时间、全局每日上限。比如单个商品3小时内最多通知一次全系统每天最多通知30条超过就暂停当天通知。这个用SQLite可以在几十行代码内实现def is_global_limit_reached(conn, daily_limit30): today_start time.mktime(time.strptime(time.strftime(%Y-%m-%d), %Y-%m-%d)) row conn.execute( SELECT COUNT(*) FROM notify_log WHERE notified_at ?, (today_start,) ).fetchone() return row[0] daily_limit神价通知是提醒服务不是营销轰炸。宁可漏掉一两个边缘情况也不要让你的手机变成一个持续震动的闹钟。真正核心的神价大概率会持续一小段时间冷却期内不会吃亏。4.3 历史价格数据到底怎么用历史价格是这套系统里最值钱的数据资产。它不仅用于判断“当前价格是不是接近历史最低”还能帮你识破“先涨价再降价”的套路。很多时候你看到一个商品写着限时5折但实际成交价比一个月前的日常价还高就是因为平台先抬高了原价。你要是没有历史价格记录根本看不出来。我习惯每天把采集到的所有价格数据都存进SQLite跑一个月之后做一个简单的统计看看每个商品的价格中位数、最低价、平均活动周期。这个数据还能用来优化你的监控清单常年价格没有波动的商品就直接下线没必要继续占着监控额度。有一个小地方要特别注意电商页面里展示的价格有两种一个是商品原价一个是促销价还有一种叠加优惠券之后的到手价。你的脚本里最好把“到手价”作为主要判断依据把“原价”单独存一个字段防止因为券的有效期导致判断错误。4.4 部署在哪台设备上最合适这个系统要发挥价值关键是“长期稳定在线”。部署位置我按推荐度排个序云服务器最省心NAS次之家里吃灰的旧笔记本也能用软路由和树莓派同样可行。你要是刚开始玩不折腾Docker那些东西直接用cron加Python环境就够了。注意一点家里带宽的IP地址经常变动如果你依赖一些需要固定IP调用的接口可能经常要断线重连。云服务器在这块体验最好成本也不高。另外很多云平台的轻量应用服务器新用户价格很便宜做这件事绰绰有余。系统资源占用非常低这个脚本跑起来内存占用大概几十兆CPU只在每次执行的那几秒有波动完全不影响同机部署的其他服务。4.5 合规和“度”的问题多提醒一句。任何数据采集项目都要在合理合法的前提下运行。电商页面的数据结构、接口参数如果遇到平台明确禁止访问的路径不要硬来。控制频率避免对目标站点造成压力只采集自己需要的数据不批量搬运、不恶意刷接口。这套“优惠雷达”是给个人使用的小工具不是商业爬虫系统保持克制才是长久之道。如果要在公开分享或者商业场景里使用请进一步咨询专业法律意见这里只讲技术实践。5. 进阶玩法与一点个人体会5.1 从单品监控到批量比价跑熟之后你可以把监控清单从几件商品扩展到几十件甚至接入多个平台。思路是一样的每个平台写一个适配器把价格数据统一成标准格式。这样你就可以做一个“全网比价视图”同一件商品在不同平台的到手价一目了然。每次大促之前我会先把所有想买的商品合并成一张比价表用脚本把所有平台的当前价格拉一遍标记出哪些平台已经达到历史低位再决定在哪里下单。多平台适配器最关键的地方是“价格归一化”。有些平台显示的是裸价有些显示的是券后价有些则是“满减后预估到手价”。这几个数字之间可能差几十元如果不做归一化后面所有判断都是错的。5.2 多人订阅和专属优惠雷达如果你想帮家里人也盯一些常购商品可以调整推送逻辑把Server酱单点推送改成企业微信群机器人或钉钉群机器人。每个家庭成员在群里接收同样的通知谁方便谁去下单。群机器人有两个细节值得留意一个是可以设置关键词把“神单雷达”设为触发词消息就能正常送达另一个是可以用所有人的方式提醒但建议只在“历史级大漏”的时候才日常命中保持普通消息即可。用同样的结构你还可以按“商品分组”来分配通知对象。比如数码类的推给你自己日用百货类推给家里负责采购的人。数据的采集存储完全共用只是通知路由不同。5.3 大促期间怎么临时调整策略“618”“双11”这类大促前几天价格波动会非常剧烈。真到了大促当天你的手机可能连续收到几十条通知这个体验并不好因为真正值得秒杀的是极少数。我的经验是大促开始前一周先把所有监控商品的“期望价”下调5%-10%把冷却时间从3小时缩短到30分钟同时开启每日通知上限比如50条封顶。大促结束后再恢复日常参数。还有一个很实用的小技巧把你监控清单里那些“高客单价商品”单独分一组大促期间用更短的监控间隔。高客单价商品的一次神价省下来的钱可能顶得上几十单小商品值得被优先照顾。最后分享一个我自己踩出来的建议不要试图第一次就做一个“全覆盖、全自动、零失误”的完美系统。任何自动化工具都需要观察和微调。你先挑三五个真正想买的商品把整个链路跑通一星期感受一下通知频率合不合适、历史价格数据有没有积累、推送渠道稳不稳定。跑顺之后再慢慢往里面加商品。省钱是个细水长流的事优惠雷达也一样稳定运营比一次性搞大更重要。你缺的从来不是运气而是一个能7x24小时替你盯盘的“队友”。
返回列表