☰
京东商品价格实时监控:Python自动化采集与通知实战
2026/10/27 4:02:38 网站建设 项目流程

简介:这是一份面向电商价格监控场景的实用工具包,适合想了解或动手实现京东商品价格实时跟踪的开发者与电商从业者参考。资源围绕价格抓取、数据存储与变动提醒展开,可用于学习爬虫定时采集、价格趋势记录及邮件通知的完整链路。压缩包共8个文件,约7KB,以4个Python脚本为核心,分别承担页面抓取、邮件推送与数据库连接等职责,另含MySQL建表脚本、项目配置文件和说明文档,结构精简、便于按模块阅读与二次改造。目前已有283人学习下载,可作为入门价格监控系统的最小实现样例,帮助读者快速理解数据采集、入库与提醒之间的协作方式,并在此基础上扩展监控频率、商品范围或通知渠道。

1. 电商价格监控:从“手动刷页面”到“自动盯价格”的落地思路

做电商运营的朋友大概都经历过这种场景:竞品凌晨悄悄改价,第二天早上才发现自家商品被压了一头,流量和转化已经掉了一截。手动刷页面盯价格,人累不说,还容易漏。这份“京东电商商品价格实时监控”资源,核心就是解决这个痛点——把商品价格采集、变化比对、异常提醒串成一条自动化链路,让价格波动从“事后发现”变成“实时感知”。它适合两类人:一是想快速跑通一个价格监控 Demo 的开发者,二是准备在电商数据采集方向做二次开发的技术同学。技术栈上,这类项目常见做法是 Python 做采集与调度,配合 requests、定时任务和轻量存储,前端或通知层按需接入。下面按“能干什么、怎么跑、怎么改”的顺序拆开讲。

2. 项目定位与技术选型:价格监控链路是怎么搭起来的

2.1 这个项目解决的具体问题

价格监控听起来简单,真做起来要回答三个问题:盯哪些商品、多久盯一次、价格变了怎么知道。这份资源围绕京东商品页做文章,把“商品链接 → 价格字段提取 → 历史价格落库 → 变化判定 → 通知触达”这条链路拆成可独立调试的模块。对新手来说,它是一份能照着跑通的采集样例;对熟手来说,它更像一个骨架,采集器、调度器、存储层、通知层各自职责清晰,替换其中任意一层都不至于牵一发动全身。

从适用场景看,它覆盖的是中小规模的监控需求:几十到几百个 SKU,分钟级到小时级的采集频率,价格变化后通过邮件或即时消息推送。这个量级用单机脚本加定时任务完全扛得住,不需要上分布式采集集群。很多开发者一上来就想搞大而全的架构,结果连第一个商品的价格都没抓稳,这是血泪经验——先把单商品链路跑通,再谈扩展。

2.2 技术栈拆解与选型理由

这类项目的技术选型通常围绕“轻量、可调试、易扩展”三个原则。采集层用 Python 的 requests 发 HTTP 请求,配合解析库提取价格字段;调度层用 APScheduler 或系统 cron 做定时触发;存储层用 SQLite 起步,数据量上来再换 MySQL 或 PostgreSQL;通知层对接 SMTP 邮件或 Webhook。下面这张表把各层职责和常见替代方案列清楚,方便你按自己的环境替换。

层级本项目常见实现可替代方案选型说明
采集层requests + 解析库httpx、Playwright静态页用 requests 足够,动态渲染页再上浏览器方案
调度层APScheduler / cronCelery Beat单机任务用 APScheduler 最省事,分布式再考虑 Celery
存储层SQLiteMySQL、PostgreSQL起步阶段 SQLite 零配置,迁移成本低
通知层SMTP 邮件 / Webhook企业微信、钉钉机器人Webhook 接入最快,邮件适合留档
配置层YAML / JSON 文件数据库配置表商品列表用配置文件管理,改起来直观

选型上没有玄学,关键是别过度设计。我一般会先把采集和存储跑通,通知层最后接,因为通知只是“结果出口”,链路不稳的时候接通知只会让你收到一堆误报。

2.3 目录结构长什么样

拿到源码后先别急着跑,花五分钟看目录结构,能省下后面半小时的排查时间。这类项目的典型布局如下:

price-monitor/ ├── config/ │ └── products.yaml # 监控商品清单与阈值配置 ├── src/ │ ├── collector.py # 商品页请求与价格解析 │ ├── parser.py # 价格字段提取与清洗 │ ├── storage.py # 历史价格读写 │ ├── notifier.py # 变化通知发送 │ └── scheduler.py # 定时任务入口 ├── data/ │ └── prices.db # SQLite 数据库文件 ├── logs/ │ └── monitor.log # 运行日志 ├── requirements.txt └── README.md

config/products.yaml是你要改的第一个文件,里面放商品 ID、链接、期望价格区间和采集间隔。src/下按职责拆模块,collector只管发请求拿 HTML,parser只管从 HTML 里抠价格,两者分开的好处是页面结构变了只改 parser,请求策略变了只改 collector。data/和logs/是运行时产物,不用提交到版本库。

2.4 核心数据流:一次采集到底发生了什么

把一次采集拆开看,顺序是:调度器触发 → 读取商品配置 → 逐个请求商品页 → 解析价格 → 与上次价格比对 → 写入新记录 → 触发通知。这个流程里最容易翻车的是“解析价格”和“比对逻辑”两步。解析要处理页面上的促销价、划线价、券后价多种字段,比对要区分“真降价”和“页面抖动导致的误读”。常见做法是在 parser 里做字段优先级排序,在比对时加一个最小变化阈值,比如价格变动超过 1 元才触发通知,避免几分钱的波动把通知刷爆。

3. 跑起来:环境准备、配置与首次采集

3.1 环境准备与依赖安装

先确认 Python 版本,这类项目一般要求 3.8 以上。建虚拟环境是基本操作,别把依赖装到系统环境里,后面换项目会互相打架。

# 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装依赖 pip install -r requirements.txt

requirements.txt里通常包含 requests、beautifulsoup4 或 lxml、APScheduler、PyYAML 这几个包。如果安装过程中某个包编译失败,先看是不是缺系统级依赖,比如 lxml 在某些环境需要先装编译工具链。这一步的报错信息要完整看,别只盯着最后一行。

3.2 商品配置怎么写

打开config/products.yaml,结构一般是这样:

products: - id: "100012043978" url: "https://item.jd.com/100012043978.html" name: "示例商品A" interval_minutes: 30 # 采集间隔,单位分钟 price_threshold: 1.0 # 价格变动超过该值才通知 target_price: 199.00 # 期望价格,低于此值重点提醒

id和url对应同一个商品,interval_minutes控制采集频率,别设得太激进,分钟级以下对单机脚本没意义还容易触发风控。price_threshold是防误报的关键参数,设成 1.0 表示价格变动超过 1 元才发通知。target_price用于区分“普通变动”和“达到心理价位”,通知文案可以据此分级。

3.3 首次运行与结果验证

配置改好后,先别开定时任务,手动跑一次采集,确认链路通:

# 单次采集模式,跑完即退出 python -m src.scheduler --once

跑完看两个地方:logs/monitor.log里有没有解析失败的记录,data/prices.db里有没有新写入的价格行。用 sqlite3 命令行查一下最直观:

sqlite3 data/prices.db "SELECT product_id, price, captured_at FROM price_history ORDER BY captured_at DESC LIMIT 5;"

如果表是空的,先看日志里请求返回的状态码,再看 parser 有没有匹配到价格字段。首次跑通的标准是:日志无 ERROR、数据库有记录、价格数值和页面显示一致。三个条件缺一个都别急着开定时任务。

3.4 开启定时采集与通知

单次采集稳定后,再挂定时任务。APScheduler 的入口一般长这样:

from apscheduler.schedulers.blocking import BlockingScheduler from src.collector import collect_all # 每 30 分钟执行一轮全量采集 scheduler = BlockingScheduler() scheduler.add_job(collect_all, "interval", minutes=30, id="price_job") scheduler.start()

interval是触发方式,也可以换成cron按整点跑。id给任务起个名字,方便日志里定位。通知层在notifier.py里配置 SMTP 或 Webhook 地址,建议先用一个测试商品验证通知能收到,再放开全量监控。通知发不出去的时候,先查网络出口和认证信息,别一上来就怀疑代码。

4. 核心模块拆解:采集、解析、存储三段代码逐段看

4.1 采集器:请求头、超时与重试

采集器负责把商品页拿回来,看似简单,细节全在请求参数上。下面是一段典型实现:

import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def build_session(): session = requests.Session() # 重试策略:最多重试 3 次,退避因子 0.5 秒 retry = Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503]) session.mount("https://", HTTPAdapter(max_retries=retry)) # 请求头模拟常规浏览器,缺少 UA 容易被拦截 session.headers.update({ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept-Language": "zh-CN,zh;q=0.9", }) return session def fetch_page(session, url, timeout=10): # 超时参数必须设,否则网络抖动时线程会一直挂着 resp = session.get(url, timeout=timeout) resp.raise_for_status() return resp.text

Retry的total控制重试次数,backoff_factor控制退避间隔,status_forcelist指定哪些状态码触发重试。timeout是单次请求的硬上限,不设的话遇到慢响应整个采集轮次都会被拖住。请求头里 UA 和语言字段是最基础的伪装,缺了这两项,部分页面会返回简化版或直接拒绝。这段代码的逻辑说明就一句:把“网络不稳定”和“服务端偶发错误”挡在采集层,别让它们污染后面的解析逻辑。

4.2 解析器:价格字段的优先级与清洗

解析器从 HTML 里抠价格,难点在于页面往往同时存在多个价格字段。常见做法是按优先级依次尝试:

from bs4 import BeautifulSoup import re def parse_price(html): soup = BeautifulSoup(html, "lxml") # 优先级 1:促销价 / 到手价 for selector in [".price .p-price", ".summary-price .price", "#jd-price"]: node = soup.select_one(selector) if node: text = node.get_text(strip=True) match = re.search(r"[\d.]+", text) if match: return float(match.group()) # 优先级 2:页面内嵌 JSON 中的价格字段 match = re.search(r'"p":"([\d.]+)"', html) if match: return float(match.group(1)) return None

select_one按 CSS 选择器找节点,多个选择器按顺序尝试,命中即返回。re.search从文本里抽数字,兼容“¥199.00”“199.00元”这类格式。第二段正则是在页面结构变化时的兜底,从内嵌 JSON 里找价格字段。返回None表示解析失败,调用方要据此记日志而不是静默跳过。参数上唯一要调的是选择器列表,页面改版后把新的选择器加进列表最前面即可。

4.3 存储层:历史价格表设计与写入

存储层要回答“价格变化怎么追溯”。表结构不用复杂,一张历史表加一张商品表就够:

CREATE TABLE IF NOT EXISTS price_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id TEXT NOT NULL, price REAL NOT NULL, captured_at TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_product_time ON price_history (product_id, captured_at);

product_id关联配置里的商品,price存解析后的数值,captured_at存采集时间。索引建在(product_id, captured_at)上,因为查询“某商品最近 N 条价格”是最频繁的操作。写入时用参数化语句,别拼字符串:

def save_price(conn, product_id, price, captured_at): conn.execute( "INSERT INTO price_history (product_id, price, captured_at) VALUES (?, ?, ?)", (product_id, price, captured_at), ) conn.commit()

参数化写入防注入也防格式错误,commit的时机按批次来,单条写入就单条提交,批量采集可以攒一批再提交,减少磁盘 IO。

4.4 变化判定与通知触发

比对逻辑决定通知质量。核心是取该商品上一条记录,算差值,超过阈值才通知:

def check_and_notify(conn, product_id, new_price, threshold): row = conn.execute( "SELECT price FROM price_history WHERE product_id = ? ORDER BY captured_at DESC LIMIT 1", (product_id,), ).fetchone() if row is None: return # 首次采集,只落库不通知 old_price = row[0] if abs(new_price - old_price) >= threshold: send_notification(product_id, old_price, new_price)

threshold来自配置里的price_threshold,abs保证涨价降价都能触发。首次采集返回None时直接跳过通知,避免“从无到有”被误判成价格变动。send_notification里做实际推送,邮件或 Webhook 按配置走。这段逻辑的边界条件要测:价格持平、小幅波动、大幅变动、首次采集,四种情况都过一遍再上线。

5. 避坑与排查:价格监控最容易翻车的五个地方

5.1 解析结果为空,日志里全是 None

现象是采集正常返回 200,但parse_price一直返回None。原因通常是页面结构改版,原来的 CSS 选择器失效,或者页面改成了动态渲染,requests 拿到的 HTML 里根本没有价格节点。解决分两步:先把返回的 HTML 存一份到本地,用浏览器开发者工具对照找新的选择器;如果确认是动态渲染,就得换 Playwright 这类能执行 JS 的方案。我一般会在 parser 里加一条“解析失败时落盘 HTML”的调试分支,排查时直接看落盘文件,比反复请求线上页面快得多。

5.2 通知被刷屏,一天收到几十条

现象是价格没怎么变,通知却发个不停。原因多半是阈值设得太小,或者页面上的价格字段在促销价和划线价之间来回跳。解决方法是把price_threshold调大,同时在 parser 里固定字段优先级,只取一个稳定的价格字段,别让不同字段混着比。另外可以在通知层加一个“同商品 N 分钟内只通知一次”的去重窗口,双保险。

5.3 定时任务跑着跑着停了

现象是前几轮采集正常,过一段时间日志不再更新。原因常见于未捕获异常导致调度线程退出,或者数据库连接长时间未释放。解决是在任务函数外层包 try/except,把异常记进日志而不是让它冒泡;数据库连接用上下文管理,用完即关。APScheduler 的BlockingScheduler对异常比较敏感,一个未处理异常就可能让整个调度停摆,这点要特别注意。

5.4 数据库越跑越大,查询变慢

现象是运行几周后,查最近价格要等好几秒。原因是历史表只增不删,索引也没跟上。解决是定期归档,比如只保留最近 90 天的明细,更早的按天聚合;同时确认(product_id, captured_at)索引存在。SQLite 单表到百万行级别才开始明显变慢,中小规模监控一般到不了这个量级,但归档策略早点定下来没坏处。

5.5 换台机器跑,价格全判成“首次采集”

现象是迁移环境后,第一轮采集把所有商品都当成新商品,没触发任何通知。原因是数据库文件没跟着迁移,或者路径配置指向了新的空库。解决是迁移时把data/prices.db一起带走,并在启动日志里打印数据库路径和记录总数,一眼就能看出是不是连错了库。这个坑不常遇到,但遇到一次就够折腾半天。

6. 二次开发:从单机脚本到可维护监控服务的改造路径

跑通之后想往下走,改造方向无非三个:扩商品、提稳定性、加分析。扩商品方面,把products.yaml换成数据库配置表,加一个简单的管理接口,商品增删就不用改文件重启了。提稳定性方面,把采集和解析拆成独立进程,采集只管落原始 HTML,解析异步消费,页面改版时解析层可以单独重跑历史数据,不用重新请求。加分析方面,基于price_history做价格曲线和最低价统计,SQL 就能出结果:

-- 每个商品近 30 天的最低价与出现时间 SELECT product_id, MIN(price) AS min_price, captured_at FROM price_history WHERE captured_at >= datetime('now', '-30 days') GROUP BY product_id;

这条查询配合前端图表,就是一张价格走势看板。改造时有个原则:先加监控再加功能。给采集成功率、解析失败率、通知发送成功率各加一个计数,跑一周看数据,再决定下一步优化哪里。没有监控数据的改造基本靠猜,猜错的成本比多写几行日志高得多。

验证改造效果的方法也简单:拿一个已知会变价的商品做对照,手动改配置里的阈值,观察通知是否符合预期;再模拟一次页面结构变化,看解析失败是否被正确记录而不是静默吞掉。这两项过了,改造才算稳。

从那以后我每次接这类采集项目,都强制先把“单商品单次采集”跑通并落盘验证,再谈定时和通知,顺序反了就是给自己埋雷。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询