ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python和MySQL的电商比价可视化分析系统开发详解

基于Python和MySQL的电商比价可视化分析系统开发详解 断断续续做了两周多的电商比价可视化分析系统今天总算把源码、数据库脚本和说明文档全部整理归档。这套系统基于Python 3 MySQL 8.0实现覆盖从商品信息抓取、价格历史存储到Web页面可视化的完整链路。如果你正打算做类似的可视化分析项目或者需要一套能直接跑起来的电商数据Demo这篇文章应该能让你少走不少弯路。先交代一下背景。我最初的需求很简单把几个主流电商平台上的同款商品价格抓下来存进数据库再通过图表展示价格走势和平台对比。可真到动手就会发现单纯的爬虫只是第一关数据入库、清洗、同款匹配、图表联动每一个环节都有坑。这篇文章会从整体架构、数据库设计、关键实现到问题排查全部过一遍代码片段都是能直接用的。1. 项目整体设计与思路拆解1.1 这次要解决的痛点是什么电商比价听起来不复杂但真正做起来有几个很烦人的点。第一数据分散在多个平台靠人工打开网页挨个查价格效率低且容易遗漏。第二商品价格不是一成不变的今天看是低价明天可能就涨了没有历史记录就看不出规律。第三原始数据堆在数据库里没有意义用户需要的是“哪个平台最便宜”“最近30天价格怎么波动”这类直观结论。所以这个项目的定位不是单纯写个爬虫而是一个能完成数据采集、存储、分析、展示的闭环系统。从实际使用场景出发系统至少得具备这些能力支持多平台商品数据采集、能记录价格历史变化、能识别不同平台的同款商品、能按价格排序和对比最后再通过可视化图表把结果呈现出来。这样一来无论是做课程设计还是自己平时购物前查个底价都能派上用场。1.2 技术选型Python、MySQL和可视化框架怎么搭技术栈选择上我几乎没有纠结直接定了Python MySQL Pyecharts/Flask的组合。Python在数据采集和处理领域生态太成熟了requests、BeautifulSoup、pandas这些库直接拿来用省去很多造轮子的时间。MySQL则负责稳定的数据存储和查询价格记录这种结构化数据用关系型数据库特别合适SQL做聚合排序也方便。可视化部分我用了Pyecharts做图表再用Flask把它们串成一个轻量Web页面这样不用装额外的桌面环境浏览器打开就能看。有人可能会问为什么不用Excel或者MongoDBExcel做一次性分析可以但增量写入、历史查询、并发写入都不行MongoDB虽然灵活但比价系统里多表关联、价格排序这些场景SQL更顺手。所以在这个项目规模下Python MySQL是最省心的组合。1.3 系统功能模块划分整个系统我拆成了五个模块每个模块职责单一方便单独调试和替换。模块职责主要技术点数据采集模块抓取商品标题、价格、销量、商品链接requests、BeautifulSoup、解析适配数据清洗模块处理脏数据、统一格式、生成商品编码pandas、正则表达式数据库模块商品表、平台表、价格记录表的建表与访问MySQL、pymysql、SQL分析模块多平台比价、价格趋势计算、最新价排名SQL窗口函数、pandas可视化模块生成折线图、柱状图提供Web展示Pyecharts、Flask、HTML模板这样一来爬虫只要管采集分析模块不用关心数据从哪来把接口约定好就行。后面想扩展新的电商平台只要在爬虫模块加一个解析适配器其他部分几乎不用动。这种模块化的思维方式比堆功能代码重要得多。2. 数据库设计与数据采集2.1 MySQL表结构设计与索引优化数据库是整个系统的地基表结构没设计好后面写查询都会很痛苦。我实际用了三张核心表商品表、平台表、价格记录表。商品表存的是商品的基础信息核心字段是product_code这个字段是不同平台同款商品的匹配依据。比如“Apple iPhone 15 Pro Max 256GB 原色钛金属”在平台A和平台B的标题可能完全不同但通过归一化提取出“iphone15promax256g”这个编码就能把两个平台的记录关联起来。CREATE TABLE product ( id int(11) NOT NULL AUTO_INCREMENT, product_code varchar(64) NOT NULL COMMENT 商品统一编码用于同款匹配, title varchar(255) NOT NULL COMMENT 商品标题, category varchar(64) DEFAULT NULL COMMENT 商品分类, brand varchar(64) DEFAULT NULL COMMENT 品牌, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_product_code (product_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;平台表很简单记录平台名称和域名以后扩展平台就是往表里插数据的事。价格记录表才是核心每次爬虫采集到的价格、销量都会追加一条记录这样既保留了历史变化又能通过时间维度做趋势分析。CREATE TABLE price_record ( id bigint(20) NOT NULL AUTO_INCREMENT, product_code varchar(64) NOT NULL, platform_id int(11) NOT NULL, product_title varchar(255) DEFAULT NULL COMMENT 采集时标题快照, price decimal(10,2) NOT NULL COMMENT 当前售价, original_price decimal(10,2) DEFAULT NULL COMMENT 划线价, sales int(11) DEFAULT NULL COMMENT 销量, crawl_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 采集时间, PRIMARY KEY (id), KEY idx_product_time (product_code, crawl_time), KEY idx_platform_time (platform_id, crawl_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;索引上我建了product_code crawl_time联合索引因为最常见的查询就是“某个商品在一段时间内的价格走势”。如果没有这个索引数据量上去后查询会很慢。这里有个设计取舍当初也在纠结要不要把商品标题直接存在价格表里后来还是加了product_title快照字段因为商品标题可能被商家修改保留采集时的标题能避免历史数据被覆盖。2.2 爬虫采集与数据清洗流程爬虫部分我并没有写得特别复杂正常情况用requests加BeautifulSoup就够了。但有几个细节值得注意不考虑细节的话抓回来的数据基本没法直接用。首先是请求头。很多平台会检查User-Agent直接发默认的requests头容易被拒绝。我准备了一个请求头列表每次请求随机选一个代码大致这样import random import requests from bs4 import BeautifulSoup import re user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/119.0 Safari/537.36, ] def fetch_url(url): headers {User-Agent: random.choice(user_agents)} resp requests.get(url, headersheaders, timeout10) resp.encoding resp.apparent_encoding return resp.text然后是解析和清洗。每个平台页面结构不一样我把解析逻辑单独抽成一个parser.py每个平台一个函数返回统一格式的字典。价格是个典型脏数据源页面上可能是“¥1,299.00”或者“1299元起”靠正则把非数字字符去掉再转成float。def parse_price(text): if not text: return None cleaned re.sub(r[^0-9.], , text) try: return float(cleaned) except ValueError: return None标题归一化成product_code这步很关键。我的做法是先把标题转小写去掉空格和括号再用规则提取品牌、型号、容量这些关键特征。不追求百分百准确只要同一款商品在不同平台的编码一致就行。实际开发中这一步是最需要反复调试的因为中文标题的写法太自由了。2.3 数据入库与增量更新策略数据入库我一开始走了弯路用for循环一条条insert几百条数据慢到让人怀疑人生。后来改成executemany批量插入速度直接提升几十倍。核心代码其实很简单import pymysql connection pymysql.connect( hostlocalhost, userroot, password123456, databaseprice_db, charsetutf8mb4 ) def insert_prices(records): sql INSERT INTO price_record (product_code, platform_id, product_title, price, original_price, sales, crawl_time) VALUES (%s, %s, %s, %s, %s, %s, %s) with connection.cursor() as cursor: cursor.executemany(sql, records) connection.commit()增量更新的逻辑也值得说一下。如果每次都把所有商品的所有记录重复插入数据库很快就会膨胀。我的策略是入库前先查一下这个商品在这个平台最近一条价格如果价格和销量都没变就跳过如果有变化再插入新记录。这样既保留了历史轨迹又不会无限堆积无用数据。定时任务我用了APScheduler设置每6小时跑一次增量爬虫跑完自动停。3. 核心功能实现与可视化3.1 比价逻辑与同款商品匹配比价不是简单地把所有价格堆在一页而是要把不同平台的同款商品拉平后再排序。这里最大的难点还是同款匹配我靠product_code解决所以爬虫清洗时生成准确的编码就很重要。有了product_code后最常用的查询是“查某商品在各平台的最新价并排序”。MySQL 8.0支持窗口函数我用ROW_NUMBER()给每个平台按采集时间倒序排取最新一条然后再按价格正序。这个写法比子查询高效不少WITH latest_price AS ( SELECT product_code, platform_id, price, crawl_time, ROW_NUMBER() OVER (PARTITION BY product_code, platform_id ORDER BY crawl_time DESC) AS rn FROM price_record WHERE product_code iphone15promax256g ) SELECT product_code, platform_id, price, crawl_time FROM latest_price WHERE rn 1 ORDER BY price ASC;实际测试中这个查询在几十万条记录量级下依然能秒回说明索引发挥了作用。如果不用窗口函数换成关联子查询写法也完全可以但逻辑稍微绕一些。除了最新价对比比价系统还应该能看“历史最低价”和“平均价”。这些用SQL聚合就能实现比如MIN(price)、AVG(price)再结合时间段过滤就能知道当前价格在历史中是高是低这个信息对购物决策很有用。3.2 基于Pyecharts的价格趋势和对比图表可视化选择了Pyecharts优点是API简洁能直接生成HTML文件也方便嵌入Flask。我主要用了两种图表折线图展示单个商品在不同平台的价格走势柱状图展示同一商品当前在各平台的售价对比。折线图的构建代码如下from pyecharts.charts import Line from pyecharts import options as opts def build_trend_chart(date_list, price_list_a, price_list_b): line ( Line() .add_xaxis(date_list) .add_yaxis(平台A, price_list_a, is_smoothTrue) .add_yaxis(平台B, price_list_b, is_smoothTrue) .set_global_opts( title_optsopts.TitleOpts(title商品价格趋势), tooltip_optsopts.TooltipOpts(triggeraxis), ) ) return line柱状图更简单X轴放平台名称Y轴放最新价格一眼就能看出谁便宜。这里有个优化点Pyecharts的图表默认样式其实已经不错但如果要放到Web页面不要直接保存成图片而是在Flask模板里调用render_embed()返回HTML片段这样图表还是可交互的鼠标悬停能看到具体数值。3.3 Flask页面如何把图表串起来Flask在这里起到的作用很轻只是把数据库查询和图表渲染串起来。我写了一个app.py两个路由首页展示所有追踪商品的最新价排名商品详情页展示某个商品的趋势图和平台对比图。from flask import Flask, render_template app Flask(__name__) app.route(/) def index(): # 从数据库获取最新比价排行略 products get_latest_rank() return render_template(index.html, productsproducts) app.route(/product/product_code) def product_detail(product_code): line build_trend_chart_for_product(product_code) bar build_platform_bar_for_product(product_code) return render_template(detail.html, lineline.render_embed(), barbar.render_embed())模板里对应位置直接渲染div idtrend{{ line|safe }}/div div idbar{{ bar|safe }}/div因为render_embed()返回的是已经带div和script的HTML片段所以用|safe过滤一下就行。这个方案不需要前后端分离也不需要npm打包对个人项目或者课程设计来说试错成本极低。4. 完整运行过程与实操复盘4.1 Python与MySQL环境搭建含安装避坑如果你想把这个项目从零跑起来环境准备是第一关也是很多新手卡住的地方。Python建议装3.8及以上版本安装时一定要勾选“Add Python to PATH”不然后续在命令行里敲python会提示找不到命令。装完测试一下python --version pip --versionMySQL我用的8.0安装过程中有一个环节是选字符集务必选utf8mb4不然后面处理中文容易乱码。装完MySQL后建议建一个专属数据库用户不要所有项目都拿root裸奔。连接数据库前先命令行验证一下mysql -u root -p能在MySQL命令行里输入SELECT 1;成功返回说明数据库服务正常。接下来安装项目依赖把所有需要的库一次装齐pip install requests beautifulsoup4 pandas pymysql pyecharts flask apscheduler如果下载速度慢可以把pip源切成国内镜像装包体验会好很多。到这里环境就基本就绪了。4.2 源码目录、数据库导入和配置文件修改整个项目源码的目录结构我保持得很清晰方便把不同功能拆开维护price_analysis/ ├── app.py # Flask入口 ├── config.py # 数据库配置 ├── crawler/ │ ├── spider.py # 爬虫主体 │ └── parser.py # 页面解析与清洗 ├── analyzer/ │ ├── price_metrics.py # 比价计算 │ └── chart_builder.py # 图表生成 ├── templates/ │ ├── index.html │ └── detail.html ├── static/ ├── docs/ │ └── 系统设计文档.md └── database/ └── price_db.sql拿到源码后先把数据库建好、导入初始SQLmysql -u root -p -e CREATE DATABASE IF NOT EXISTS price_db DEFAULT CHARSET utf8mb4; mysql -u root -p price_db database/price_db.sql然后修改config.py里的数据库连接信息把密码改成你自己的DB_CONFIG { host: localhost, user: root, password: your_password, database: price_db, charset: utf8mb4 }需要注意的是如果密码里有、#这样的特殊字符在代码里字符串处理时要小心最好用环境变量或者单独配置文件不要硬编码到源码里。4.3 从爬虫到可视化页面的全流程实操我建议你先不急着跑爬虫而是把数据库里的样例数据调出来看看确认数据链路是通的。执行几条简单SQLSELECT COUNT(*) FROM price_record; SELECT * FROM price_record LIMIT 10;如果初始化脚本里带了样例数据可以直接启动Web页面体验完整效果。如果库是空的再运行爬虫python crawler/spider.py爬虫跑起来后控制台会打印采集到的商品标题和价格。第一次跑可能比较慢因为我在代码里故意设置了2到5秒的随机间隔这是为了避免给目标站点造成压力。等数据库有数据后再启动Flask服务python app.py浏览器访问http://127.0.0.1:5000首页能看到最新的比价排行。点进任意商品详情页能看到近30天的价格趋势折线图和平台最新价格柱状图。整套流程跑通后你会对“数据从哪来、存在哪、怎么展示”有非常直观的感知。5. 常见问题与排查技巧实录5.1 数据库连接失败的几个原因这是出现频率最高的问题报错通常像pymysql.err.OperationalError。我从几个维度排查基本能解决90%的情况。首先是看MySQL服务有没有启动。Windows用户可以去服务管理器里看Linux可以执行systemctl status mysql。服务没启动其他全是白搭。其次是检查端口和账号权限默认端口是3306如果本机端口被改过要在配置里同步改掉。然后是账号授权问题哪怕root也可能只允许本地登录。执行这条确保权限到位GRANT ALL PRIVILEGES ON price_db.* TO rootlocalhost; FLUSH PRIVILEGES;MySQL 8.0默认的认证插件是caching_sha2_password有些老版本的pymysql会连不上。解决办法是升级pymysql或者把账号认证改回mysql_native_password。我自己遇到过一次改成后者后立刻恢复。5.2 中文乱码和编码问题中文乱码基本逃不掉根源一般有两个方向。第一个是MySQL建库建表时没有用utf8mb4导致存储就有问题。好一点的解决路径是建库时就指定CREATE DATABASE price_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;第二个是连接参数没有指定字符集pymysql连接里一定要写charsetutf8mb4。另外爬虫抓网页时也要注意编码很多老页面还是GBK可以在requests拿到响应后用resp.apparent_encoding来猜编码。我踩过最尴尬的一次是图表页面标题中文乱码结果数据却是好的查了半天发现是HTML模板没写meta charsetutf-8。5.3 爬虫数据抓不到或字段为空数据抓不到大概率是页面结构变了或者有基础反爬。我的应对策略是不要把页面解析逻辑写死在主流程里不同平台的解析函数都放到parser.py改起来方便。如果拿到的HTML是空壳很可能是页面数据通过接口异步加载这时候要打开浏览器开发者工具找到真正的数据接口直接请求那个接口比分析DOM稳定得多。还有一点必须提醒抓取频率一定要控制别并发立刻抓几十个页面。我看到过不少教程推荐多线程加速但个人项目根本没有这个必要慢一点反而安全。遇到验证码就停手先检查自己的请求头和行为频率不要想着绕过。合规采集公开数据才是能长期维护的路子。5.4 定时任务与数据重复问题定时任务我踩过重复插入的坑。第一次用APScheduler每6小时跑一次跑了几天发现数据库数据量翻倍因为有些商品价格没变我又插入了一遍。解决方法是入库前先查最新记录价格和销量相同就跳过。另外一个更稳妥的办法是给price_record表加唯一索引控制粒度到“商品平台采集时间分钟级”从数据库层面杜绝重复ALTER TABLE price_record ADD UNIQUE KEY uk_p_platform_time (product_code, platform_id, crawl_time);如果任务跑挂了也可以在代码里加异常捕获记录日志不要让一个商品失败导致整个任务停止。6. 个人实操体会与扩展方向多次调试下来我最大的体会是“先设计数据再写爬虫”。我第一次做的时候一上来就写爬虫结果存到数据库才发现没考虑同款匹配又推倒重来。后来把product_code的规则设计清楚后面所有查询和比价都顺了。还有一条经验图表在精不在多。一套比价系统核心就是价格趋势折线图和平台价格柱状图把这两张图画清楚比堆十个花哨图表有用得多。用户在意的永远是“哪个平台便宜”和“现在买亏不亏”。如果还想继续扩展这个项目有两个方向我觉得很有价值。一个是把爬虫改成独立服务用消息队列接收任务这样即使采集量大也不会阻塞Web服务。另一个是加入价格告警功能用户设置目标价后系统定时检查数据库最新价格低于目标价就通过邮件或企业微信推送。这两个方向不算太难但会让整个系统从“能看”变成“有用”。最后再分享一个小技巧做可视化页面时不要急着马上接真实数据先用SQL造一批模拟数据把图表效果调好再切换真实数据源。这样调试体验好也不会因为爬虫中途失败而卡在展示环节。整个项目已经整理成源码、数据库脚本和文档按上面步骤操作基本可以顺利跑起来。
返回列表