ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python构建京东商品价格监控系统:从爬虫到数据分析实战

2026/9/3 8:29:12 拓冰建站 浏览量
Python构建京东商品价格监控系统:从爬虫到数据分析实战 简介这是一套面向电商运营人员、市场分析师及Python初学者的价格监测与分析实践方案聚焦京东平台商品价格动态追踪与竞品对比分析。系统基于requestsBeautifulSoup实现轻量级爬虫支持定时抓取指定商品链接的实时价格、促销信息等字段并存入SQLite本地数据库配套提供数据清洗、趋势统计、多商品价格对比可视化matplotlib/seaborn等完整分析流程。资源包共6个文件含3个核心Python脚本爬取、解析、主控、1份README.md说明文档、1个说明文本及1份附赠Word版操作指南总计36KB结构紧凑、即拿即用。已有76人学习下载读者可直接复用代码框架进行商品监控部署快速掌握电商数据采集—存储—分析闭环技能并获得可扩展的竞品价格波动研判思路。1. 项目概述与核心价值最近在帮一个做电商运营的朋友处理一个需求他们想实时监控几个核心竞品在京东上的价格变动同时分析自家产品的定价策略是否合理。手动去查不仅效率低而且很难捕捉到那些短期的促销和秒杀价格。于是我花了点时间用Python搭建了一套自动化的京东商品价格数据爬取与分析系统。这套系统说白了就是一个能定时干活、自动存数据、还能生成直观图表的“数字助理”。它的核心工作流程很清晰系统会按照你设定的时间比如每小时一次自动去访问指定的京东商品页面把当前的价格、促销信息、商品标题等关键数据“抓”下来然后规规矩矩地存到本地的数据库里。数据积累一段时间后你就可以通过它内置的数据可视化功能一眼看出某个商品的价格是“过山车”还是“一条直线”也能轻松对比不同竞品之间的价格差异。对于做电商运营、市场研究或者单纯想“抄底”买某个东西的朋友来说这玩意儿能省下大量机械劳动的时间把精力聚焦在分析决策上。整个系统的技术栈非常“亲民”核心就是Python。用requests或selenium来模拟浏览器请求抓取网页用BeautifulSoup或lxml解析HTML提取数据用sqlite3或pymysql把数据存起来最后用matplotlib或pyecharts画图分析。即使你不是专业程序员跟着步骤一步步来也能把它跑起来。下面我就把这套系统的设计思路、关键代码、踩过的坑以及怎么用得顺手毫无保留地分享出来。2. 系统整体设计与技术选型考量2.1 为什么选择Python与这套技术组合选择Python作为主力语言几乎是必然的。在数据抓取和处理这个领域Python的生态就像个百宝箱要什么有什么。requests库简单易用几行代码就能完成HTTP请求解析库BeautifulSoup和lxml对新手和老手都友好数据处理有pandas坐镇画图有matplotlib,seaborn,plotly等一堆选择。这种从抓取到分析无缝衔接的能力是其他语言很难比拟的。对于京东这种大型电商网站我的技术选型主要基于以下几点考量爬虫框架初期我直接用了requestsBeautifulSoup的组合。它的好处是轻量、直接学习成本低。但京东页面动态内容较多特别是价格信息有时通过简单的GET请求无法获取到数据可能通过JavaScript异步加载。这时就需要用到selenium这类自动化测试工具来模拟真实浏览器行为确保能抓到渲染后的完整数据。我的策略是先用requests试如果抓不到关键数据再降级使用selenium。数据存储考虑到这个系统是个人或小团队使用对高并发要求不高但需要轻便、易部署。SQLite成了首选。它是一个文件数据库无需安装任何服务器Python原生支持非常适合本地存储和快速原型开发。如果未来数据量剧增或需要多人协作可以无缝迁移到MySQL或PostgreSQL。任务调度要实现定时抓取有两种主流思路。一是用操作系统的定时任务如Linux的cron或Windows的“任务计划程序”直接定时执行Python脚本。这种方式简单粗暴依赖系统环境。另一种是在Python脚本内部实现定时循环比如使用schedule或APScheduler库。我选择了后者因为这样能让整个系统更加内聚所有逻辑都封装在一个或几个Python文件中部署和移植更方便。可视化分析matplotlib是基础功能强大但默认样式较丑。seaborn基于matplotlib统计图表更美观。如果想做交互式图表pyecharts或plotly是很好的选择。我最终采用了matplotlibseaborn的组合因为我们的分析主要是看趋势和对比静态图表足够清晰且对运行环境依赖最小。注意在技术选型上永远要考虑“性价比”。不要一开始就追求大而全的架构。用最简单的工具解决核心问题等需求明确、遇到瓶颈时再升级这是最稳妥高效的开发路径。2.2 系统架构与核心模块拆解我把整个系统抽象为四个核心模块它们像流水线一样协同工作爬虫调度模块这是系统的大脑。它负责管理一个任务列表列表里记录着所有需要监控的商品链接。然后它根据预设的时间规则例如每天上午10点和下午4点触发爬虫模块去工作。我使用APScheduler库来实现这个功能因为它支持复杂的定时规则如每周特定几天而且稳定可靠。数据抓取与解析模块这是系统的“手”和“眼睛”。它接收一个商品链接负责与京东服务器通信下载网页内容并从复杂的HTML代码中精准地“抠”出我们需要的价格、商品名等信息。这是技术挑战最大的一部分因为京东的反爬机制会不断更新。数据存储模块这是系统的“笔记本”。它把抓取到的结构化数据持久化地保存到SQLite数据库中。设计一个好的数据库表结构至关重要它决定了后续数据分析的便利性。数据分析与可视化模块这是系统的“嘴巴”。它从数据库中查询历史数据通过计算和绘图把冷冰冰的数字变成直观的折线图、柱状图告诉我们价格是怎么波动的谁比谁贵了多少。这个架构清晰且解耦。每个模块各司其职通过清晰的接口函数调用、数据库表连接。这意味着如果你想更换某个部分比如把SQLite换成MySQL或者把matplotlib换成pyecharts只需要修改对应的模块而不会牵一发而动全身。3. 核心细节解析与实操要点3.1 京东页面分析与反爬策略应对京东的商品页面结构比较复杂而且反爬措施做得不错。直接requests.get()拿到的HTML很可能找不到价格信息。价格往往藏在script标签的JSON数据里或者通过接口异步加载。第一步定位价格数据源打开一个京东商品页例如某个手机按F12打开开发者工具。在Elements标签页直接搜索“jdPrice”或“price”等关键词可能一无所获。切换到Network标签页刷新页面在所有的网络请求中筛选XHR或JS类型的请求。仔细观察请求的URL和响应内容。你很有可能会发现一个包含skuId商品ID的接口其返回的数据是清晰的JSON格式里面就包含了price价格、name名称等信息。这个接口才是我们真正的目标。例如你可能找到类似这样的请求https://item.jd.com/100123456789.html的真实数据来自https://p.3.cn/prices/mgets?skuIdsJ_100123456789。第二步模拟请求头即使找到了接口直接访问也可能被拒绝。服务器会检查请求的Headers特别是User-Agent。我们需要让我们的Python脚本看起来像一个真实的浏览器。import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Referer: https://item.jd.com/, # 增加Referer更模拟真实浏览行为 } response requests.get(api_url, headersheaders)第三步处理动态加载与降级方案如果上述接口方式失效或者你需要抓取的信息必须页面完全渲染才能获得比如“秒杀价”标签就需要动用selenium。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 使用无头模式不弹出浏览器窗口 options webdriver.ChromeOptions() options.add_argument(--headless) driver webdriver.Chrome(optionsoptions) try: driver.get(product_url) # 等待价格元素加载出来 price_element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, span.price)) ) price price_element.text finally: driver.quit()实操心得优先尝试寻找并调用数据接口这比渲染整个页面效率高成百上千倍对目标网站也更友好。将selenium作为保底方案。在实际代码中我会先尝试用requests抓接口如果失败或数据不完整再自动切换到selenium模式并记录日志方便后续排查。3.2 数据库表结构设计数据库设计的目标是清晰记录每一次抓取的数据并方便后续按商品、按时间进行查询分析。我设计了一张核心表product_price字段名数据类型说明idINTEGER PRIMARY KEY自增主键sku_idVARCHAR(20) NOT NULL商品唯一ID如100123456789product_nameTEXT商品名称可选可从首次抓取或另表获取priceDECIMAL(10, 2) NOT NULL价格单位元promotion_infoTEXT促销信息如“满减”、“秒杀”capture_timeDATETIME NOT NULL抓取时间精确到秒source_urlTEXT商品页面URL设计理由sku_id是商品的唯一标识比URL更稳定URL可能变化。所有分析都将围绕sku_id进行。capture_time记录抓取时刻这是分析价格趋势的X轴。price字段使用DECIMAL类型精确存储金额避免浮点数精度问题。promotion_info字段存储文本可以记录当时的活动状态便于分析促销对价格的影响。创建表的SQL语句如下CREATE TABLE IF NOT EXISTS product_price ( id INTEGER PRIMARY KEY AUTOINCREMENT, sku_id VARCHAR(20) NOT NULL, product_name TEXT, price DECIMAL(10, 2) NOT NULL, promotion_info TEXT, capture_time DATETIME NOT NULL, source_url TEXT ); -- 创建索引以加速按商品和时间的查询 CREATE INDEX idx_sku_time ON product_price (sku_id, capture_time);4. 实操过程与核心环节实现4.1 爬虫调度器的实现我选择APScheduler作为调度核心因为它功能强大且易于集成到Python应用中。from apscheduler.schedulers.blocking import BlockingScheduler import datetime from your_spider_module import main_spider_function # 导入你的抓取主函数 def job(): 定时执行的任务 print(f{datetime.datetime.now()} 开始执行爬虫任务...) try: main_spider_function() print(f{datetime.datetime.now()} 爬虫任务执行完毕。) except Exception as e: print(f{datetime.datetime.now()} 爬虫任务执行失败: {e}) if __name__ __main__: # 创建调度器 scheduler BlockingScheduler() # 添加作业每天上午10点和下午4点各执行一次 scheduler.add_job(job, cron, hour10,16, minute0) # 也可以使用间隔时间例如每2小时执行一次 # scheduler.add_job(job, interval, hours2) print(爬虫调度器已启动按 CtrlC 退出。) try: scheduler.start() except (KeyboardInterrupt, SystemExit): print(调度器已停止。)BlockingScheduler会阻塞主线程适合单独运行这个调度脚本。如果你的系统需要同时处理其他事情可以考虑使用BackgroundScheduler。4.2 数据抓取与存储的完整代码示例下面是一个整合了requests抓取接口、异常处理、数据解析和SQLite存储的完整函数示例。import requests import json import sqlite3 from datetime import datetime import time import re def fetch_jd_product_price(sku_id, product_url): 抓取京东商品价格并存入数据库 :param sku_id: 商品SKU ID :param product_url: 商品页面URL :return: 抓取到的价格信息字典失败返回None # 1. 尝试从价格接口获取数据 price_api_url fhttps://p.3.cn/prices/mgets?skuIdsJ_{sku_id} headers { User-Agent: Mozilla/5.0 ..., Referer: product_url, } price_info None try: resp requests.get(price_api_url, headersheaders, timeout10) resp.raise_for_status() # 检查HTTP错误 data resp.json() if data and len(data) 0: price_info { sku_id: sku_id, price: float(data[0].get(p, 0)), # p 字段通常代表价格 product_name: None, # 接口可能不返回名称需要另外解析 promotion_info: data[0].get(op, ), # op可能为促销信息 } print(f接口获取成功: SKU {sku_id} 价格 {price_info[price]}) except (requests.RequestException, json.JSONDecodeError, KeyError) as e: print(f价格接口请求失败 {sku_id}: {e}) # 此处可添加降级到selenium的逻辑 # price_info fetch_with_selenium(product_url) # 2. 如果接口成功尝试补充商品名称从页面获取 if price_info: try: # 简单从URL的HTML中提取标题实际应用可能需要更复杂的解析 page_resp requests.get(product_url, headersheaders, timeout10) # 使用正则简单匹配生产环境建议用BeautifulSoup title_match re.search(rtitle(.*?)/title, page_resp.text) if title_match: # 清理标题移除“【行情 报价 价格 评测】-京东”等后缀 full_title title_match.group(1) product_name full_title.split(【)[0].strip() price_info[product_name] product_name except Exception as e: print(f获取商品名称失败 {sku_id}: {e}) # 3. 数据入库 if price_info: save_to_db(price_info, product_url) else: print(fSKU {sku_id} 价格信息抓取失败本次跳过。) return price_info def save_to_db(price_info, source_url): 将价格信息保存到SQLite数据库 conn sqlite3.connect(jd_price_data.db) cursor conn.cursor() sql INSERT INTO product_price (sku_id, product_name, price, promotion_info, capture_time, source_url) VALUES (?, ?, ?, ?, ?, ?) capture_time datetime.now().strftime(%Y-%m-%d %H:%M:%S) data_tuple ( price_info[sku_id], price_info[product_name], price_info[price], price_info.get(promotion_info, ), capture_time, source_url ) try: cursor.execute(sql, data_tuple) conn.commit() print(f数据入库成功: SKU {price_info[sku_id]} 于 {capture_time}) except sqlite3.Error as e: print(f数据入库失败: {e}) finally: conn.close() # 主抓取函数遍历监控列表 def main_spider_function(): # 你的监控商品列表格式[(sku_id, url), ...] product_list [ (100012345678, https://item.jd.com/100012345678.html), (100023456789, https://item.jd.com/100023456789.html), # ... 更多商品 ] for sku_id, url in product_list: fetch_jd_product_price(sku_id, url) time.sleep(2) # 礼貌性延迟避免请求过快 if __name__ __main__: # 单独测试抓取 main_spider_function()这段代码是一个可运行的最小核心。fetch_jd_product_price函数封装了从接口获取、解析到存储的完整流程并包含了基本的异常处理。main_spider_function则负责遍历所有需要监控的商品。4.3 数据可视化分析实战数据存进去之后才是价值开始体现的时候。我们使用pandas做数据处理matplotlib和seaborn画图。首先从数据库读取数据import sqlite3 import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from datetime import datetime, timedelta # 设置中文字体如果图表需要显示中文 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus] False # 用来正常显示负号 def load_data_from_db(sku_idNone, days30): 从数据库加载数据 :param sku_id: 指定商品SKU为None则加载所有 :param days: 加载最近多少天的数据 :return: pandas DataFrame conn sqlite3.connect(jd_price_data.db) start_date (datetime.now() - timedelta(daysdays)).strftime(%Y-%m-%d %H:%M:%S) if sku_id: query SELECT sku_id, product_name, price, promotion_info, capture_time FROM product_price WHERE sku_id ? AND capture_time ? ORDER BY capture_time df pd.read_sql_query(query, conn, params(sku_id, start_date)) else: query SELECT sku_id, product_name, price, promotion_info, capture_time FROM product_price WHERE capture_time ? ORDER BY sku_id, capture_time df pd.read_sql_query(query, conn, params(start_date,)) conn.close() # 转换数据类型 df[capture_time] pd.to_datetime(df[capture_time]) df[price] pd.to_numeric(df[price]) # 如果product_name有空值可以用sku_id填充 df[product_name].fillna(df[sku_id], inplaceTrue) return df接下来绘制单个商品的价格趋势图def plot_single_product_trend(sku_id, days30): 绘制单个商品的价格趋势图 df load_data_from_db(sku_idsku_id, daysdays) if df.empty: print(fSKU {sku_id} 在最近{days}天内无数据。) return product_name df[product_name].iloc[0] plt.figure(figsize(14, 7)) plt.plot(df[capture_time], df[price], markero, linestyle-, linewidth2, markersize4) # 标注促销信息点 promo_df df[df[promotion_info].notna() (df[promotion_info] ! )] if not promo_df.empty: plt.scatter(promo_df[capture_time], promo_df[price], colorred, s50, zorder5, label促销) # 为每个促销点添加简单注释 for _, row in promo_df.iterrows(): plt.annotate(促, xy(row[capture_time], row[price]), xytext(0, 10), textcoordsoffset points, hacenter, colorred, fontweightbold) plt.title(f{product_name} 近{days}天价格趋势, fontsize16, fontweightbold) plt.xlabel(抓取时间, fontsize12) plt.ylabel(价格 (元), fontsize12) plt.grid(True, linestyle--, alpha0.7) plt.xticks(rotation45) plt.legend() plt.tight_layout() # 保存图片 filename fprice_trend_{sku_id}_{datetime.now().strftime(%Y%m%d)}.png plt.savefig(filename, dpi300) print(f图表已保存为: {filename}) plt.show()最后绘制多商品价格对比图以最近一次抓取价格为例def plot_multi_product_comparison(sku_list): 绘制多个商品最近一次价格的对比柱状图 latest_prices [] product_names [] conn sqlite3.connect(jd_price_data.db) cursor conn.cursor() for sku_id in sku_list: query SELECT product_name, price FROM product_price WHERE sku_id ? ORDER BY capture_time DESC LIMIT 1 cursor.execute(query, (sku_id,)) result cursor.fetchone() if result: product_names.append(result[0] if result[0] else sku_id) latest_prices.append(result[1]) else: product_names.append(sku_id) latest_prices.append(0) # 无数据则记为0 conn.close() if not latest_prices: print(没有找到任何商品数据。) return plt.figure(figsize(12, 6)) bars plt.bar(product_names, latest_prices, colorsns.color_palette(husl, len(sku_list))) # 在柱子上方显示价格数值 for bar, price in zip(bars, latest_prices): height bar.get_height() plt.text(bar.get_x() bar.get_width()/2., height max(latest_prices)*0.01, f¥{price:.2f}, hacenter, vabottom, fontsize10) plt.title(竞品价格对比最新, fontsize16, fontweightbold) plt.ylabel(价格 (元), fontsize12) plt.xticks(rotation15, haright) plt.grid(True, axisy, linestyle--, alpha0.7) plt.tight_layout() filename fprice_comparison_{datetime.now().strftime(%Y%m%d)}.png plt.savefig(filename, dpi300) print(f对比图表已保存为: {filename}) plt.show() # 使用示例 if __name__ __main__: # 绘制指定SKU的趋势图 plot_single_product_trend(100012345678, days7) # 对比多个SKU skus_to_compare [100012345678, 100023456789, 100034567890] plot_multi_product_comparison(skus_to_compare)通过这些图表你可以非常直观地看到某个商品是不是在周末降价竞品A和B的价格差是否稳定自己的调价策略是否立刻引起了竞争对手的反应这些洞察就是数据带来的直接价值。5. 常见问题与排查技巧实录在实际搭建和运行过程中你肯定会遇到各种各样的问题。下面是我踩过的一些坑和解决方法希望能帮你快速排雷。5.1 爬虫抓不到数据或数据为空这是最常见的问题九成以上原因出在请求本身上。检查请求头特别是User-Agent。有些网站会对没有User-Agent或使用Python默认User-Agent的请求直接拒绝。确保你的User-Agent是一个常见的浏览器字符串。可以尝试在代码里准备几个不同的User-Agent轮流使用。验证接口地址京东的数据接口可能会变化。定期用浏览器开发者工具F12 - Network重新检查一下你之前找到的那个价格接口是否还在URL格式有没有变。这是最需要维护的地方。处理IP限制如果你短时间内请求过于频繁可能会触发IP限流。症状是返回一些错误页面或验证码。解决方案在请求之间增加随机延迟time.sleep(random.uniform(1, 3))。如果监控商品很多可以考虑使用代理IP池但这对于个人或小规模应用通常不是必须的。应对动态渲染如果requests始终拿不到价格而浏览器能看到那基本确定是动态加载。这时就必须启用selenium方案。记得在selenium代码中加入“显式等待”WebDriverWait确保元素加载完成再抓取而不是用固定的sleep。5.2 数据库操作错误连接被锁定如果你在多个地方或频繁地连接/关闭数据库可能会遇到database is locked错误。这在SQLite中较常见。解决方案确保你的数据库连接在使用后正确关闭conn.close()。更好的做法是使用with语句或确保异常发生时也能关闭连接。对于多线程/多进程场景需要考虑更高级的连接管理或换用MySQL。重复数据如果你的调度器配置不当可能在极短时间内触发两次抓取导致两条几乎相同时间的数据。解决方案这通常不是大问题分析时按时间聚合取平均或最新即可。也可以在插入前做一个简单判断如果同一商品在非常短的时间间隔内如1分钟内已有记录则跳过插入。5.3 可视化图表显示异常中文乱码这是matplotlib的经典问题。务必在绘图前设置中文字体如前面代码所示。你需要确保你指定的字体如SimHei在你的操作系统上存在。时间轴过于密集如果抓取频率很高比如每小时一次几天的数据点就会很多导致X轴标签重叠。解决方案使用Pandas的resample方法对数据进行重采样比如将每小时数据聚合成每天的平均价格再绘图。或者调整X轴刻度的显示密度。import matplotlib.dates as mdates # 在plot_single_product_trend函数中设置X轴格式 ax plt.gca() ax.xaxis.set_major_formatter(mdates.DateFormatter(%m-%d %H:%M)) # 格式化为月-日 时:分 ax.xaxis.set_major_locator(mdates.AutoDateLocator(maxticks10)) # 自动选择最多10个刻度图表布局错乱当标签较长或图表元素较多时可能会显示不全。解决方案养成在plt.show()或plt.savefig()前调用plt.tight_layout()的习惯它能自动调整子图参数使之填充整个图像区域。5.4 系统长期运行的稳定性日志记录这是最重要的不要只用print。使用Python内置的logging模块将程序运行状态、抓取成功/失败、错误信息记录到文件中。这样当系统在后台运行时你才能知道它是否健康出了什么问题。异常处理像网络超时、解析失败、数据库断开这类错误一定要用try...except捕获并进行恰当处理如记录日志、跳过当前商品、等待后重试避免一个商品出错导致整个程序崩溃。资源清理如果使用了selenium务必在finally块中调用driver.quit()来关闭浏览器驱动否则会残留大量Chrome进程耗尽内存。这套系统从搭建到稳定运行是一个不断迭代和调整的过程。一开始可能只监控几个商品每小时抓一次。跑顺之后再逐步增加商品数量优化抓取策略比如对促销频繁的商品提高抓取频率丰富分析维度比如加入价格变化提醒功能。最重要的是它真正解决了问题把我们从重复的体力劳动中解放了出来让决策有了数据支撑。如果你也遇到了类似的需求不妨就从今天分享的这个基础版本开始动手试试遇到具体问题欢迎随时交流。本文还有配套的精品资源点击获取