尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从爬虫到可视化:美妆大数据分析系统全链路实践

发布时间:2026/9/19 11:35:05

资讯中心
01
ARTICLE

从爬虫到可视化:美妆大数据分析系统全链路实践

从爬虫到可视化:美妆大数据分析系统全链路实践
简介Hadoop爬虫Spark美妆大数据分析可视化系统是一份完整的毕业设计论文面向计算机、大数据、电子商务等专业本科毕业生可用于解决毕设选题难、论文框架不清晰等问题也为美妆行业从业者提供基于公开数据做市场分析的参考案例。资源为单个docx文件压缩包大小3.85MB内容包含中英文摘要、目录、绪论、开发环境与工具、技术路线、爬虫设计、数据清洗与存储、可视化分析等章节论文结构完整逻辑清晰。目前已有535人学习在同类毕业设计资料中具有一定的参考热度。论文围绕Python、PyCharm、Scrapy实现美妆相关数据的爬取结合Hadoop与Spark完成海量数据清洗和计算使用MySQL进行结果存储并通过柱状图、折线图、饼图等方式直观展示市场趋势、品牌竞争和消费者偏好。读者既能了解从数据采集、存储、分析到可视化的完整技术链路也能获得毕业设计写作、系统实现与答辩准备的直接参考适合用于论文借鉴、项目复现和答辩思路梳理。1. 不止是爬虫加图表美妆数据系统的真正工作量选“潮流美妆大数据分析可视化”做课题的人多半被“大数据”三个字吓住过。拆开看核心链路并不复杂Scrapy 抓取、pandas 清洗、MySQL 存储、Spark 批处理、Django 渲染看板一条线走完。真正做过一遍会发现爬虫只占三成工作量剩下七成在字段设计与数据清洗。字段怎么定、脏数据怎么清、维度表怎么建这些细节才是答辩时能讲出深度的部分也决定了分析结果可不可信。这篇按实际开发顺序拆解适合正在做同类毕业设计的学生也适合想快速搭数据分析 Demo 的开发者。命令和参数直接可抄常见坑一并标注照着走就能跑通整个链路。2. 技术选型与数据链路Scrapy、MySQL、Spark 的职责边界2.1 B/S 架构与 Django 的选型理由系统采用 B/S 模式前台展示与数据处理分层。框架选 Django 而不是 Flask主要看中三点自带 Admin 后台可以用极少量代码完成管理员对用户和美妆信息表的增删改查ORM 屏蔽了原生 SQL表结构变更时不需要大面积改查询语句URLConf 的正则路由在写图表接口时非常灵活一个路径对应一个视图排查问题比 Flask 的集中式路由更直观。版本搭配要留意兼容性。论文环境是 Python 3.6.4低版本 Django 不支持 Python 3官方推荐搭配是 Django 3.2.12。如果本机装的是 Python 3.10 以上Django 建议直接上 4.2但 3.2 的 LTS 版本对课设来说最稳网上排错资料最多踩坑成本最低。提示Django 3.2 在 Python 3.10 下会提示兼容性 warning但不影响运行。想零警告就二选一Python 降到 3.8或者 Django 升到 4.2。2.2 Scrapy 和 Requests 的差距在并发模型采集层用 Scrapy 而不是 Requests核心差距在并发模型。Requests 是同步阻塞一个线程只能等一个请求返回想提速就得自己开线程池Scrapy 基于 Twisted 异步事件循环单进程能维持几十个并发连接抓美妆列表页这种密集型任务速度差距在 5 到 10 倍。Scrapy 还自带去重、重试、限速和 Item Pipeline这些在 Requests 方案里全要手写写到后面错误处理很难覆盖完整。新手常犯的错误是把 Scrapy 当 Requests 用在 parse 里写 for 循环逐个调用 Request把异步框架写成了同步。正确做法是 yield Request 交给调度器让框架自己维护并发队列。另外 scrapy 的 Request 默认会去重翻页 URL 带动态参数时需要注意指纹计算方式否则页面特征变了也抓不到新数据。2.3 Hadoop 与 Spark 在链路里的真实位置很多课设在 Hadoop 和 Spark 上只是装个环境跑 wordcount这是本末倒置。这套系统里它们的定位是Hadoop 提供 HDFS 做原始数据的备份存储Spark 对 MySQL 导出的明细数据做批量聚合计算品牌销量份额、价格带分布、评分趋势这些指标。单机环境下 Hadoop 用伪分布式即可Spark 跑 local 模式不需要搭真正意义的 spark 集群。MapReduce 不是不能用但同一份聚合逻辑MapReduce 要写几十行 JavaSpark 用 DataFrame API 十几行搞定而且中间结果在内存里流转迭代计算快一到两个数量级。这也是选择 Spark 而不是纯 MapReduce 的理由。技术栈完整分工如下环节工具职责数据形态采集Scrapy抓取美妆信息、评价、销量JSON / CSV清洗pandas去重、格式统一、异常值处理DataFrame存储MySQL持久化明细数据关系表计算Hadoop Spark原始数据备份、批量聚合RDD / DataFrame展示Django ECharts接口与图表渲染JSON 到图表这套分工的边界很清晰Scrapy 只负责把页面变成结构化数据不关心数据怎么消费Spark 只做计算不关心数据从哪来Django 只做展示不承担任何清洗逻辑。边界一旦模糊比如在爬虫里写 SQL join或者在 Django 里做全表聚合系统很快就会变成改一处崩三处的状态。3. Scrapy 爬虫落地从 Item 定义到 Pipeline 入库3.1 工程初始化与 Item 字段设计爬虫工程在 PyCharm 的 Terminal 里用命令行创建不要手动建目录scrapy startproject beauty_crawler cd beauty_crawler scrapy genspider beauty_spider example.com创建后目录结构为spiders 目录放爬虫主逻辑items.py 定义字段pipelines.py 做清洗入库settings.py 控制并发和下载延迟。美妆信息的核心字段定义如下# items.py import scrapy class BeautyItem(scrapy.Item): title scrapy.Field() # 商品标题 brand scrapy.Field() # 品牌后续分组维度 category scrapy.Field() # 品类口红/粉底/眼影 price scrapy.Field() # 价格入库前转 float sales scrapy.Field() # 销量用于排序分析 rating scrapy.Field() # 评分保留一位小数 comment_count scrapy.Field() # 评价数判断热度 author scrapy.Field() # 发布者部分来源是笔记 source_url scrapy.Field() # 详情页 URL唯一去重 crawl_time scrapy.Field() # 抓取时间默认当前时间字段设计的原则是先想清楚要分析什么再定抓什么。如果后面要做“价格区间 × 品类”的交叉分析price 和 category 就必须拆成独立字段不能塞进一个描述文本里。source_url 强烈建议保留既用来做 URL 去重也方便回溯原始页面。scrapy.Field()本身不限制类型类型校验放在 Pipeline 里做比放在 Item 里更灵活。3.2 列表页解析与翻页实现解析用 XPath 比正则稳定。页面结构变化时 XPath 只需要改路径正则要重写匹配逻辑。一个列表页的典型写法# spiders/beauty_spider.py import scrapy from beauty_crawler.items import BeautyItem class BeautySpider(scrapy.Spider): name beauty_spider start_urls [https://example.com/beauty] def parse(self, response): # 选中所有商品节点逐条提取字段 for node in response.xpath(//div[contains(class,product-item)]): item BeautyItem() item[title] node.xpath(.//a[classtitle]/text()).get() item[brand] node.xpath(.//span[classbrand]/text()).get() item[price] node.xpath(.//span[classprice]/text()).get() item[sales] node.xpath(.//span[classsales]/text()).get() item[source_url] node.xpath(.//a/href).get() yield item # 翻页下一页链接继续回调 parse next_page response.xpath(//a[classnext]/href).get() if next_page: yield scrapy.Request( urlresponse.urljoin(next_page), callbackself.parse )两个关键点。第一.//表示从当前节点往下找get()返回第一个匹配值取不到时返回 None 而不是抛异常解析容错性更好。第二翻页用response.urljoin()拼接相对路径比手写字符串拼接安全能自动处理协议相对地址//host/path这类情况。3.3 清洗 Pipeline 与增量入库Pipeline 是 Scrapy 里做数据清洗的位置。每个 Item 从 spider 出来后按优先级依次经过所有 Pipeline常见做法是第一个做字段清洗第二个做入库# pipelines.py import pymysql from itemadapter import ItemAdapter class CleanPipeline: def process_item(self, item, spider): adapter ItemAdapter(item) # 价格清理去掉货币符号和空格转 float price str(adapter.get(price, )).replace(¥, ).strip() adapter[price] float(price) if price else 0.0 # 空标题直接丢弃 if not adapter.get(title): raise DropItem(empty title, dropped) return item class MysqlPipeline: def open_spider(self, spider): self.conn pymysql.connect( host127.0.0.1, userroot, password123456, databasebeauty_db, charsetutf8mb4 ) self.cursor self.conn.cursor() def process_item(self, item, spider): sql INSERT INTO beauty_info (title, brand, category, price, sales, rating, comment_count, source_url, crawl_time) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE sales VALUES(sales) self.cursor.execute(sql, ( item[title], item[brand], item[category], item[price], item[sales], item[rating], item[comment_count], item[source_url], item[crawl_time] )) self.conn.commit() return item def close_spider(self, spider): self.cursor.close() self.conn.close()入库 SQL 里的ON DUPLICATE KEY UPDATE是增量更新的关键source_url 建唯一索引后重复抓取同一商品不会插入新行而是更新销量和评价数。这样每天定时跑一次爬虫就能积累美妆商品的时间序列数据后续做趋势分析才有素材。3.4 并发与反爬参数设置settings.py 里按目标站点承受能力设置并发不要盲目调大# settings.py CONCURRENT_REQUESTS 16 # 全局并发数32 以上容易被封 IP DOWNLOAD_DELAY 0.5 # 每请求间隔单位秒 ROBOTSTXT_OBEY False # 课设环境按需关闭生产环境需合规 DEFAULT_REQUEST_HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: https://example.com/ }CONCURRENT_REQUESTS和DOWNLOAD_DELAY是反比关系。出现 403 或频繁验证码时先把并发降到 4、延迟提到 2观察一段时间再逐步回调。DOWNLOAD_DELAY对同一域名生效多域名抓取时可以适当调低。4. 数据清洗与 MySQL 表结构分析质量的决定因素4.1 pandas 清洗的标准操作爬虫落库的数据不能直接分析常见问题包括品牌写法不一致、价格为 0 的异常行、评分超出区间、时间格式不统一。清洗脚本单独放一个文件用 pandas 读库处理后写回# clean_data.py import pandas as pd import pymysql conn pymysql.connect(host127.0.0.1, userroot, password123456, databasebeauty_db, charsetutf8mb4) df pd.read_sql(SELECT * FROM beauty_info, conn) # 1. 品牌名标准化统一大小写去除首尾空格 df[brand] df[brand].str.strip().str.title() # 2. 过滤异常价格价格必须大于 0 且小于 5000 df df[(df[price] 0) (df[price] 5000)] # 3. 评分越界处理超过 5 分按 5 分截断 df.loc[df[rating] 5, rating] 5.0 # 4. 时间字段统一为日期格式 df[crawl_time] pd.to_datetime(df[crawl_time]).dt.date # 5. 按商品维度去重保留最新一条 df df.sort_values(crawl_time).drop_duplicates( subset[title, brand], keeplast) # 写回清洗后的数据 df.to_sql(beauty_info_clean, conn, if_existsreplace, indexFalse) conn.close()drop_duplicates(subset[...], keeplast)是清洗里最关键的一步先按抓取时间排序再按标题加品牌去重保证每个商品只保留最新抓取状态避免同一条数据被抓两次造成销量翻倍。to_sql的if_existsreplace适合课设阶段反复跑清洗任务生产环境应该改成增量更新只处理新增时间段的数据。注意pandas 的to_sql对 DECIMAL 字段会有精度问题insert 后建议抽查几条price如果出现 float 尾差入库字段统一改成 DECIMAL(10,2) 并由 SQL 侧负责转换。4.2 表结构与索引设计清洗后的数据落到独立表原始表保留不动方便回溯。核心表设计如下字段类型约束说明idINTPRIMARY KEY AUTO_INCREMENT主键titleVARCHAR(200)NOT NULL商品标题brandVARCHAR(50)INDEX品牌分组维度categoryVARCHAR(50)INDEX品类分组维度priceDECIMAL(10,2)成交价salesINT销量ratingDECIMAL(2,1)评分comment_countINT评价数source_urlVARCHAR(500)UNIQUE INDEX原文链接防重复crawl_timeDATEINDEX抓取日期三个索引位置是设计重点brand 和 category 是分析时最常用的分组维度必须建索引crawl_time 建索引是因为后面做时间序列分析时要按日期范围过滤没有索引的话千万行级别的全表扫描会直接拖垮查询。source_url 的唯一索引对应爬虫入库时的ON DUPLICATE KEY UPDATE两者必须配套使用。4.3 用 SQL 验证数据分布清洗完先不急着上 SparkMySQL 里基础聚合先跑一遍确认数据分布合理-- 按品类统计销量 Top 10 SELECT category, SUM(sales) AS total_sales FROM beauty_info_clean GROUP BY category ORDER BY total_sales DESC LIMIT 10; -- 品牌价格带分布每 100 元一档 SELECT brand, FLOOR(price / 100) * 100 AS price_band, COUNT(*) AS product_cnt FROM beauty_info_clean GROUP BY brand, FLOOR(price / 100) * 100 ORDER BY brand, price_band; -- 近 30 天抓取量趋势 SELECT crawl_time, COUNT(*) AS daily_cnt FROM beauty_info_clean WHERE crawl_time DATE_SUB(CURDATE(), INTERVAL 30 DAY) GROUP BY crawl_time ORDER BY crawl_time;这三个查询分别回答“什么品类卖得好”“品牌定位在什么价格段”“采集是否连续”。最后一个尤其重要如果某天 daily_cnt 掉到接近 0说明当天爬虫被反爬拦截了需要回去看抓取日志而不是继续往下做分析。数据采集断层会让后续的趋势分析出现假性下跌这个要提前排除。5. Spark 批处理在 Hadoop 上算品牌竞争度与消费偏好5.1 JDBC 读取与分区参数单机课设不需要 Spark 集群local 模式加 JDBC 直读 MySQL 就够# spark_analysis.py from pyspark.sql import SparkSession spark SparkSession.builder \ .appName(BeautyAnalysis) \ .master(local[2]) \ .config(spark.sql.shuffle.partitions, 4) \ .getOrCreate() df spark.read \ .format(jdbc) \ .option(url, jdbc:mysql://127.0.0.1:3306/beauty_db) \ .option(dbtable, beauty_info_clean) \ .option(user, root) \ .option(password, 123456) \ .option(driver, com.mysql.jdbc.Driver) \ .load()master(local[2])表示本地双核运行不提交集群。spark.sql.shuffle.partitions4是关键调参默认 200 个分区在单机上纯属浪费资源每个分区都要起 task改成 4 个能明显减少 shuffle 文件数和内存压力。读取大表时还可以加partitionColumn和lowerBound、upperBound做并行读取让多个 task 各读一段主键范围。5.2 品牌竞争度与消费偏好指标用 DataFrame API 做聚合写法接近 SQL 但后续接机器学习更方便from pyspark.sql import functions as F # 品牌竞争度销量份额 品类覆盖数 brand_stats df.groupBy(brand).agg( F.sum(sales).alias(total_sales), F.countDistinct(category).alias(category_cnt), F.avg(rating).alias(avg_rating) ).orderBy(F.desc(total_sales)) # 价格带 × 品类交叉分析 price_cat df.withColumn( price_band, (F.col(price) / 100).cast(int) * 100 ).groupBy(price_band, category).count() # 消费偏好评论数大于 100 的商品里按评分排序 top_rated df.filter(F.col(comment_count) 100) \ .orderBy(F.desc(rating), F.desc(comment_count)) \ .limit(20)countDistinct(category)算品牌覆盖的品类数能区分“专精型品牌”和“全品类品牌”这是论文里品牌竞争度分析的核心指标。filter里限制评论数大于 100是为了过滤掉刷分的冷门商品只看有真实用户基础的评分避免分析结果被异常值带偏。5.3 结果写回 MySQL 与 HDFS 备份分析结果用两种方式落盘指标表写回 MySQL 供 Django 读取原始明细备份到 HDFS# 写回 MySQL供 Django 看板使用 brand_stats.write \ .format(jdbc) \ .option(url, jdbc:mysql://127.0.0.1:3306/beauty_db) \ .option(dbtable, agg_brand_stats) \ .option(user, root) \ .option(password, 123456) \ .option(truncate, true) \ .mode(overwrite) \ .save() # 原始数据备份到 HDFS df.write.mode(overwrite) \ .parquet(hdfs://localhost:9000/user/beauty/clean_data)写回 MySQL 时.option(truncate, true)配合.mode(overwrite)先清表再写入避免重复跑任务时数据翻倍。HDFS 备份用 parquet 格式而不是 CSV列式存储压缩率高后续增量分析可以直接用 Spark 读 parquet省掉一次 JDBC 全量拉取。提示Spark 写 MySQL 时遇到ClassNotFound com.mysql.jdbc.Driver是驱动 jar 没进 CLASSPATH。把 mysql-connector-java 的 jar 放到$SPARK_HOME/jars目录重启 SparkSession 即可。6. Django 看板渲染与排错实操6.1 JSON 接口与 ECharts 双轴图Spark 算好的聚合结果写回 MySQL 后Django 只负责查表。视图里用游标执行只读 SQL避免 ORM 在聚合查询时生成多层子查询from django.http import JsonResponse from django.db import connection def brand_chart(request): with connection.cursor() as cursor: cursor.execute( SELECT brand, total_sales, avg_rating FROM agg_brand_stats ORDER BY total_sales DESC LIMIT 10 ) rows cursor.fetchall() return JsonResponse({code: 0, data: [ {brand: r[0], sales: float(r[1]), rating: float(r[2])} for r in rows ]})前端模板引入 EChartsfetch 后 setOption。画销量柱状图叠加评分折线图时评分 y 轴必须设max: 5否则折线被压缩在图表底部趋势完全看不出来。图表容器需要显式声明高度父级隐藏时初始化会导致渲染宽度为 0图表空白。6.2 三个方向的排错顺序看板打开慢按接口、数据库、计算层三步排查。接口慢就看 Django 日志和浏览器 Network 面板的耗时分布数据库慢就开 MySQL 慢查询日志用EXPLAIN看聚合 SQL 是否走了索引Spark 任务慢则在 local 模式下调spark.driver.memory而不是盲目加分区数。爬虫内存持续上涨通常是并发设置过高或 Item 里累积了大字段。用scrapy parse --spiderbeauty_spider URL单条调试看请求耗时分布比反复改代码重启快。全部跑通后把爬虫和 Spark 任务交给 cron 定时调度Django 只读聚合表三层解耦之后任一层出问题都不会拖垮整条链路这也是这套系统最值得保留的架构习惯。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。