简介:一套基于网络爬虫技术的小说网数据采集、分析与可视化课程设计源码,专为需要完成期末大作业或课程设计的Python初学者打造,也可作为毕业设计前期探索的参考模板。项目完整覆盖爬虫调度、网页解析、数据清洗、结果存储与可视化展示等关键环节,逻辑清晰且便于二次拓展。压缩包共含94个文件,核心代码为11个Python脚本,辅以HTML模板、CSS样式、JavaScript脚本、页面截图与项目依赖文件,总大小仅4.13MB,整体轻量、目录规范、开箱即用。这套作品已获导师认可,并取得97分高分;目前已有3069人学习或下载。下载后无需修改即可运行,读者可重点研究其小说数据采集流程、词云图与Dashboard看板的设计思路,快速迁移到相似的数据分析项目中,无论是课程设计提交还是个人练手,都具备良好参考价值。 每年到课程设计季,总有人对着“用Python写一个爬虫项目”发呆,不知道从哪下手。这个zip里的内容其实是一条很经典的技术路线:爬虫采集小说网站数据,清洗后做分析,最后用可视化图表把结果展示出来。它覆盖了requests请求、页面解析、数据存储、pandas分析和pyecharts绘图这几大块,刚好把Python数据分析的主要环节串了一遍。不管你是要做课程设计,还是想拿一个练手项目巩固爬虫和数据分析技能,这套源码都有很高的参考价值。
我花了不少时间把整个项目跑通,也顺手整理了一份从零到一的操作笔记。下面这些内容是针对这套源码的拆解,包含技术选型的理由、关键节点的实现思路、我踩过的坑和具体的排查方法,希望能帮到正在做类似项目的你。
1. 项目整体设计与技术选型:为什么选爬虫+可视化
1.1 课程设计选“小说数据爬取”的现实考量
这几年我接触过不少课程设计选题,大部分人会纠结“做什么才能既体现工作量,又不容易翻车”。小说网站数据采集这个方向有几个天然优势:第一,数据量大且结构相对规整,每本小说都有分类、作者、字数、状态这些固定字段,爬下来之后能分析的东西很多;第二,目标网站不像电商平台那样有高强度反爬,对初学者友好;第三,主题轻松,小说是大众熟悉的东西,答辩时讲起来不费劲,老师也能快速理解你的逻辑。
这套题目的核心链路是:爬虫取数——数据清洗与入库——pandas统计——图表可视化。它能同时展示Python编程能力、网络请求处理能力和数据分析能力,在课程设计答辩里属于“性价比”很高的方向。
1.2 技术栈选型的取舍
很多人一上来就纠结:用Scrapy还是requests?用MySQL还是SQLite?用pyecharts还是matplotlib?
我的建议是:课程设计场景,别过度设计,够用就好。
- 数据采集:本项目用的是requests + BeautifulSoup,而不是Scrapy。原因是Scrapy的学习曲线和项目结构对一个单元课设来说偏重,而requests搭配BeautifulSoup写起来直观,调试也方便,出问题能很快定位。框架层面的东西放到以后工作中再接触完全来得及。
- 数据存储:选SQLite。它不需要单独安装数据库服务,一个文件就能搞定,配合Python自带的sqlite3模块,代码量很小。MySQL还得配置账户、权限,增加无谓的部署成本。
- 解析库:用lxml作为BeautifulSoup的解析引擎。相比Python内置的html.parser,lxml在速度上有明显优势。采集量到几千条的时候差距就出来了。
- 可视化:用的是pyecharts。它生成的是交互式HTML图表,鼠标悬停有数据提示,可以缩放平移,答辩演示时观感远比静态的matplotlib图表好。而且pyecharts的配置项比较语义化,掌握基本套路之后改样式很快。
- 数据分析:pandas + numpy,这没什么好说的,Python数据分析的事实标准。
这套组合的另一个好处是环境依赖少,一条pip install requests beautifulsoup4 lxml pandas pyecharts就能把依赖装齐,不用担心答辩现场环境配不起来。
2. 爬虫模块核心实现:从请求到落库
2.1 目标分析与请求策略
开始写代码之前,最先做的是打开目标小说网站的页面,按F12看Network面板,搞清楚三件事:网页是静态渲染还是动态加载、列表页URL有什么规律、需要的字段在哪些标签里。
大部分中小型小说网站都是服务端渲染,小说信息直接写在HTML里,这给爬虫省了很多事。记得我最初调试时用浏览器无痕模式打开目标网站首页,点开“全部小说”分类列表,观察URL变化,发现翻页规律通常是类似page=1、page=2这样的query参数,直接把数字改掉就能翻页。这类站不太需要模拟复杂交互,requests就能搞定。
请求头伪装是必须做的一步。代码里的headers至少要有User-Agent和Referer,否则服务器返回的很容易是403或者跳转验证页。
import requests headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer': 'https://example-novel-site.com/', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', } def fetch_page(url): resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() return resp请求频率控制是一个新手容易忽略的点。不加延时地疯狂发请求,轻则IP被封,重则影响目标站正常服务。合理做法是每次请求之间随机停顿1到3秒,模拟人工浏览节奏:
import random import time def delay(): time.sleep(random.uniform(1, 3))2.2 页面解析与字段提取
页面拿到手之后,用BeautifulSoup定位数据节点。目标字段常见的包括:书名、作者、分类、连载状态、字数、最近更新时间、封面图片地址等。
这里最稳妥的做法是先在浏览器里找到对应字段的HTML结构,再写选择器。我个人的习惯是优先用select_one()配合CSS选择器,因为CSS选择器的可读性比正则表达式好,而且BeautifulSoup对CSS选择器支持很成熟。
from bs4 import BeautifulSoup soup = BeautifulSoup(resp.text, 'lxml') book_items = soup.select('div.book-item') for item in book_items: title_tag = item.select_one('h2 a') author_tag = item.select_one('p.author a') cate_tag = item.select_one('p.category a') status_tag = item.select_one('p.status') words_tag = item.select_one('p.words span') if not title_tag or not author_tag: continue row = { 'title': title_tag.get_text(strip=True), 'author': author_tag.get_text(strip=True), 'category': cate_tag.get_text(strip=True) if cate_tag else None, 'status': status_tag.get_text(strip=True) if status_tag else None, 'words': parse_words(words_tag.get_text(strip=True) if words_tag else '0'), }注意几个细节:
get_text(strip=True)会把标签内部的空白字符去掉,避免混入\n和空格。- 字段缺失时不能直接报错,要用
if not tag: continue或者try/except兜底,防止一条数据解析异常拖垮整个采集流程。 - 字数是多少万字,提取出来的是带“万”的字符串,需要写一个
parse_words()函数做转换,统一存成整数(单位:万字)。没有“万”字的就按实际数值处理。
处理字段类型这件事看着小,实际很影响后续分析。我当时一开始偷懒,字数直接存原始字符串,结果后面画字数分布图时全得重新清洗,白费了不少功夫。这一步宁可前面做干净,也不要指望后面补救。
2.3 数据入库与去重
数据库表结构设计要贴合分析需求。我之前用到的建表SQL大致长这样:
import sqlite3 conn = sqlite3.connect('novels.db') cur = conn.cursor() cur.execute(''' CREATE TABLE IF NOT EXISTS novels ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, author TEXT, category TEXT, status TEXT, words INTEGER, clicks INTEGER, score REAL, update_time TEXT, crawled_at TEXT DEFAULT CURRENT_TIMESTAMP, UNIQUE(title, author) ) ''') conn.commit()给title + author加了唯一约束,为的是重复爬取时不产生重复数据。插入数据时用INSERT OR IGNORE:
insert_sql = ''' INSERT OR IGNORE INTO novels(title, author, category, status, words, clicks, score, update_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''' data = [ row['title'], row['author'], row['category'], row['status'], row['words'], row['clicks'], row['score'], row['update_time'] ] cur.execute(insert_sql, data) conn.commit()批量插入时建议攒一批再commit,比如每50条提交一次,速度比一条一提交快好几个量级。第一次写爬虫时我没注意这个细节,几百条数据入库慢得像蜗牛,后来改成批量提交顺畅多了。
3. 数据分析与可视化:从数据表到图表
3.1 分析维度怎么定
数据入库只是第一步,课程设计能否拿高分,很大程度取决于后面分析和可视化部分有没有自己的想法。这套源码里,我从四个维度做了分析:
- 小说分类分布:看看各分类下的小说数量占多少,能反映平台的内容结构。
- 字数分布:衡量不同分类的小说的平均篇幅,了解平台主推的长短篇方向。
- 评分分布:结合平均分的计算公式,分析平台小说的整体质量水平。
- 作者作品数排行:找找哪些作者在平台上最活跃、作品最多。
此外,还可以按连载状态分组,统计完结和连载中的比例;或者分析点击量和评分之间的关系。分析维度不用贪多,四五个有代表性的就够撑起整个项目了。
pandas统计的核心代码并不复杂:
import pandas as pd df = pd.read_sql_query("SELECT * FROM novels", conn) # 分类数量统计 category_count = df.groupby('category')['title'].count().sort_values(ascending=False) # 字数分布分箱 df['words_bin'] = pd.cut( df['words'], bins=[0, 50, 100, 200, 500, 1000], labels=['0-50万字', '50-100万字', '100-200万字', '200-500万字', '500万字以上'] ) words_dist = df.groupby('words_bin', observed=False).size()3.2 可视化图表选型
pyecharts里我主要用了四类图:
- 柱状图:展示各分类的小说数量Top10、作者作品数Top10,直观又简单。
- 饼图/环形图:展示小说分类占比,环形图观感更好,配置项也不复杂。
- 散点图:看字数与评分的关系,可以帮助发现“字数越多评分越低”之类的有趣结论。
- 词云图:把所有书名切词后生成词云,视觉冲击力强,答辩时很容易引起关注。
核心图表代码示例,以柱状图为例:
from pyecharts.charts import Bar from pyecharts import options as opts bar = ( Bar() .add_xaxis(category_count.index[:10].tolist()) .add_yaxis('小说数量', category_count.values[:10].tolist()) .set_global_opts( title_opts=opts.TitleOpts(title='小说分类数量Top10'), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=15)), ) ) bar.render('category_bar.html')散点图的加入是我后来觉得最出彩的一步,因为纯柱状图谁都会画,但“字数—评分”的散点分布能体现出分析深度。当散点图呈现明显聚集或负相关趋势时,答辩老师通常会追着问一句“你从图里得出什么结论”,这时候已经有场景可讲了。
词云图需要提前做分词,这里用到的库是jieba:
import jieba from pyecharts.charts import WordCloud book_titles = ' '.join(df['title'].tolist()) seg_list = jieba.cut(book_titles, cut_all=False) words = [w for w in seg_list if len(w) > 1 and w not in stopwords] word_freq = pd.Series(words).value_counts().to_dict() wc = ( WordCloud() .add(series_name='书名热词', data_pair=list(word_freq.items()), word_size_range=[20, 80]) .set_global_opts(title_opts=opts.TitleOpts(title='小说书名热词Top词云')) ) wc.render('wordcloud.html')3.3 页面布局整合
自己开发中后期,一定不要只输出一堆孤立HTML文件,最好做一个总览页面,把所有图表组合在一张页面上展示。pyecharts通过Page组件可以轻松把多个图表合到一起:
from pyecharts.charts import Page page = Page(layout=Page.SimplePageLayout) page.add(bar, pie, scatter, wordcloud) page.render('analysis_report.html')一个带下拉滚动效果的总览报告,在答辩演示时比一个个开HTML文件体面得多。
4. 踩坑实录与问题排查
4.1 请求被拒、编码乱码与数据缺失
采集过程中最常遇到的三个问题,这里直接说结论。
请求被拒:如果返回状态码是403或者页面内容变成了“访问过于频繁”之类的提示,基本是请求头不够或者频率太高。优先检查有没有带完整的User-Agent,其次是确认有没有加随机延时。我在调试时习惯先打印状态码和响应内容的前200个字符,快速判断是正常页面还是拦截页。
中文乱码:有的网站的编码是gbk或者gb2312,直接用resp.text会解析成乱码。解决办法是结合响应头里的charset设置编码:
resp.encoding = resp.apparent_encodingapparent_encoding是根据内容自动推断的编码格式,拿到乱码时优先用这一行代码补救。
字段缺失:有些页面没有评分或者没有点击量,这很正常。解析时要用条件判断兜底,并在入库前统一做空值填充,比如评分缺失就填0,点击量缺失就填-1(方便后续过滤)。
4.2 页面结构变化、入库变慢与图表中文显示
页面结构变化:做爬虫项目最怕的就是爬到一半目标网站改版,选择器全部失效。这个问题在课程设计期间大概率不会遇到,但配套的排查方法还是要会:解析失败时不要慌,用浏览器重新打开目标页,F12里定位新的结构,替换选择器即可。最好提前把解析逻辑封装成函数,改起来只动一个地方。
入库变慢:如果采集量上万条,逐条commit会非常慢。另外,不要反复CREATE TABLE IF NOT EXISTS和CONNECT,连接建立一次,循环里复用,性能差别很大。我第一次写的时候把连接放在了循环里面,结果采集500条数据花了快10分钟,调出来之后秒级完成。
图表中文乱码:pyecharts在网页上一般不会乱码,但如果用matplotlib画静态图,需要额外设置中文字体。建议优先选pyecharts,省掉字体配置这件事。实在要用matplotlib的话,可以这样设置:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei'] plt.rcParams['axes.unicode_minus'] = False4.3 关于数据量与结果稳定性的经验
课程设计项目最容易出现的一个问题是采集数据量太少,统计结果没有说服力。我建议至少采集2000条以上小说数据再做分析,图表的分布规律才会明显。如果目标网站的列表页总数不够,可以换成多个分类入口分别采集,最后合并去重。
另外,爬虫运行期间要留意网站是否正常响应,不要长时间高频请求。在我的源码里加了一个进度条和异常重试机制,采集报错时会自动重试最多3次,还跳到下一条继续处理。这个小细节比较加好感,答辩时如果老师问到鲁棒性,可以直接拿出来说。
5. 最后再说点实操层面的体会
跑通这套项目之后,有一点心得想分享给正在做类似事情的朋友。
爬虫课程设计做得好不好,关键不在代码量多少,而在链路是否完整、分析是否有观点。很多同学写爬虫只写到“爬下来存入CSV”就停了,这其实是完成了一半。加上pandas统计和pyecharts可视化之后,整个项目才算闭环,也才真正体现“数据分析”的能力。代码架构上不用搞得太复杂,函数化拆分好,全局变量控制好,再配上简单的异常处理,就已经超过大多数课程设计的水准了。
如果你时间充裕,还可以给项目做一个Streamlit演示界面,把可视化结果直接嵌入页面,或者增加一个按关键词搜索小说的小功能。这些扩展方向都不难,但对最终展示效果的提升非常明显。希望这篇拆解能给你提供一些参考,动手做一遍比看十遍攻略都管用,加油。
本文还有配套的精品资源,点击获取