项目标题AI大模型基于大数据Spark小说数据分析系统 可视化 Django框架 requests爬虫 Echarts可视化 python 大数据技术源码1. 项目概述从看书到拆解书平台去年有个做网文运营的朋友跟我吐槽说他们平台上百万本小说编辑选书全靠拍脑袋读者喜欢什么口味的书、哪些分类在崛起、作者更新到多少章最容易养肥读者这些背后全是经验主义。我当时第一反应是——这不就是一个典型的大数据分析场景吗小说平台每天产生海量的点击、推荐、收藏、评论数据把这些数据捞出来洗一洗、算一算、画一画规律就出来了。这个项目的核心思路就是从公开的小说平台网页上抓取作品的基础数据和读者互动数据用Spark做批量分析和特征统计最后在Django搭建的Web系统里用Echarts把结果可视化展示出来。一句话概括爬虫负责取数Spark负责算数Django和Echarts负责读数。对做大数据方向的朋友来说这个项目的价值在于它把一整条数据流水线串起来了——不是教科书里那种一卡一卡的零件而是一个从采集、存储、清洗、分析到可视化展示的完整闭环。你自己把这条链路跑一遍比看十篇技术博客记得牢。我也建议手里有大数据课程设计、毕业设计或者简历项目需求的同学重点参考这套架构。整个项目用到的技术栈相对主流Python做爬虫和数据处理、Spark做核心分析、MySQL做存储、Django做后端Web框架、Echarts做前端图表。全是开源免费的成熟方案不存在版权风险部署起来也不挑环境某平台的Windows笔记本或者云服务器都能跑。2. 系统整体设计与技术选型思路2.1 为什么要用爬虫 Spark Django Echarts这套组合我一开始其实纠结过要不要用Pandas直接分析——毕竟对于几十万行的数据量Pandas单机跑也不算慢。但这个项目的定位是大数据分析系统光有个分析脚本那是交不了差的。而且小说平台的数据量一旦放开爬取几百万行是常态单机Pandas处理起来既吃内存又慢用Spark做分布式计算从架构设计上更站得住脚。再来说Spark和Flink之间的选择。这是离线分析场景不是实时流处理Spark基于内存的批处理模型完全够用而且生态成熟、文档丰富、招聘市场认可度高。Django负责Web展示和服务端调度它的MTV模式把数据模型、路由控制和模板渲染分开后台管理界面甚至可以直接用Django自带的功能省去了很多CRUD的重复代码。Echarts是百度开源的一个纯JavaScript图表库配置简单、图表类型全、视觉效果也好看。相比自己用Canvas画图或者引入笨重的商业控件Echarts在前端工程里可以直接通过CDN引用几行配置就能画出一个交互式柱状图。缺点也有——它是前后端分离的经典搭配数据通过JSON接口传输所以需要自己想清楚接口格式。2.2 系统模块划分与数据流向整个系统分四个模块数据采集模块requests BeautifulSoup爬取小说基础信息和读者互动数据数据清洗与存储模块过滤无效数据、去重、格式统一存入MySQL和HDFS供Spark读取数据分析模块Spark读取数据按分类、字数、状态、推荐量等多维度做统计和挖掘结果可视化模块Django从MySQL读取分析结果通过Echarts渲染成图表浏览器端交互展示数据流向是单向的爬虫 → 数据文件/数据库 → Spark清洗计算 → 结果表 → Django读取 → JSON → Echarts渲染。3. 数据采集层实战requests爬虫的实现与细节3.1 爬虫采集的目标字段设计写爬虫之前先想清楚分析需要什么字段。我最终定了核心八件套小说书名、作者、分类、连载状态、总字数、收藏数、推荐票数、章节数外加一个爬取时间戳用于增量更新。这里有个设计细节要说一下收藏数和推荐票数这两个指标特别容易爬漏。不少小说网站的列表页只显示收藏数推荐票藏在详情页需要二次请求。所以在写解析逻辑时我把字段分成两批——列表页能拿到的归一批详情页补充归一批最后合并入库。如果只图省事拿列表页数据后面的Spark分析就会因为缺字段而少算好几个维度。3.2 请求策略与反爬应对早期我爬数据时吃过亏不加任何伪装几百个请求发过去对方服务器直接把我本机IP封了半小时。后来学乖了总结下来几件事必须做第一是头部伪装。User-Agent、Referer、Accept-Language这些字段一定要带上最好从真实的浏览器请求里复制。用一个常见的浏览器UA数组随机轮换比每请求换一个冷门UA更自然。第二是控制频率。requests本身发请求的速度极快不加限制一秒钟就能打几十个请求出去这种行为模式太明显了。我用了一个带随机的延时控制import time import random def polite_sleep(min_sec1.2, max_sec2.5): 控制请求频率避免被封 time.sleep(random.uniform(min_sec, max_sec))每次请求后调用这个函数让请求间隔在1.2到2.5秒之间随机波动。既不会让对方服务器压力过大也符合人类操作的行为节律。第三是错误处理。遇到超时、连接被重置、返回非200状态码的情况不能直接让程序崩溃。我写了一个重试机制三次失败后跳过当前页面并记录日志。别小看这个设计几万条数据的采集过程中网络抖动是必然事件。3.3 解析与数据清洗的细节BeautifulSoup解析HTML这一步本身不难真正的坑在于数据本身的脏。几个真实遇到过的问题字数字段网页显示12.3万字入库前必须转成整数123000连载状态有的是连载中、有的是已完结还有的标暂停更新需要做映射统一收藏数偶尔会出现空值数据量大时直接删除会浪费我用0填充并标记来源封面URL和内容简介里会混杂特殊字符JSON序列化时容易出问题清洗逻辑我用一个独立的函数模块处理每一步都做防御性判断宁可多写几个if也不让异常数据流到Spark层。清洗后的数据按天分文件存储文件名带日期标记这为后面的Spark增量分析留好了接口。4. Spark大数据分析模块核心逻辑与算子选择4.1 读取数据与环境配置Spark读取数据的方式有两种选型如果数据量不大几十万行直接读MySQL表最方便如果数据量大几百万行且持续增长把数据落到HDFS上读Parquet格式更高效。我实际项目里两个方案都做了兼容核心是读MySQL的分析逻辑用JDBC连接读HDFS的用Parquet格式。启动Spark之前的参数配置是重点在开发环境里我建议至少指定executor内存和并行度from pyspark.sql import SparkSession spark SparkSession.builder \ .appName(novel_analysis) \ .config(spark.executor.memory, 4g) \ .config(spark.driver.memory, 2g) \ .config(spark.sql.shuffle.partitions, 8) \ .getOrCreate()spark.sql.shuffle.partitions这个参数要特别留意默认值是200如果集群资源有限shuffle阶段会产生大量小任务导致资源耗尽。我在单机开发环境下调到8跑起来明显顺滑。4.2 分析维度设计与实现分析维度是Spark模块的核心我从实际业务出发设计了五类指标第一类分类热度排行。按小说分类分组统计每个分类下的作品总数、平均收藏数、平均字数。用DataFrame的groupBy加agg搞定from pyspark.sql import functions as F df spark.read.parquet(hdfs://path/to/novel_data) category_stats df.groupBy(category) \ .agg( F.count(book_name).alias(book_count), F.avg(collections).alias(avg_collect), F.avg(word_count).alias(avg_words) ) \ .orderBy(F.desc(book_count))这个维度解决的核心问题是编辑选分类方向时的数据支撑。比如数据显示玄幻分类虽然作品最多但平均收藏只有都市的四分之一那选书的方向重点就该偏向都市。第二类字数分布区间分析。把字数按区间分桶10万以下、10万-50万、50万-100万、100万-300万、300万以上统计各区间的作品占比和平均推荐票。这里的核心发现价值在于有没有存在一个黄金字数区间在这个区间内小说更容易获得高推荐票。如果数据显示100万-300万区间的平均推荐票显著高于其他区间那对作者写故事的长度规划是有直接指导意义的。用Spark的CASE WHEN做分桶df.withColumn(word_range, F.when(F.col(word_count) 100000, 10万以下) .when(F.col(word_count) 500000, 10-50万) .when(F.col(word_count) 1000000, 50-100万) .when(F.col(word_count) 3000000, 100-300万) .otherwise(300万以上) ) \ .groupBy(word_range) \ .agg(F.count(book_name).alias(book_count), F.avg(recommendation).alias(avg_rec)) \ .orderBy(word_range)第三类作者产出效率分析。按作者分组统计每个作者的作品数量、平均字数、平均收藏数重点看高产出作者和高收藏作者的重合度。这个维度的数据对于平台方来说可以直接指导签约策略。第四类收藏/推荐比分析。这是我自己加的一个很有趣的指标。计算每本书的推荐票数与收藏数的比值比值高说明读者看完后主动推荐的意愿强通常意味着内容质量不错或者话题性强。Spark里直接做列运算就行df.withColumn(rec_ratio, F.round(F.col(recommendation) / F.when(F.col(collections) 0, 1).otherwise(F.col(collections)), 2))第五类热度综合评分。对收藏数、推荐票数、字数、章节数做标准化后进行加权求和排出一个综合热度Top榜单。标准化的常用方法是Min-Max归一化min_max_scale df.select( F.min(collections).alias(min_c), F.max(collections).alias(max_c), F.min(recommendation).alias(min_r), F.max(recommendation).alias(max_r) ).collect()[0] df_scaled df.withColumn(c_scaled, F.round((F.col(collections) - min_max_scale[min_c]) / (min_max_scale[max_c] - min_max_scale[min_c] 1e-5), 4))4.3 分析结果的落库策略Spark计算完的DataFrame直接写入MySQL用df.write.jdbc()接口df.write.jdbc( urljdbc:mysql://localhost:3306/novel_db, tableanalysis_category_stats, modeoverwrite, properties{user: root, password: xxx, driver: com.mysql.cj.jdbc.Driver} )写库这里有个选择是overwrite还是append。我建议分表处理——全量统计表用overwrite因为每次计算都是全量重跑直接覆盖干净省事增量趋势表用append每次跑批追加当天快照做趋势图表用。分析后的结果给Django层调用Django只做读操作业务逻辑都在Spark层处理好这样Web层就非常轻——这也是一个很重要的分层思路重计算全部交给SparkWeb层永远别做算法逻辑。5. Django与Echarts可视化展示层从数据表到图表的完整链路5.1 Django项目结构与数据接口设计Django项目我用的标准结构核心是两个appanalyze负责数据展示接口dashboard负责后台管理。由于前后端分离Django不再需要渲染复杂的HTML模板只需要给前端提供干净的JSON接口前端通过Ajax去取数。服务器端视图里一个典型的接口写法长这样import json from django.http import JsonResponse def category_stats_api(request): 分类热度统计接口 # 从MySQL读取Spark分析结果表 data list(CategoryStats.objects.all().values(category, book_count, avg_collect, avg_words)) return JsonResponse({code: 0, data: data})Django自带的ORM帮了大忙Spark分析结果落库后Django这边直接查模型就能拿到数据完全不用自己拼SQL。然后前端拿到JSON数据后交给Echarts渲染。5.2 前端Echarts图表的选型与配置实践可视化图表的选型要跟着数据讲故事。这个项目里我做了一个组合看板核心看板区放四张图分类热度排行 –柱状图横向分类名做Y轴收藏数做X轴一眼看出哪类最受欢迎字数分布区间 –饼图展示各区间占比适合表达比例作者产出效率 –散点图X轴是作品数量Y轴是平均收藏数高价值作者一眼可见综合热度Top20榜单 –列表横向柱状图组合用一张横向长条图展示Echarts配置本身不复杂以分类热度柱状图为例fetch(/api/category-stats/) .then(response response.json()) .then(res { if (res.code ! 0) return; const data res.data; const categories data.map(item item.category); const bookCounts data.map(item item.book_count); const avgCollects data.map(item item.avg_collect); const chart echarts.init(document.getElementById(categoryChart)); chart.setOption({ title: { text: 小说分类作品数量与平均收藏 }, tooltip: { trigger: axis }, legend: { data: [作品数量, 平均收藏数] }, xAxis: { type: category, data: categories }, yAxis: [ { type: value, name: 作品数量 }, { type: value, name: 平均收藏数 } ], series: [ { name: 作品数量, type: bar, data: bookCounts }, { name: 平均收藏数, type: line, yAxisIndex: 1, data: avgCollects } ] }); });这个组合图的设计意图很明确数量多的分类不一定热度高用柱状图数量折线图均值双Y轴的形式把两个指标放在同一张图里做对比信息量比单指标柱状图大得多。5.3 页面布局与交互体验优化图表建好只是第一步整个看板布局才是真正影响使用体验的地方。我参考数据看板类产品的惯例采用了上总下分、左总右分的布局逻辑顶部放一个总览卡片条总作品数、总作者数、总字数、平均收藏数四个核心KPI数字左侧放分类热度柱状图和字数分布饼图右侧放作者产出散点图和综合Top榜中间区域放一个支持时间筛选的热点趋势图通过下拉框切换统计维度交互上有一个点特别值得说图表联动。点击分类柱状图中的某个分类列表和散点图会联动刷新为该分类的数据。这个功能不依赖任何框架前端全局维护一个当前选中分类的变量每次点击柱状图时触发一个自定义事件其余图表接收事件后重新请求新接口即可。这个交互设计在面试时讲出来很加分它体现的是整体数据串联意识而不是单张图的堆砌。6. 系统集成部署与常见问题排查实录6.1 环境配置与依赖版本管理的坑这个项目的依赖管理我一开始吃过亏——直接在全局Python环境里pip install了所有包结果某一天升级了requests库爬虫的解析逻辑莫名其妙开始报错排查了半天才发现是新版本库的API变动导致。后来老老实实用了虚拟环境加requirements.txt管理spark3.2.0 django4.0 requests2.25.0 beautifulsoup44.9.0 pymysql1.0.0一个版本问题就耽误三四天的事件让我养成习惯任何实验室阶段的项目第一件事就是建虚拟环境并固化合依赖的版本。别偷懒。另外要在Django配置里注意MySQL连接驱动。用PyMySQL时需要在__init__.py加上一句import pymysql pymysql.install_as_MySQLdb()如果不加这句Django默认找的是MySQLdb驱动直接报模块缺失的错误。6.2 爬虫采集实战中的三个经典事故事故一IP被封。前面讲过不加频控直接全速爬爬了不到十分钟就被封IP。解决方式就是那套延时重试的方案。事故二数据编码乱码。有的页面返回的编码不是UTF-8而是GBK或者GB2312直接用requests的.text属性解析出来的全是乱码。解决办法是拿到response后先检查encoding必要时用response.content手动解码resp requests.get(url, headersheaders) if resp.encoding.lower() ! utf-8: resp.encoding utf-8 html resp.text事故三重复数据爆炸。爬虫跑了两轮之后MySQL里同一个书名出现了好几条记录因为我没有在写入前做去重。后来加了按书名作者名的唯一性检查重复数据直接跳过不写。6.3 Spark运行时的常见报错与调优报错一内存不足OOM。处理大DataFrame时经常遇到java.lang.OutOfMemoryError。原因通常是executor内存不足或者缓存的数据太大。解决办法一是调大spark.executor.memory参数二是在转换过程中多用广播变量、少用不必要的数据缓存。另一个优化技巧是读完原始数据之后先做一次列裁剪只保留需要的字段再进后续的聚合计算。报错二类冲突。py4j.Py4JException最典型的场景是环境里存在多个Spark版本冲突。这个没有特别好的根治方法唯一有效的是在干净的虚拟环境里重新安装固定版本。报错三Driver与Executor之间的性能瓶颈。数据量一大Spark跑的明显变慢。我在分析日志里发现了问题所在——某个环节有个巨大的宽依赖shuffle数据量很大。优化方法是用repartition(col_name)重新分区或者先在子分区内做部分聚合再做全局聚合。这个思路叫预聚合能把shuffle的数据量降一个数量级。6.4 Django接口性能优化的经验Spark算完了Django接口如果出问题就是最后一步掉链子。我遇到过一个接口巨慢的情况——前端加载整个看板要5秒多排查发现是Django的ORM在循环查询每次循环都去MySQL请求一次导致N1查询问题。优化方法是使用select_related或prefetch_related一次性把关联数据取出来外加在MySQL里给常用查询字段建立索引。优化之后页面加载时间从5秒降到了1.2秒。前端页面开启缓存也是一个策略分析结果每天更新一次完全没有必要让用户每次打开页面都请求后端重算。我在Django里用cache_page装饰器把核心接口缓存1小时from django.views.decorators.cache import cache_page cache_page(60 * 60) def category_stats_api(request): # 业务逻辑保持不变7. 项目复盘与个人经验分享这个项目做完之后我最大的收获反而不是技术层面的——技术方案网上都有现成教程真正值钱的是在动手之前想清楚了多少问题。第一个经验是分析目标要前置。我是先定了要回答什么问题哪类小说值得推、多长字数的书最容易获得高口碑、作者的产出效率与热度是否正相关再倒推需要采集什么字段、需要做什么计算、图表需要怎么展示。如果反过来先把数爬了再想能分析什么很容易陷入采集了一堆没用的字段、分析时又发现关键字段缺失的尴尬。第二个经验是数据管道要留好扩展口。我一开始只设计了全量分析的流程后来需要看趋势图发现没有增量管道只能重新设计。建议从一开始就把数据表按时间分区给以后留好历史回溯的空间。第三个经验是关于工程化思维的。纯写分析代码和做一个能看的系统之间还有一道沟——错误处理、日志记录、参数配置分离、接口返回格式统一这些看着不炫却非常影响体验的细节才是从会写代码到能交付系统的分水岭。这个项目如果还有人愿意继续往下拓展我提示两个方向一是用Spark Streaming做实时热度排行二是引入NLP预训练模型做小说内容的标签自动抽取。当然那是另外一个坑深的故事了等哪天真的做完了再回来和你们分享。最后说一句私心话这种端到端的数据可视化项目做的过程确实不少次想放弃但当你打开系统首页看着图表数据哗啦啦铺开来的时候还是会有点小成就感的。祝各位动手顺利少踩我之前踩过的坑。