小说网站数据采集与可视化分析实战项目(含爬虫+Flask+PyEcharts)

小说网站数据采集与可视化分析实战项目(含爬虫+Flask+PyEcharts) 本文还有配套的精品资源点击获取简介直接可运行的小说类网站数据抓取与分析项目用Python实现全流程从目标小说站点自动提取书名、作者、分类、更新时间、章节列表等结构化信息支持数据清洗后存入本地文件或内存内置Flask轻量后端app.py配合HTML前端页面index.html、dashboard-1.html、wordCloud.html等实现交互展示通过PyEcharts生成阅读热度趋势图、题材分布饼图、高频词云图等多种可视化图表已打包pyecharts35-1.7.1 wheel安装包和完整依赖列表requirements.txt适配Python 3.7环境开箱即用项目结构清晰包含templates模板目录、static静态资源CSS/JS/字体/图片、resourse原始数据存储、test测试用例及IDEA工程配置高校计算机专业学生可用于课程设计、期末大作业或毕业设计参考实际交付成绩97分。1. 项目概述这不是一个“玩具项目”而是一套能真实跑通、能交差、能讲清楚原理的课程设计闭环你是不是也经历过这样的时刻老师布置课程设计题目是“基于Python的小说网站数据分析系统”要求包含爬虫、后端、前端和可视化——但翻遍B站和CSDN要么是只爬不展示的半成品要么是用假数据硬凑的“演示系统”一改网址就报错一换小说站就崩盘我带过三届计算机专业本科生做课程设计每年都有至少15%的同学卡在“数据拿不到”或“图表画不出来”上最后熬夜拼凑PPT答辩时被问一句“你这个热度趋势图的数据源在哪”就哑火。这个项目就是我从2021年带学生做《Web开发与数据处理》课设时手把手打磨出来的实战模板。它不是教你怎么写requests.get()而是告诉你当目标网站加了反爬策略你该先检查哪三个HTTP头当BeautifulSoup解析失败为什么lxml解析器比html.parser更稳当PyEcharts生成的词云图中文乱码问题根本不在字体路径而在WordCloud组件初始化时少传了一个font_family参数。关键词里写的“Python爬虫、小说数据分析、PyEcharts可视化、Flask后端、课程设计”每一个都不是虚词——爬虫模块spider目录真正在抓取起点中文网和纵横中文网两个主流站点的实时书库Flask后端app.py不是只返回JSON而是完整实现了路由分发、静态资源托管、模板渲染三层逻辑PyEcharts图表不是截图贴进HTML而是通过render_embed()注入到Jinja2模板中支持动态刷新所有依赖都打包进pyecharts35-1.7.1-py3-none-any.whl连pip install命令都帮你写好了。它面向的不是“想学Python”的小白而是“明天就要交代码报告答辩PPT”的大三学生。你不需要懂分布式调度但必须知道怎么用time.sleep(1)规避请求频率限制你不需要精通D3.js但得明白Bar().add_xaxis().add_yaxis()背后的数据结构是怎样的嵌套列表。这个项目跑起来之后你不仅能向导师演示一个可交互的仪表盘更能清晰回答“数据从哪来清洗了什么图表怎么算出来的为什么选这个库而不是那个”——这才是课程设计该有的样子。2. 整体架构设计与技术选型逻辑拆解2.1 为什么放弃Scrapy坚持手写RequestsBeautifulSoup组合很多同学第一反应是“爬虫就得用Scrapy”但在这个课程设计场景下Scrapy反而成了负累。我试过用Scrapy重写本项目的爬虫模块结果发现光是配置settings.py里的DOWNLOAD_DELAY、RANDOMIZE_DOWNLOAD_DELAY、ROBOTSTXT_OBEY就花了学生4小时调试Spider类时yield scrapy.Request()的异步回调机制让初学者完全摸不着头脑更关键的是Scrapy默认把数据导出为JSON/CSV而本项目需要将清洗后的数据直接喂给Flask内存缓存或本地文件中间还要插入章节更新时间的格式标准化如“2024-03-15 18:22”统一转为datetime对象再序列化Scrapy的Pipeline机制在这里显得冗余且难调试。反观requests BeautifulSoup组合requests.get(url, headersHEADERS)一行就能发起请求soup.find_all(div, class_book-item)直观对应网页DOM结构错误堆栈直接指向某行代码学生debug时能一眼看到是find()没找到元素还是text.strip()遇到NoneType。更重要的是我们封装了一个BaseSpider基类位于spider/base_spider.py里面预置了通用的请求头、超时设置、重试逻辑HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en-US;q0.8,en;q0.7, Accept-Encoding: gzip, deflate, Connection: keep-alive, }这个HEADERS字典不是随便抄的其中Accept-Language: zh-CN是绕过某些小说站地域重定向的关键Connection: keep-alive则保证了多页请求复用TCP连接避免频繁握手拖慢速度。而BaseSpider的fetch_page()方法内置了三次重试指数退避def fetch_page(self, url, timeout10): for attempt in range(3): try: resp requests.get(url, headersself.headers, timeouttimeout) resp.raise_for_status() return resp.text except requests.exceptions.RequestException as e: if attempt 2: raise e time.sleep(2 ** attempt) # 第一次等1s第二次等2s第三次等4s return None这种设计让学生能快速理解“网络请求不稳定”这个基本事实并学会用工程化手段应对而不是靠try...except pass糊弄过去。2.2 Flask为何只用原生路由不引入Flask-SQLAlchemy或Flask-Login课程设计的核心目标是“展示数据处理全流程”不是“构建生产级Web应用”。引入SQLAlchemy意味着要额外学习ORM映射、数据库迁移、会话管理引入Flask-Login则要处理用户认证、密码哈希、session存储——这些和“小说数据分析”主题毫无关系只会分散精力。我们的app.py只有137行却完整覆盖了所有必要功能/根路由返回首页index.html/dashboard返回仪表盘dashboard-1.html/wordcloud返回词云页/api/books提供JSON接口供前端AJAX调用。最关键的是它用最朴素的方式解决了数据共享问题全局变量BOOKS_CACHE []配合app.before_first_request装饰器在首次访问时自动触发爬虫执行并缓存结果BOOKS_CACHE [] app.before_first_request def init_cache(): global BOOKS_CACHE if not BOOKS_CACHE: spider QidianSpider() # 起点中文网爬虫实例 BOOKS_CACHE spider.crawl_books(limit50) # 抓取前50本热门书 print(f[INFO] 初始化缓存完成共加载 {len(BOOKS_CACHE)} 条数据)这里有个重要细节app.before_first_request只在第一个请求时执行避免每次刷新都重新爬取。而BOOKS_CACHE是列表而非字典因为后续可视化需要按顺序展示如热度趋势图的时间轴列表天然保持插入顺序。有学生曾问我“用Redis不是更专业吗”我的回答是课程设计评分标准里没有“是否用了Redis”这一项但有“能否清晰解释数据流向”。用全局变量学生能在app.py第12行看到BOOKS_CACHE []在第45行看到return jsonify(BOOKS_CACHE)在dashboard-1.html里看到{{ books|tojson }}整个链条一目了然。专业不等于复杂而是精准匹配需求。2.3 PyEcharts版本锁定为35-1.7.1而非最新版背后的兼容性深意PyEcharts官网推荐使用pip install pyecharts但最新版当前是2.0已彻底重构APIBar().add()变成Bar().add_series()render()方法被弃用改为render_notebook()或render_embed()。而本项目配套的pyecharts35-1.7.1-py3-none-any.whl是专为Python 3.7定制的旧版二进制包。为什么这么做因为高校机房和学生笔记本普遍安装的是Anaconda3-2020.07自带Python 3.7.6而新版PyEcharts要求Python ≥3.8。更重要的是1.7.1版的WordCloud组件对中文支持更成熟——它的add()方法可以直接接收[(word, freq), ...]格式的元组列表无需像新版那样先构造WordCloudItem对象。我们在spider/data_processor.py里清洗完小说标题高频词后生成的是标准的list[tuple[str, int]]# 清洗后得到的高频词数据结构示例 top_words [ (主角, 127), (修炼, 98), (系统, 85), (穿越, 76), (重生, 63) ]这个结构能直接喂给WordCloud().add(, top_words, word_size_range[20, 100])而新版需要from pyecharts.options import WordCloudItem items [WordCloudItem(namew, valuef) for w, f in top_words] wc.add(, items, word_size_range[20, 100])多一层封装就多一分出错可能。课程设计不是技术选型大赛而是要在有限时间内交付一个稳定运行的系统。所以requirements.txt里明确写着# 注意必须使用此wheel包新版pyecharts不兼容Python 3.7 # pip install pyecharts35-1.7.1-py3-none-any.whl并在README.md中强调“若执行pip install pyecharts导致报错请立即卸载并安装提供的wheel包”。2.4 目录结构设计每个文件夹名都在传递工程思维项目目录不是随意命名的每个名字都在暗示其职责边界spider/只放爬虫相关代码base_spider.py定义通用逻辑qidian_spider.py和zongheng_spider.py分别实现起点和纵横的特化爬取。学生修改某个网站逻辑时只需动对应文件不会污染其他模块。resourse/存放原始抓取的HTML快照qidian_raw_20240315.html和清洗后的JSONbooks_cleaned.json。这是课程设计“可验证性”的关键——导师抽查时可以对比resourse/里的原始HTML和spider/里的解析代码确认数据提取逻辑是否正确。templates/纯前端页面不包含任何Python代码。index.html是入口页dashboard-1.html是核心仪表盘wordCloud.html专注词云展示。所有页面共享base.html模板通过{% block content %}继承避免重复写导航栏和CSS引入。static/严格按子目录划分——css/放样式表js/放前端交互脚本如chart_loader.js负责动态加载PyEcharts生成的图表font/专门放simhei.ttf黑体解决中文显示问题image/放项目Logo和占位图。这种结构让学生明白“静态资源不是随便扔一个文件夹就行分类管理是工程基本功”。test/包含test_spider.py和test_processor.py用unittest框架编写。例如test_spider.py里有一个测试用例专门验证当起点中文网某本书的作者字段为空时爬虫是否返回未知作者而非抛出异常def test_author_empty_handling(self): # 模拟一段作者字段为空的HTML片段 html div classauthor作者span/span/div soup BeautifulSoup(html, lxml) author soup.select_one(.author span).get_text(stripTrue) self.assertEqual(author, ) # 原始提取结果为空 # 经过data_processor.py的clean_author()处理后应为未知作者 from spider.data_processor import clean_author self.assertEqual(clean_author(author), 未知作者)这个测试用例的存在让学生第一次体会到“防御性编程”的意义——不是代码能跑就行而是要考虑所有边界情况。3. 核心模块详解与实操要点3.1 爬虫模块如何让代码在真实网站上“活下来”爬虫模块位于spider/目录核心文件是qidian_spider.py。它不是简单地requests.get()然后soup.find()而是构建了一套应对小说网站典型反爬策略的“生存机制”。第一关User-Agent轮换与Referer伪造起点中文网会对非常规UA如python-requests/2.28.1返回403。我们的解决方案不是固定一个UA而是在BaseSpider中维护一个UA池USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 ] def get_random_ua(self): return random.choice(self.USER_AGENTS)每次请求前动态设置headers[User-Agent] self.get_random_ua()。同时对于详情页请求如https://book.qidian.com/info/1010749701必须带上Referer头否则服务器会拒绝响应。我们在QidianSpider.crawl_chapter_list()方法中显式设置chapter_url fhttps://book.qidian.com/info/{book_id} headers self.headers.copy() headers[Referer] fhttps://www.qidian.com/rank/yuepiao?chn{channel_id} resp requests.get(chapter_url, headersheaders, timeout10)这里的Referer值不是随便写的而是从榜单页URL中提取的频道IDchn21代表玄幻频道模拟用户从榜单点击进入详情页的真实行为。第二关动态章节列表的解析技巧起点中文网的章节列表是JavaScript动态渲染的requests拿到的HTML里只有占位符。但我们发现其API接口是公开的https://book.qidian.com/ajax/book/category?bookId1010749701。这个接口返回JSON格式的章节数据且无需登录或Cookie。QidianSpider直接调用此APIdef crawl_chapter_list(self, book_id): api_url fhttps://book.qidian.com/ajax/book/category?bookId{book_id} try: resp requests.get(api_url, headersself.headers, timeout10) data resp.json() if data[code] 200: chapters [] for item in data[data][episodelist]: chapters.append({ title: item[cname], update_time: item[uTime], # 时间戳格式1710525600 is_vip: item[vip] }) return chapters except Exception as e: print(f[ERROR] 解析章节列表失败 {book_id}: {e}) return []这里的关键洞察是不要跟JS渲染死磕去找它背后的真实数据源。学生在调试时打开浏览器开发者工具的Network标签页筛选XHR请求很容易就能定位到这个API。这教会他们一个底层思维现代网站的“页面”只是数据的皮肤真正的数据往往藏在API里。第三关数据清洗的“脏数据容忍度”设计小说网站的数据质量极差作者名可能是“作者辰东”、“辰东著”、“辰东著”甚至空字符串更新时间可能是“昨天”、“2小时前”、“2024-03-15 18:22:33”分类标签可能是“玄幻·东方玄幻”、“玄幻 / 东方玄幻”、“玄幻|东方玄幻”。spider/data_processor.py里的清洗函数不是追求“完美标准化”而是建立“最小可用标准”def clean_author(raw_author): 清洗作者字段返回标准化字符串 if not raw_author or raw_author.strip() in [, 作者, 著, 著]: return 未知作者 # 移除常见前缀和符号 author re.sub(r[著\(\)\[\]\{\}【】、。、], , raw_author) author re.sub(r\s, , author).strip() return author[:20] # 截断过长作者名防止前端溢出 def clean_update_time(raw_time): 将各种格式的更新时间统一转为YYYY-MM-DD HH:MM if not raw_time: return 1970-01-01 00:00 # 处理相对时间昨天 - 昨天日期 if 昨天 in raw_time: yesterday datetime.now() - timedelta(days1) return yesterday.strftime(%Y-%m-%d) 00:00 # 处理时间戳10位整数 if raw_time.isdigit() and len(raw_time) 10: return datetime.fromtimestamp(int(raw_time)).strftime(%Y-%m-%d %H:%M) # 处理标准格式2024-03-15 18:22:33 - 2024-03-15 18:22 try: dt datetime.strptime(raw_time, %Y-%m-%d %H:%M:%S) return dt.strftime(%Y-%m-%d %H:%M) except ValueError: pass return 1970-01-01 00:00 # 默认兜底时间注意clean_author()里的author[:20]——这不是偷懒而是工程权衡。作者名超过20个字符的极少如“风凌天下著”而前端表格列宽有限强行保留会导致UI错乱。课程设计评分中“界面美观度”是明确打分项这种务实的截断比“理论上完整”更重要。3.2 Flask后端轻量但不失健壮的服务层app.py是整个系统的粘合剂它不追求高并发但必须保证在单机环境下稳定服务。其核心设计原则是一切以可调试性为先。路由设计的“语义化”与“可测试性”我们定义了四个核心路由路由方法功能可测试性设计/GET返回index.html首页首页包含一个“手动触发爬虫”按钮点击后AJAX调用/api/crawl方便学生随时重跑爬虫验证效果/dashboardGET返回dashboard-1.html仪表盘页面底部嵌入scriptconsole.log(Dashboard loaded);/script学生F12就能确认页面是否正确加载/wordcloudGET返回wordCloud.html词云页独立页面避免与其他图表JS冲突词云加载失败时页面仍可访问/api/booksGET返回JSON格式的图书数据接口地址直接在浏览器访问如http://127.0.0.1:5000/api/books学生能直观看到返回的JSON结构无需Postman其中/api/crawl路由是调试利器app.route(/api/crawl, methods[POST]) def trigger_crawl(): global BOOKS_CACHE try: # 强制重新爬取 spider QidianSpider() BOOKS_CACHE spider.crawl_books(limit30) return jsonify({status: success, count: len(BOOKS_CACHE)}) except Exception as e: return jsonify({status: error, message: str(e)}), 500学生在开发时可以反复点击首页的“刷新数据”按钮观察控制台输出确认爬虫是否真的在运行、数据是否更新。这种即时反馈比看日志文件高效十倍。静态资源托管的“零配置”实践Flask默认不自动托管静态文件但本项目通过app.py里的两行代码实现“开箱即用”# 在app.py顶部添加 import os STATIC_FOLDER os.path.join(os.path.dirname(__file__), static) # 创建Flask应用时指定 app Flask(__name__, static_folderSTATIC_FOLDER, template_foldertemplates)这样所有放在static/css/下的CSS文件都可以用url_for(static, filenamecss/style.css)引用无需配置Nginx或Apache。学生把项目文件夹拷贝到另一台电脑只要pip install -r requirements.txt然后python app.py就能立刻访问http://127.0.0.1:5000没有任何环境差异。模板渲染的“安全边界”意识dashboard-1.html里渲染热度趋势图的代码是div idchart-hot stylewidth: 100%; height: 400px;/div script // PyEcharts生成的JavaScript代码直接嵌入 {{ chart_hot | safe }} /script注意| safe过滤器——它告诉Jinja2不要对chart_hot变量进行HTML转义。这是因为PyEcharts的render_embed()返回的是包含script标签的完整HTML字符串。如果不加| safe浏览器会把script当成普通文本显示出来图表自然无法渲染。这个细节是学生在调试时最容易卡住的点之一。我们在app.py的/dashboard路由里特意把chart_hot变量的生成过程写得非常清晰app.route(/dashboard) def dashboard(): # 从缓存获取数据 books BOOKS_CACHE # 构建热度趋势图数据 dates sorted(set([b[update_time][:10] for b in books])) # 提取唯一日期 hot_counts [sum(1 for b in books if b[update_time].startswith(d)) for d in dates] # 创建PyEcharts Bar图 bar Bar(init_optsopts.InitOpts(width100%, height400px)) bar.add_xaxis(dates) bar.add_yaxis(日更新数量, hot_counts) bar.set_global_opts( title_optsopts.TitleOpts(title小说日更新热度趋势), xaxis_optsopts.AxisOpts(name日期), yaxis_optsopts.AxisOpts(name更新数量), datazoom_opts[opts.DataZoomOpts()], # 添加数据缩放便于查看长周期趋势 ) # 渲染为嵌入式JavaScript chart_hot bar.render_embed() return render_template(dashboard-1.html, chart_hotchart_hot)这段代码展示了完整的“数据→图表→渲染”链条学生可以逐行理解dates是怎么从BOOKS_CACHE里提取的hot_counts是如何统计的bar.add_xaxis()的参数类型是什么。这种透明性是课程设计教学价值的核心。3.3 PyEcharts可视化不只是画图更是数据叙事可视化不是把数据塞进图表就完事而是要用图表讲好“小说市场”的故事。本项目生成的三类图表各自承担不同的叙事角色。阅读热度趋势图Bar图回答“什么时候更新最活跃”数据源是BOOKS_CACHE中每本书的update_time字段。我们不是简单统计“每天有多少本书更新”而是做了时间窗口聚合将update_time截取到日2024-03-15 18:22→2024-03-15然后统计每个日期出现的频次。这样得到的柱状图横轴是连续日期纵轴是当日更新小说数量。关键参数是datazoom_opts[opts.DataZoomOpts()]它在图表底部添加了缩放滑块学生可以拖动查看任意时间段的趋势而不是被固定视图限制。这教会他们可视化要服务于探索而不是仅仅呈现。题材分布饼图Pie图回答“什么类型的小说最多”题材数据来自category字段但原始数据是“玄幻·东方玄幻”、“都市·异术超能”这样的复合标签。我们在data_processor.py里做了一级分类归约def get_main_category(raw_category): 从复合分类中提取主分类 if not raw_category: return 其他 # 按常见分隔符分割取第一个 for sep in [·, /, |, 、]: if sep in raw_category: return raw_category.split(sep)[0].strip() return raw_category.strip()[:10] # 默认取前10字作为主分类这样“玄幻·东方玄幻”变成“玄幻”“都市·异术超能”变成“都市”。饼图代码中我们还设置了radius[40%, 70%]让饼图呈现环形效果视觉上更现代label_optsopts.LabelOpts(formatter{b}: {d}%)让标签显示“分类名: 百分比”比默认的“分类名”更信息丰富。词云图WordCloud回答“读者最关心什么”词云的数据源不是小说简介而是所有小说标题的高频词。我们用jieba分词但做了关键优化排除停用词stopwords.txt里预置了“的”、“了”、“和”、“与”等无意义词并加入小说领域专用词典dict.txt里添加了“主角”、“系统”、“穿越”、“重生”、“修炼”等。WordCloud组件的word_gap参数设为10避免词语过于拥挤shapepentagon五边形让词云更有设计感区别于千篇一律的圆形。最重要的是我们没有用默认的随机颜色而是指定了colorrandom让词云色彩更鲜活——课程设计答辩时一个色彩明快的词云图比灰扑扑的柱状图更能抓住评委眼球。4. 实操过程与完整部署指南4.1 从零开始的完整操作流程含踩坑记录假设你刚下载完项目压缩包接下来是手把手带你跑通的每一步。我以Windows系统为例Mac/Linux命令略有不同已在README.md中说明全程记录真实操作和可能遇到的坑。第一步环境准备5分钟1. 确认Python版本打开命令提示符输入python --version必须是Python 3.7.x。如果不是请下载Python 3.7.9并勾选“Add Python to PATH”。2. 创建虚拟环境强烈推荐避免包冲突bash cd /path/to/your/project python -m venv venv venv\Scripts\activate.bat # Windows激活 # Mac/Linux用source venv/bin/activate提示如果提示venv\Scripts\activate.bat is not recognized请在PowerShell中执行Set-ExecutionPolicy RemoteSigned -Scope CurrentUser然后重试。第二步依赖安装3分钟1. 进入项目根目录确保看到requirements.txt和pyecharts35-1.7.1-py3-none-any.whl文件。2. 执行安装命令注意顺序bash pip install -r requirements.txt pip install pyecharts35-1.7.1-py3-none-any.whl注意必须先装requirements.txt再装wheel包。因为requirements.txt里包含了requests,beautifulsoup4,jieba等基础依赖而wheel包是PyEcharts的特定版本。如果顺序颠倒pip可能会降级已安装的包导致兼容性问题。第三步首次运行与数据采集10-15分钟1. 启动Flask服务bash python app.py此时控制台会输出* Serving Flask app app.py * Environment: production WARNING: This is a development server. Do not use it in a production deployment. * Debug mode: off * Running on http://127.0.0.1:50002. 打开浏览器访问http://127.0.0.1:5000。你会看到一个简洁的首页顶部有导航栏中间是“小说数据分析系统”标题下方有一个蓝色按钮“点击此处启动数据采集”。3. 点击按钮页面会显示“正在采集数据…”大约等待10秒后自动跳转到仪表盘页/dashboard。此时控制台会打印[INFO] 初始化缓存完成共加载 50 条数据踩坑记录如果点击按钮后页面卡在“正在采集…”且无响应大概率是网络问题。请检查① 是否开了代理软件关闭② 防火墙是否阻止了Python进程临时禁用③ 尝试在app.py中将QidianSpider.crawl_books(limit50)的limit参数改为10先小规模测试。第四步验证可视化效果2分钟1. 在仪表盘页你应该能看到- 顶部是“小说日更新热度趋势”柱状图横轴显示最近7天的日期纵轴是更新数量- 中间是“题材分布”环形饼图显示“玄幻”、“都市”、“仙侠”等占比- 底部是“高频词云”中心是“主角”周围环绕“系统”、“穿越”、“重生”等词。2. 点击导航栏的“词云分析”进入/wordcloud页面确认词云图正常显示且中文不乱码。注意如果词云显示方框□□□说明simhei.ttf字体未生效。请检查static/font/目录下是否有simhei.ttf文件并确认wordCloud.html中WordCloud组件的font_family参数是否为simhei代码在app.py的/wordcloud路由里。4.2 数据存储与持久化方案详解项目支持两种数据存储模式内存缓存默认和本地文件持久化。前者适合快速演示后者适合课程设计报告中展示“数据落地”能力。内存缓存模式默认启用如前所述BOOKS_CACHE是全局列表数据只存在于Python进程内存中。优点是读写极快重启服务后数据丢失符合课程设计“演示即止”的需求。学生在app.py里可以清晰看到数据生命周期app.before_first_request初始化 → 用户访问/dashboard时读取 → 服务关闭时销毁。本地文件持久化需手动开启如果你想在resourse/目录下生成持久化文件只需修改app.py中的两行代码1. 在文件顶部取消注释import json和import os2. 在init_cache()函数中添加文件写入逻辑app.before_first_request def init_cache(): global BOOKS_CACHE if not BOOKS_CACHE: spider QidianSpider() BOOKS_CACHE spider.crawl_books(limit50) # 新增写入本地JSON文件 resourse_path os.path.join(os.path.dirname(__file__), resourse) os.makedirs(resourse_path, exist_okTrue) with open(os.path.join(resourse_path, books_cleaned.json), w, encodingutf-8) as f: json.dump(BOOKS_CACHE, f, ensure_asciiFalse, indent2) print(f[INFO] 数据已保存至 {resourse_path}/books_cleaned.json)这样每次启动服务时不仅会缓存数据还会生成resourse/books_cleaned.json文件。你在课程设计报告中可以截图这个JSON文件证明“数据已成功采集并存储”这是答辩时非常有力的佐证材料。4.3 项目结构化交付物清单课程设计必备一份高分课程设计除了能跑的代码还需要规范的交付物。本项目已为你准备好全套模板位于deliverables/目录若不存在请自行创建report.pdf课程设计报告模板包含“需求分析”、“系统设计”、“核心算法”、“测试结果”、“总结与展望”五章每章预留了截图位置和文字描述框。特别在“核心算法”章节提供了spider/data_processor.py中clean_update_time()函数的完整代码截图和逐行注释。presentation.pptx答辩PPT模板共12页封面、目录、项目背景、技术选型对比表Scrapy vs Requests/BS4、爬虫架构图手绘风格、数据清洗流程图、三类图表效果截图、测试用例执行结果test/test_spider.py的终端输出、性能分析单次爬取50本书耗时45秒、创新点如API优先策略、致谢、QA页。所有图表均标注了数据来源和生成时间。video_demo.mp43分钟演示视频录制了从启动服务、点击采集、浏览仪表盘、切换词云的全过程画面右下角有实时时间水印证明是现场录制而非剪辑。实操心得我在指导学生时要求他们必须在答辩前24小时用手机支架固定拍摄video_demo.mp4。很多学生以为“代码能跑就行”但评委更看重“你是否真正掌握了这个系统”。一段流畅的演示视频比10页文字报告更有说服力。视频里一定要有“点击”、“滚动”、“切换”等用户操作动作不能只是静态截图。5. 常见问题与排查技巧实录5.1 爬虫模块常见问题速查表问题现象可能原因排查步骤解决方案requests.exceptions.ConnectionError: Max retries exceeded目标网站封禁了你的IP或请求过于频繁1. 检查是否开启了代理软件2. 在浏览器中手动访问https://book.qidian.com确认能否正常打开3. 查看app.py中QidianSpider的delay参数是否过小将spider/qidian_spider.py中self.delay 1改为self.delay 3增加请求间隔或更换网络环境如用手机热点AttributeError: NoneType object has no attribute get_textsoup.find()或select_one()未找到对应元素返回None1. 打开resourse/qidian_raw_20240315.html搜索关键词如“书名”2. 对比网页源码和qidian_spider.py中find()的CSS选择器修改选择器例如将soup.select_one(.book-name h1)改为soup.select_one(h1.book-name)或添加容错elem soup.select_one(.book-name h1); title elem.get_text(stripTrue) if elem else 未知书名爬取的作者名全是“未知作者”clean_author()函数未能识别原始HTML中的作者结构1. 在qidian_spider.py的parse_book_detail()方法中print(soup.prettify()[:500])输出前500字符2. 在控制台查找作者相关的HTML片段发现起点新版作者字段在div classauthor内但span标签被移除改为纯文本。修改清洗逻辑author soup.select_one(.author).get_text(stripTrue).replace(作者, ).replace(著, )5.2 Flask后端问题排查指南问题访问http://127.0.0.1:5000显示“Not Found”这是最常见的路径错误。Flask默认只在templates/目录下查找HTML文件。请确认- 你的项目根目录下templates/文件夹是否存在-templates/文件夹内index.html文件名是否拼写正确注意大小写Windows不敏感但Linux敏感-app.py中render_template(index.html)的参数是否为index.html而非Index.html或index.htm。问题仪表盘图表区域空白控制台报错ReferenceError: echarts is not defined这表示PyEcharts的JavaScript库未加载。请检查-static/js/目录下是否有echarts.min.js文件项目已预置-templates/base.html中是否正确引入script src{{ url_for(static, filenamejs/echarts.min.js) }}/script- 浏览器开发者工具的Network标签页筛选JS确认echarts.min.js状态码是否为200。如果显示404请检查static/js/路径是否正确或尝试清除浏览器缓存CtrlF5强制刷新。5.3 PyEcharts可视化疑难杂症词云图中文显示为方框□□□根本原因不是字体文件缺失而是PyEcharts未正确识别字体。解决方案分三步1. 确认static/font/simhei.ttf存在2. 在app.py的/wordcloud路由中WordCloud组件初始化时必须显式指定font_familypython wc WordCloud( init_optsopts.InitOpts(width100%, height600px), # 关键指定字体族 font_familysimhei )3. 在static/css/style.css中添加全局字体声明css body { font-family: simhei, Microsoft YaHei, sans-serif; }这样即使词云组件内部渲染失败页面整体字体也能 fallback 到黑体。饼图标签重叠看不清文字当题材种类过多8种时饼图扇区太小标签会挤在一起。解决方案是启用rosette玫瑰图模式pie Pie(init_optsopts.InitOpts(width100%, height500px)) pie.add(, [list(z) for z in zip(categories, counts)], radius[30%, 70%], # 内外半径 rosetypearea) # 启用玫瑰图面积代表数值玫瑰图用扇区面积而非角度表示数值即使种类多也能清晰区分大小。5.4 课程设计高分避坑经验血泪总结不要试图“全站爬取”有学生为了显示工作量把爬取数量从50改成500结果导致app.py启动超时服务无法响应。课程设计评分标准里“功能完整性”远大于“数据规模”。50本高质量数据比500本残缺数据得分更高。测试用例必须覆盖“空数据”场景test_spider.py里我特意加入了test_empty_author、test_no_chapters等用例。答辩时导师常会问“如果某本书没有章节你的系统会怎样”有测试用例的学生能立刻回答“会返回空列表前端显示‘暂无章节’不会崩溃。”README.md是隐形评分项很多学生忽略文档。本项目的README.md包含环境要求Python 3.7、一键启动命令python app.py、截图预览、常见问题链接。我在批改时会专门检查README是否完整——它反映了学生的工程素养。答辩时永远先演示再讲解不要一上来就说“我用了Flask和PyEcharts”而是打开浏览器点击、滚动、切换让评委亲眼看到系统运行。演示完再指着代码说“这里我用app.before_first_request确保数据只加载一次避免重复爬取。”我个人在实际指导中发现97分的项目和85分的项目差距往往不在代码深度而在细节的完备性一个能正确处理空作者的清洗函数一个在词云中正确指定字体的参数一个在README里写清楚“如何修改爬取数量”的说明——这些微小的、务实的细节累积起来就是高分的全部秘密。本文还有配套的精品资源点击获取简介直接可运行的小说类网站数据抓取与分析项目用Python实现全流程从目标小说站点自动提取书名、作者、分类、更新时间、章节列表等结构化信息支持数据清洗后存入本地文件或内存内置Flask轻量后端app.py配合HTML前端页面index.html、dashboard-1.html、wordCloud.html等实现交互展示通过PyEcharts生成阅读热度趋势图、题材分布饼图、高频词云图等多种可视化图表已打包pyecharts35-1.7.1 wheel安装包和完整依赖列表requirements.txt适配Python 3.7环境开箱即用项目结构清晰包含templates模板目录、static静态资源CSS/JS/字体/图片、resourse原始数据存储、test测试用例及IDEA工程配置高校计算机专业学生可用于课程设计、期末大作业或毕业设计参考实际交付成绩97分。本文还有配套的精品资源点击获取