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

发布时间:2026/9/3 7:06:35
Python爬虫实战:构建京东商品价格监控与数据分析系统 简介这是一套面向电商运营人员、市场研究人员及Python初学者的价格监测与分析实践方案聚焦京东平台商品价格动态追踪与竞品对比分析。系统基于RequestsBeautifulSoup实现轻量级爬虫支持定时抓取指定商品链接的实时价格并存入SQLite本地数据库配套提供数据清洗、趋势统计与Matplotlib可视化脚本可生成价格波动曲线、竞品价差热力图等分析图表助力定价策略制定与市场敏感度评估。资源共6个文件含3个核心Python脚本爬取、查看、主控、1份README说明文档、1个使用说明txt及1份附赠资源docx总大小仅36KB结构紧凑、开箱即用。目前已有76人学习下载代码注释清晰涵盖反爬应对基础逻辑、数据库建表与查询封装、可视化参数配置等实用细节适合快速部署验证与二次开发。1. 项目概述与核心价值最近在跟几个做电商运营的朋友聊天他们最头疼的就是价格监控。今天一个价明天一个价竞品那边又偷偷调了价自己手动去查不仅效率低还容易错过关键的价格变动节点。这种时候一个能自动抓取、分析价格数据的工具就显得尤为重要。我花了一段时间基于Python爬虫技术搭建了一套京东商品价格数据爬取与分析系统。这套系统的核心目标很明确自动、定时地从京东抓取我们关心的商品价格存起来然后通过可视化和统计分析把价格波动趋势、竞品对比这些信息直观地呈现出来。无论是用来做市场调研、定价策略参考还是监控渠道价格是否混乱都能派上大用场。简单来说这个系统扮演了一个“自动化市场侦察兵”的角色。它解决了人工盯盘的几个核心痛点首先是时效性系统可以设定在每天促销开始、结束等关键时间点抓取确保数据及时其次是全面性可以同时监控数十甚至上百个SKU和多个竞品这是人力难以做到的最后是客观性所有数据自动记录避免了人工记录可能产生的疏漏和主观偏差。对于电商运营、产品经理、市场分析人员来说这类工具能直接从数据层面提供决策支持比如判断何时补货、何时调整促销力度、竞品的定价策略是什么等等。接下来我就把这套系统的设计思路、技术实现细节以及实操中踩过的坑完整地分享出来。2. 系统整体架构与设计思路2.1 核心需求与模块拆解接到“监控京东商品价格”这个需求我们不能一上来就写爬虫代码。首先得把需求拆解成几个可执行、可维护的模块。经过分析整个系统可以划分为四个核心模块数据采集模块这是系统的“手”和“眼睛”。负责根据预设的商品链接列表模拟浏览器访问京东页面从中精准地提取出商品价格、名称、促销信息等关键数据。这个模块必须稳定、抗干扰因为京东的反爬机制一直在升级。数据存储模块这是系统的“记忆库”。抓取到的数据不能只放在内存里需要持久化存储。我们选择本地数据库如SQLite或MySQL来存放每一次抓取的价格快照。这样设计的好处是可以追溯任意商品在任意历史时间点的价格为后续的趋势分析打下基础。任务调度模块这是系统的“心脏”和“时钟”。价格监控是持续性的工作我们需要系统能定时自动运行抓取任务比如每天上午10点、下午4点各执行一次。这个模块负责管理和触发这些定时任务确保数据采集的规律性。数据分析与可视化模块这是系统的“大脑”和“输出界面”。原始的价格数据只是一串数字和日期我们需要通过图表和统计方法将其转化为有洞见的信息。比如绘制某个商品过去30天的价格曲线计算价格波动的标准差或者将几个竞品价格放在同一张折线图上进行对比。这四个模块环环相扣构成了一个完整的数据流水线。从定时触发抓取到解析存储数据再到最终的分析呈现形成了一个自动化闭环。在设计时我特别注重模块间的低耦合性比如数据存储模块定义好统一的接口那么无论是采集模块还是分析模块都通过这个接口与数据库交互这样未来如果想更换数据库类型或者增加新的分析维度改动会非常小系统也更容易维护和扩展。2.2 技术栈选型与考量技术选型直接决定了开发效率和系统稳定性。下面是我为每个模块选择的核心技术及其背后的考量数据采集模块requestsBeautifulSoup4/lxml这是经典的Python爬虫组合。requests库负责发送HTTP请求获取网页的HTML源码它简单易用功能强大。BeautifulSoup4或lxml则负责解析HTML像一把“手术刀”从复杂的网页结构中精准地“切”出我们需要的价格数据。对于京东这种结构相对规整的页面这个组合在大多数情况下够用且高效。Selenium/Playwright这是应对复杂反爬的“重型武器”。当目标页面价格数据是通过JavaScript动态加载即打开网页源代码看不到价格需要浏览器执行JS后才能渲染出来时requests就无能为力了。Selenium或Playwright可以模拟一个真实的浏览器环境等待JS执行完毕后再获取完整的页面内容。它们的缺点是速度较慢资源占用高。我的策略是优先尝试requests如果失败再评估是否启用Selenium。考量选择BeautifulSoup4还是lxmllxml的解析速度更快但BeautifulSoup4的API更友好容错性更好。对于新手或页面结构不算极其复杂的情况BeautifulSoup4是更稳妥的选择。是否需要Selenium这需要通过初步测试确定。打开京东商品页右键“查看网页源代码”搜索商品当前价格比如“299.00”如果能找到说明是静态页面用requests即可如果找不到很可能就是动态加载需要考虑Selenium。数据存储模块SQLite如果数据量不大比如监控几十个商品每天抓取几次持续数月项目是单机运行那么SQLite是完美选择。它是一个文件数据库无需安装和配置单独的数据库服务Python标准库直接支持简单轻量。对于这个项目的多数个人或小团队应用场景SQLite是首选。MySQL / PostgreSQL如果预估数据量会非常大监控上千商品高频抓取或者需要多个客户端/程序同时访问数据库那么应该选择MySQL或PostgreSQL这类独立的数据库服务器。它们性能更强功能更完善支持并发访问。考量我建议从SQLite开始。它的.db文件便于备份和迁移。当未来数据量增长到SQLite感到压力时再迁移到MySQL也不迟。使用Python的sqlite3库或SQLAlchemy这样的ORM对象关系映射工具可以让我们用几乎相同的方式操作不同的数据库降低了未来切换的成本。任务调度模块schedule库这是一个轻量级、人性化的Python定时任务库。它的语法非常直观比如schedule.every().day.at(“10:30”).do(job)一看就懂。适合在单脚本内实现简单的定时任务。操作系统定时任务Cron / Task Scheduler更通用和稳定的方案。我们可以将爬虫脚本写成一个独立的可执行文件.py然后在Linux系统上用Cron或者在Windows系统上用任务计划程序来定时触发这个脚本。这种方式将调度逻辑与业务逻辑分离更加健壮即使Python脚本意外崩溃也不影响操作系统的定时任务机制。考量对于需要7x24小时稳定运行的生产环境强烈推荐使用操作系统的定时任务。schedule库更适合用于快速原型验证或者在需要非常复杂的时间规则如每个工作日时临时使用。本系统我将以Cron为例进行讲解。数据分析与可视化模块pandas数据分析的“瑞士军刀”。它提供的DataFrame数据结构非常适合处理我们这种带时间戳的表格型数据。我们可以方便地用pandas从数据库读取数据进行数据清洗处理缺失值、异常值、转换计算日均价、波动率和聚合按商品、按周分组统计。matplotlibseabornPython最经典的可视化组合。matplotlib功能强大可以绘制几乎所有类型的图表但默认样式比较基础。seaborn基于matplotlib提供了更美观的统计图形样式和更高级的接口如一键绘制带分布的趋势图。两者结合既能实现高度定制又能快速产出美观的图表。考量pandas是必选项它甚至内置了一些简单的绘图功能。对于可视化如果你追求出版级的图表控制和每一个细节深耕matplotlib如果你希望快速生成统计上 informative 且美观的图表seaborn是更高效的选择。本项目中我们会使用seaborn来快速绘制核心的趋势图。注意技术选型没有绝对的对错只有是否适合当前场景。我的选择是基于“快速实现、稳定运行、易于维护”的原则。如果你的环境或需求有特殊性完全可以替换其中的组件。3. 核心实现细节与实操要点3.1 京东页面分析与数据抓取策略抓取京东价格第一步不是写代码而是“侦察”。你需要弄清楚价格数据藏在网页的哪个角落。打开一个京东商品页例如某个手机页面按F12打开开发者工具。定位价格元素在页面上找到价格数字右键点击选择“检查”Inspect。开发者工具会自动定位到对应的HTML元素。你会发现价格很可能在一个带有特定class的span标签里比如span class”price J-p-100012345678″2999.00/span。这个class名如J-p-100012345678可能包含商品ID是动态的不通用。寻找稳定选择器我们不能依赖包含动态ID的class。需要向上查看其父元素寻找更稳定、更具语义化的class或id。京东的价格通常包裹在类似div class”summary-price J-summary-price”或div class”p-price”的容器里。通过多次观察不同商品页面找到共通的、稳定的CSS选择器路径。例如你可能发现通过.summary-price .price或.p-price .price这个路径可以相对稳定地定位到价格元素。处理动态加载与反爬检查请求在开发者工具的“网络”Network选项卡中刷新页面观察加载的资源。有时价格是通过一个单独的XHR/Fetch请求获取的JSON数据。如果是这样直接模拟这个请求获取JSON比解析HTML更简单、更稳定。查找包含“price”、“sku”等关键词的请求。应对反爬京东会对频繁、规律的请求进行拦截。我们需要在代码中模拟得更像真人。设置请求头Headers这是最基本的。务必在requests.get()中传入一个完整的headers字典至少包含User-Agent模拟浏览器、Referer来源页等。可以从浏览器开发者工具中复制一次真实访问的Headers。使用代理IP池如果单IP抓取频率过高很容易被暂时封禁。对于大规模持续抓取需要考虑使用代理IP池来轮换IP地址。添加随机延迟在连续的请求之间使用time.sleep()添加一个随机时间间隔如3-10秒避免请求过于密集。处理Cookie某些页面状态可能需要Cookie。我们可以使用requests.Session()对象来保持会话自动处理Cookie。3.2 数据库表结构设计与数据持久化数据库设计要保证既能高效存储又能方便后续查询分析。我们至少需要两张核心表商品信息表 (products)存储被监控商品的基本信息避免在价格表中重复存储。product_id(主键): 商品ID可从商品URL中提取如100012345678。product_name: 商品名称。product_url: 商品链接。monitor_since: 开始监控的日期。category: 商品分类可选便于分组分析。价格历史表 (price_history)存储每一次抓取的价格快照这是核心数据表。id(主键): 自增ID。product_id(外键): 关联到products表。price: 抓取到的价格浮点数。timestamp: 抓取的时间戳精确到秒。promotion_info: 促销信息文本如“满减”、“秒杀”可为空。is_lowest_30d: 标记是否为近30天最低价可通过后续分析计算更新。使用Python的sqlite3库创建和操作数据库的示例代码如下import sqlite3 import datetime def init_database(db_pathprice_data.db): conn sqlite3.connect(db_path) cursor conn.cursor() # 创建商品表 cursor.execute( CREATE TABLE IF NOT EXISTS products ( product_id TEXT PRIMARY KEY, product_name TEXT NOT NULL, product_url TEXT NOT NULL, monitor_since DATE DEFAULT CURRENT_DATE, category TEXT ) ) # 创建价格历史表 cursor.execute( CREATE TABLE IF NOT EXISTS price_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id TEXT NOT NULL, price REAL NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, promotion_info TEXT, is_lowest_30d BOOLEAN DEFAULT 0, FOREIGN KEY (product_id) REFERENCES products (product_id) ) ) # 为价格历史表创建索引加速按商品和时间的查询 cursor.execute(CREATE INDEX IF NOT EXISTS idx_product_time ON price_history (product_id, timestamp)) conn.commit() conn.close() print(f数据库初始化完成: {db_path}) def save_price_to_db(db_path, product_id, price, promotion_info): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( INSERT INTO price_history (product_id, price, promotion_info) VALUES (?, ?, ?) , (product_id, price, promotion_info)) conn.commit() conn.close()实操心得在price_history表的timestamp字段上建立索引至关重要。随着数据量增长比如一年下来可能有几十万条记录按时间范围查询例如“查询商品A最近一个月的数据”会变得非常慢。建立了(product_id, timestamp)的联合索引后这类查询速度会有数量级的提升。这是初期设计时就要考虑到的性能优化点。3.3 爬虫核心代码实现与异常处理结合前面的分析一个健壮的爬虫函数需要包含请求发送、页面解析、异常处理和数据存储。下面是一个使用requests和BeautifulSoup的示例import requests from bs4 import BeautifulSoup import time import random import sqlite3 from urllib.parse import urlparse def fetch_jd_price(product_url, db_pathprice_data.db): 抓取指定京东商品链接的价格并存入数据库 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://www.jd.com/, Accept-Language: zh-CN,zh;q0.9, } try: # 1. 发送请求 response requests.get(product_url, headersheaders, timeout10) response.raise_for_status() # 如果状态码不是200抛出HTTPError异常 response.encoding utf-8 # 2. 解析页面 soup BeautifulSoup(response.text, html.parser) # 策略1: 尝试通过常见的价格容器定位 price_selector .summary-price .price, .p-price .price, .J-p-price price_element soup.select_one(price_selector) price None product_name None if price_element: # 提取价格文本并清理非数字字符 price_text price_element.get_text(stripTrue) # 使用正则表达式或简单替换提取数字部分 import re price_match re.search(r[\d,]\.?\d*, price_text) if price_match: price float(price_match.group().replace(,, )) else: # 策略2: 如果常规选择器失效尝试在页面中搜索价格模式 # 这可能是一种降级策略准确性较低 print(f警告: 无法通过选择器 {price_selector} 定位价格尝试搜索模式...) # 此处可添加更复杂的备选解析逻辑或标记为需要人工检查 # 提取商品名称 name_element soup.select_one(.sku-name, .product-name) if name_element: product_name name_element.get_text(stripTrue) # 3. 提取商品ID (从URL中) parsed_url urlparse(product_url) # 京东商品URL模式通常如 https://item.jd.com/100012345678.html path_parts parsed_url.path.split(/) for part in path_parts: if part.endswith(.html): product_id part.replace(.html, ) break else: # 如果无法从URL提取可以尝试从页面元素中获取 product_id unknown # 4. 保存到数据库 if price and product_id ! unknown: conn sqlite3.connect(db_path) cursor conn.cursor() # 首先确保商品信息存在 cursor.execute(SELECT 1 FROM products WHERE product_id ?, (product_id,)) if not cursor.fetchone(): cursor.execute(INSERT INTO products (product_id, product_name, product_url) VALUES (?, ?, ?), (product_id, product_name, product_url)) # 插入价格记录 cursor.execute(INSERT INTO price_history (product_id, price) VALUES (?, ?), (product_id, price)) conn.commit() conn.close() print(f[成功] 商品 {product_name} ({product_id}) 价格: {price} 已保存。) return price else: print(f[失败] 无法从 {product_url} 解析出价格或商品ID。) return None except requests.exceptions.RequestException as e: print(f[网络错误] 请求 {product_url} 失败: {e}) return None except Exception as e: print(f[解析错误] 处理 {product_url} 时发生未知错误: {e}) return None finally: # 每次抓取后随机延迟模拟人类操作 delay random.uniform(2, 5) time.sleep(delay) # 示例使用 if __name__ __main__: # 初始化数据库 init_database() # 要监控的商品列表 product_urls [ https://item.jd.com/100012345678.html, # 替换为真实商品链接 https://item.jd.com/100098765432.html, ] for url in product_urls: fetch_jd_price(url)这个函数包含了基本的错误处理网络超时、解析失败并实现了简单的降级策略。在实际部署时你需要将product_urls列表替换为你真正关心的商品链接。4. 任务自动化调度与部署4.1 使用Cron实现Linux系统定时任务让爬虫自动定时运行最可靠的方法是借助操作系统的任务调度器。在Linux或Mac上我们使用Cron。编写可执行脚本将你的爬虫代码封装在一个Python脚本中例如jd_price_monitor.py。确保脚本开头有正确的shebang例如#!/usr/bin/env python3并且脚本有执行权限chmod x jd_price_monitor.py。脚本内部应包含所有必要的逻辑读取商品列表、遍历抓取、记录日志。编辑Cron任务在终端输入crontab -e来编辑当前用户的Cron配置。添加定时规则Cron的语法是分钟 小时 日 月 星期 要执行的命令。假设我们希望每天上午10点和下午4点各运行一次可以添加两行0 10 * * * /usr/bin/python3 /path/to/your/jd_price_monitor.py /path/to/your/cron.log 21 0 16 * * * /usr/bin/python3 /path/to/your/jd_price_monitor.py /path/to/your/cron.log 210 10 * * *表示每天10:00。/usr/bin/python3是Python解释器的绝对路径使用which python3命令查看你的路径。/path/to/your/jd_price_monitor.py是你的脚本绝对路径。 /path/to/your/cron.log 21将脚本的标准输出和错误输出都重定向追加到日志文件这对于排查无人值守运行时的问题至关重要。保存并生效保存Cron配置文件后任务就会按照设定自动执行。你可以通过tail -f /path/to/your/cron.log来实时查看运行日志。4.2 脚本健壮性与日志记录一个用于生产环境的自动化脚本必须考虑健壮性和可观测性。异常捕获与继续执行在遍历商品URL列表的循环中要对每个商品的抓取进行独立的try...except包装。这样即使某个商品页面结构变化导致抓取失败也不会影响后续其他商品的抓取。for url in product_urls: try: fetch_jd_price(url) except Exception as e: logging.error(f处理商品链接 {url} 时发生错误: {e}) continue # 继续下一个商品完善的日志记录不要只用print使用Python内置的logging模块。它可以方便地设置日志级别DEBUG, INFO, WARNING, ERROR、输出到文件、并格式化日志信息。import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(price_monitor.log), logging.StreamHandler() # 同时输出到控制台 ] ) logging.info(价格监控任务开始...)添加运行状态检查可以在脚本开始时检查网络连接结束时发送通知如邮件、钉钉机器人消息报告本次抓取的成功/失败概况。这对于监控脚本是否存活非常有用。5. 数据可视化与深度分析实战数据存好了接下来就是让它产生价值。我们将使用pandas和seaborn进行数据分析与可视化。5.1 数据清洗与准备从数据库读出的原始数据可能需要清洗。import pandas as pd import sqlite3 import matplotlib.pyplot as plt import seaborn as sns sns.set_style(whitegrid) # 设置seaborn绘图风格 def load_price_data(db_path, product_idNone, days_back30): 从数据库加载价格数据 :param product_id: 指定商品ID为None则加载所有商品 :param days_back: 加载最近多少天的数据 conn sqlite3.connect(db_path) # 构建查询条件 where_clause fWHERE timestamp datetime(now, -{days_back} days) if product_id: where_clause f AND product_id {product_id} query f SELECT ph.*, p.product_name FROM price_history ph JOIN products p ON ph.product_id p.product_id {where_clause} ORDER BY ph.timestamp df pd.read_sql_query(query, conn, parse_dates[timestamp]) conn.close() # 数据清洗 # 1. 检查缺失值 if df[price].isnull().any(): logging.warning(存在价格缺失值将进行删除或填充。) df df.dropna(subset[price]) # 简单删除也可用前后值填充 # 2. 检查重复记录同一时间戳的同一商品 duplicate_mask df.duplicated(subset[product_id, timestamp], keepfirst) if duplicate_mask.any(): logging.warning(f发现 {duplicate_mask.sum()} 条重复记录已删除。) df df[~duplicate_mask] # 3. 将时间戳设为索引便于时间序列分析 df.set_index(timestamp, inplaceTrue) return df5.2 单商品价格趋势可视化这是最基础也是最核心的分析。我们可以清晰地看到价格随时间的变化。def plot_single_product_trend(df, product_id): 绘制单个商品的价格趋势图 # 筛选指定商品数据 product_df df[df[product_id] product_id].copy() if product_df.empty: print(f未找到商品 {product_id} 的数据。) return product_name product_df[product_name].iloc[0] plt.figure(figsize(14, 6)) # 使用seaborn的lineplot自带置信区间ci参数这里设为None sns.lineplot(dataproduct_df, xproduct_df.index, yprice, markero, linewidth2, markersize6) # 计算并标注近期最低价和最高价 min_price product_df[price].min() max_price product_df[price].max() min_date product_df[price].idxmin() max_date product_df[price].idxmax() plt.scatter(min_date, min_price, colorred, s100, zorder5, labelf最低价: {min_price}) plt.scatter(max_date, max_price, colorgreen, s100, zorder5, labelf最高价: {max_price}) plt.title(f{product_name} ({product_id}) 近{len(product_df)}次抓取价格趋势, fontsize16) plt.xlabel(日期, fontsize12) plt.ylabel(价格 (元), fontsize12) plt.legend() plt.xticks(rotation45) plt.tight_layout() # 保存图片 plt.savefig(fprice_trend_{product_id}.png, dpi300) plt.show() # 输出简单统计信息 print(f统计周期: {product_df.index.min()} 至 {product_df.index.max()}) print(f价格区间: {min_price} - {max_price}) print(f平均价格: {product_df[price].mean():.2f}) print(f价格标准差: {product_df[price].std():.2f} (波动性指标))5.3 多商品竞品价格对比分析将多个竞品放在一张图上对比策略一目了然。def plot_competitor_comparison(df, product_id_list): 绘制多个商品竞品的价格对比趋势图 competitor_df df[df[product_id].isin(product_id_list)].copy() plt.figure(figsize(16, 8)) # 使用seaborn的lineplot通过hue参数按商品区分线条 ax sns.lineplot(datacompetitor_df, xcompetitor_df.index, yprice, hueproduct_name, markero, linewidth2, markersize6, estimatorNone) # estimatorNone表示绘制所有点 plt.title(f竞品价格对比分析, fontsize18) plt.xlabel(日期, fontsize14) plt.ylabel(价格 (元), fontsize14) plt.legend(title商品名称, bbox_to_anchor(1.05, 1), locupper left) plt.xticks(rotation45) plt.tight_layout() plt.savefig(competitor_comparison.png, dpi300, bbox_inchestight) plt.show() # 生成竞品价格统计摘要表 summary competitor_df.groupby(product_name)[price].agg([min, max, mean, std, count]).round(2) summary.columns [最低价, 最高价, 平均价, 价格波动(标准差), 数据点数] print(竞品价格统计摘要:) print(summary)5.4 价格波动性与统计分析进阶除了看图我们还可以用统计方法量化分析。def advanced_price_analysis(df, product_id): 对单个商品进行进阶价格分析 product_df df[df[product_id] product_id].copy() # 1. 计算每日均价如果一天内有多次抓取 daily_avg product_df[price].resample(D).mean() # 按天重采样并计算均值 # 2. 计算价格变化率日环比 price_pct_change daily_avg.pct_change() * 100 # 计算百分比变化 # 3. 识别价格稳定期和波动期例如连续N天价格变动小于阈值 threshold 1.0 # 价格变动1%作为阈值 stable_periods (price_pct_change.abs() threshold).rolling(window3).sum() 3 # 连续3天波动小于1% # 4. 布林带分析 (Bollinger Bands) - 识别价格是否处于高位或低位 rolling_mean product_df[price].rolling(window7).mean() # 7期移动平均 rolling_std product_df[price].rolling(window7).std() upper_band rolling_mean (rolling_std * 2) lower_band rolling_mean - (rolling_std * 2) # 将分析结果可视化 fig, axes plt.subplots(2, 2, figsize(16, 12)) # 子图1: 日均价与原始点 axes[0, 0].plot(daily_avg.index, daily_avg.values, colorblue, linewidth2, label日均价) axes[0, 0].scatter(product_df.index, product_df[price], alpha0.5, s10, colorgray, label原始数据点) axes[0, 0].set_title(日均价趋势与原始数据) axes[0, 0].legend() axes[0, 0].tick_params(axisx, rotation45) # 子图2: 价格日环比变化 axes[0, 1].bar(price_pct_change.index, price_pct_change.values, colorprice_pct_change.map(lambda x: red if x 0 else green)) axes[0, 1].axhline(y0, colorblack, linestyle-, linewidth0.5) axes[0, 1].set_title(价格日环比变化 (%)) axes[0, 1].tick_params(axisx, rotation45) # 子图3: 价格稳定期标识 axes[1, 0].plot(product_df.index, product_df[price], colorlightblue, label价格) # 在稳定期上叠加背景色 for i in range(len(stable_periods)-1): if stable_periods[i]: axes[1, 0].axvspan(product_df.index[i], product_df.index[i1], alpha0.3, colorgreen) axes[1, 0].set_title(价格曲线与稳定期绿色背景) axes[1, 0].legend() axes[1, 0].tick_params(axisx, rotation45) # 子图4: 布林带 axes[1, 1].plot(product_df.index, product_df[price], label实际价格, colorblack) axes[1, 1].plot(product_df.index, rolling_mean, label7日移动均线, colorblue) axes[1, 1].plot(product_df.index, upper_band, label上轨 (均值2σ), colorred, linestyle--) axes[1, 1].plot(product_df.index, lower_band, label下轨 (均值-2σ), colorgreen, linestyle--) axes[1, 1].fill_between(product_df.index, lower_band, upper_band, alpha0.2, colorgray) axes[1, 1].set_title(价格布林带分析) axes[1, 0].legend() axes[1, 1].tick_params(axisx, rotation45) plt.suptitle(f商品 {product_id} 进阶价格分析, fontsize16) plt.tight_layout() plt.savefig(fadvanced_analysis_{product_id}.png, dpi300) plt.show()通过这些分析你不仅能看到价格“是什么样”更能理解它“为什么这样变”。例如布林带收窄可能预示着即将出现大的价格突破涨价或降价而识别出的价格稳定期则可能是补货或调整策略的安全窗口。6. 常见问题排查与实战心得在实际搭建和运行这套系统的过程中我遇到了不少坑。这里把最常见的问题和解决方案整理出来希望能帮你节省时间。6.1 爬虫抓取失败问题排查表问题现象可能原因排查步骤与解决方案返回状态码403/4041. 请求头Headers不完整或被识别为爬虫。2. IP地址被暂时封禁。3. 商品链接失效或下架。1.检查Headers确保User-Agent是常见的浏览器字符串添加Referer通常设为京东主页https://www.jd.com。2.降低频率大幅增加请求间隔如30秒以上或切换网络如用手机热点测试是否IP被限。3.手动访问在浏览器中打开该链接确认商品页面正常。能获取HTML但解析不到价格1. 页面结构已更新CSS选择器失效。2. 价格由JavaScript动态加载初始HTML中没有。1.更新选择器重新使用开发者工具检查元素找到新的价格标签路径。可能需要使用更通用的父级容器或通过id查找。2.启用动态渲染如果确认是JS加载考虑使用Selenium或Playwright。或者在“网络”选项卡中寻找获取价格的API接口XHR请求直接模拟该请求。抓取到的价格是“**”或乱码1. 页面编码问题。2. 价格元素内包含其他干扰文本或HTML实体。1.设置编码在requests获取响应后强制设置response.encoding ‘utf-8’或’gbk’试试。2.清洗数据用正则表达式re.search(r[\d,]\.?\d*’, price_text)精准提取数字部分。程序运行一段时间后突然失败1. 京东反爬策略升级如要求验证码。2. 本地网络或代理不稳定。3. 数据库连接数达到上限长时间运行后。1.观察日志查看失败时的具体错误信息。如果是验证码可能需要引入打码平台或更复杂的模拟行为。2.增加异常重试对请求函数添加重试机制如tenacity库。3.管理数据库连接确保每次数据库操作后都正确关闭连接使用with语句或try…finally。定时任务Cron未执行1. Cron语法错误。2. 脚本执行路径或Python环境问题。3. 脚本本身有错误导致提前退出。1.检查Cron日志Linux下可查看/var/log/syslog或/var/log/cron寻找Cron执行记录和错误输出。2.使用绝对路径在Cron命令和脚本内部所有文件路径、Python解释器路径都应使用绝对路径。3.测试脚本手动在命令行运行脚本确保它能独立正常运行。在脚本开头添加print(“Cron job started”)并重定向输出到文件以确认是否被执行。6.2 数据存储与分析中的坑时区问题数据库中的timestamp字段默认可能是UTC时间。如果你的服务器在中国而分析时希望用北京时间需要在存入时或查询时进行转换。我建议在存入数据库时就使用本地时间timestamp datetime.now().strftime(‘%Y-%m-%d %H:%M:%S’)。数据重复如果调度频率过高或者脚本被意外多次触发可能导致短时间内插入多条完全相同的数据。除了在数据库层面设置(product_id, timestamp)的唯一约束在插入前也可以先检查近期如1分钟内是否已有记录。价格异常值网络波动或页面解析错误可能导致抓取到离谱的价格如0.01元或99999元。在数据分析前必须进行清洗。可以设定一个合理的价格范围如历史均价的±50%将超出此范围的数据视为异常进行剔除或标记。可视化图表过于拥挤当监控商品很多、时间跨度很长时将所有线条画在一张图上会变成一团乱麻。解决方案1按商品类别分开展示2使用交互式图表库如Plotly可以缩放和筛选3聚焦核心商品或按周/月进行数据聚合后再绘图。6.3 个人实战心得与建议从简单开始逐步迭代不要试图一开始就做一个监控几百个商品、每秒抓取的高并发系统。先从监控1-2个核心商品、每小时抓取一次开始。验证整个流程抓取-存储-分析跑通后再逐步增加商品数量和优化架构。尊重网站合规使用在爬虫代码中务必设置合理的延迟如3-5秒以上避免对京东服务器造成压力。你的目标是获取数据支持决策而不是攻击网站。查看京东的robots.txt文件遵守其爬虫协议。数据比代码更重要确保数据存储的可靠性和完整性是第一要务。定期备份数据库文件.db。在代码中做好异常处理即使某次抓取全部失败也不要让程序崩溃导致后续任务无法执行。分析思路比图表炫技更重要初期不必追求特别复杂的图表。先把“价格-时间”折线图画清楚、画准确。在此基础上再去思考你需要回答什么业务问题“什么时候买最便宜”、“竞品是否在跟我打价格战”然后选择或设计合适的分析方法和可视化方案。考虑封装与部署当脚本稳定后可以考虑将其封装成更易用的形式。例如使用argparse库添加命令行参数方便指定监控的商品列表文件或运行模式或者使用Docker容器化部署解决环境依赖问题方便在不同服务器上迁移。这套系统搭建起来后它就成为了一个默默工作的数据助手。你可以每天早上打开邮箱或看板就能看到一份清晰的价格监控报告这对于做出快速、精准的电商运营决策来说价值是实实在在的。整个过程中最花时间的往往不是写代码而是应对网站反爬策略的调整和数据分析思路的打磨这也是爬虫和数据项目最有挑战也最有乐趣的地方。本文还有配套的精品资源点击获取