Python爬取小说数据及可视化系统毕设全流程解析
每到毕业季后台私信里关于毕设的提问就会准时多起来。问得最多的不是“论文怎么写”而是“我拿到了一套基于Python爬取番茄小说数据及可视化系统的源码但跑不起来怎么办”。不夸张地说十个人里有八个卡在同一道坎上代码在别人电脑上能运行到自己手里就各种报错就算勉强启动了图表不显示、数据为空、页面白屏。作为带过不少这类课题的老手我想借这个题目把从0到1的完整实现过程好好捋一遍——从爬虫采集、数据入库到数据清洗、可视化图表再到Flask页面展示连带论文怎么写、答辩怎么答一次性说清楚后文中的“某阅读平台”即指代番茄小说数据源避免无关讨论。这套东西适合正在做毕设的同学、想练手爬虫和可视化的新手、以及想搞懂“数据采集到展示”全流程的人。1. 需求拆解与技术全局1.1 毕设题目到底在考什么很多同学以为这是一道“爬虫题”于是把重心全压在爬虫代码上结果答辩时被老师一句“你的系统亮点在哪里”问得哑口无言。实际上这类题目的核心考核点是数据工程的完整闭环而不是某一个单一技术点。拆解标题就能看出门道“基于Python爬取”对应的是数据采集层“可视化系统”对应的是数据分析与展示层“设计与实现”还默认包含数据库设计、系统架构、测试甚至论文写作。换言之导师想看的是你能否独立完成一套“采集—清洗—存储—分析—展示”的小型数据系统这正是企业里数据分析岗位日常工作的缩影。把需求拆成清单就是这个样子需求点对应模块关键技术数据采集爬虫程序requests、JSON解析、翻页处理数据落地存储模块SQLite/MySQL、批量写入数据处理清洗模块pandas、正则表达式、去重去空多维分析可视化模块pyecharts、词云、统计图系统交互Web展示Flask、Jinja2模板把这张表当成“验收清单”来做你的毕设方向就不会跑偏。很多课设只做到爬虫存CSV就停了那充其量是个爬虫脚本而加上数据库、网页看板和图表联动之后才配叫“系统”。1.2 技术栈怎么选才能不被答辩老师追问到卡壳技术选型直接影响答辩难度我的建议是选自己能讲清楚的技术而不是看起来最炫的技术。爬虫框架方面很多人纠结用Scrapy还是requests。Scrapy确实强大自带并发、调度、中间件爬虫圈也爱用但它是重量级框架代码量分散在spider、pipeline、settings等多个文件里答辩时老师追问“你这个中间件的作用是什么”很容易翻车。相反requests配合BeautifulSoup或直接解析JSON代码短、逻辑直白任何一个函数你都能当场解释完全够用。毕设场景下requests json是我最推荐的选择。存储方面SQLite开发期最省心不用安装服务一个文件搞定适合本地演示但如果论文里写“MySQL数据库设计”会让系统显得更完整。做法是开发时先用SQLite跑通再预留MySQL切换的脚本两个都写进文档里实际答辩以SQLite演示为主被问到了就说支持MySQL扩展。可视化方面pyecharts几乎是毕设最优解。它基于ECharts封装图表华丽API友好重点是能直接生成HTML片段嵌入Flask模板不像原生ECharts那样需要手动维护大量的JavaScript。中文文档也齐全调试成本低。至于Flask轻量到只有一个主文件就能撑起整个Web系统没有Django的目录负担适合这个体量。1.3 系统整体架构怎么设计整个系统采用经典的四层架构数据采集层向平台公开列表页/搜索接口发送请求拿到JSON数据解析目标字段。数据处理层对采集结果统一清洗去重、补全、类型转换。数据存储层设计novel表字段与索引合理化写入SQLite。展示分析层从数据库聚合查询生成图表通过Flask路由渲染到网页。这个架构的优点在于拆得干净。爬虫挂了不影响可视化换了数据源也可以复用存储和分析模块论文里画架构图也好看。更重要的是每一层都能作为答辩中的一个“独立提问回答点”老师问哪个方向你都有话可说。2. 数据采集层爬虫设计与实现细节2.1 先搞清楚数据到底在哪里很多初学者拿到这类题目第一反应是requests.get(页面URL)再解析HTML结果发现源代码里根本找不到书名和作者——因为这类平台的网页是前端动态渲染的列表数据通过异步接口加载直接抓HTML只能拿到一堆JS脚本和空壳标签。正确做法是打开浏览器开发者工具切到Network面板刷新列表页筛选XHR或Fetch类型的请求逐个查看返回内容。找到那个直接返回JSON预览的接口记下它的请求URL、请求方式GET还是POST、参数列表。这套“手动抓包定位接口”的能力比任何爬虫框架都重要它能应付90%以上动态站点。打开接口后通常会看到分页参数常见命名是pageNum、page、pageSize、limit、offset。观察返回结果的JSON结构确定列表字段是在data.books还是data.list里把层级关系记录下来。这一步务必在写代码前完成否则后面解析时全靠猜效率极低。2.2 爬虫代码骨架直接能用的那种爬虫类建议用一个NovelSpider类封装而不是在脚本里糊一堆函数。类的组织方式可读性好便于扩展答辩也容易讲。import requests import time import json class NovelSpider: def __init__(self): self.session requests.Session() self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36, Referer: https://example.com/, # 以实际抓包到的为准 Accept: application/json, text/plain, */* } self.base_url https://api.example.com/book/search # 以实际接口为准 self.results [] def fetch_page(self, keyword, page, page_size20): params {keyword: keyword, pageNum: page, pageSize: page_size} try: resp self.session.get( self.base_url, headersself.headers, paramsparams, timeout10 ) resp.raise_for_status() return resp.json() except Exception as exc: print(f请求失败page{page}error{exc}) return None def parse_page(self, payload): # 假设接口返回 {data: {books: [{bookName: ..., ...}]}} if not payload: return [] books payload.get(data, {}).get(books, []) parsed [] for item in books: parsed.append({ book_id: item.get(bookId), book_name: item.get(bookName), author: item.get(author), category: item.get(category), word_count: item.get(wordCount), status: item.get(serialStatus), score: item.get(score), summary: item.get(summary, ).strip() }) return parsed def crawl(self, keyword, max_page10): for page in range(1, max_page 1): payload self.fetch_page(keyword, page) rows self.parse_page(payload) if not rows: print(f第{page}页无数据提前结束) break self.results.extend(rows) print(f已采集第{page}页累计{len(self.results)}条) time.sleep(1) # 控制频率别把对方服务器打崩几个关键细节多说一句。headers里的Referer和User-Agent必须从浏览器的请求详情里复制很多接口会校验这两个字段缺失就直接拒。time.sleep(1)看着简单其实是保护你长期可用性的命脉频繁请求轻则IP被临时限制重则给正常用户带来不好的体验毕设项目尤其要克制只采集公开数据、控制频率、不碰任何需要账号权限的信息这是底线。翻页时设置max_page上限避免失控无限爬。每次请求失败不要直接崩溃打印错误后跳过最后统计成功率。2.3 字段清单直接照着建表采集字段的设计就是数据库表设计的上游建议一次想清楚。以小说类数据为例常用字段如下字段名类型说明book_id字符串/整数平台内唯一ID去重依赖book_name字符串书名author字符串作者名category字符串分类如玄幻、都市word_count整数字数注意单位是否万serial_status整数/字符串连载/完结状态score浮点数评分可能为空summary文本简介crawl_time时间戳采集时间做增量更新必备特别提醒crawl_time这个字段。很多新手会忽略它等到想做“每日更新趋势图”或者论文里写“数据可以定时增量更新”时才想起来没有记录时间只能重爬。所以第一版设计就要加上。2.4 数据落地的双保险策略爬虫跑一遍不容易中途断网、程序崩溃都是常事。我的习惯是CSV和数据库双写每次爬取结束立刻把self.results追加写入CSV作为原始备份再入数据库作为系统数据来源。CSV的好处是Excel能直接打开排查数据问题方便数据库则服务于可视化系统和前端展示。import csv def save_to_csv(filename, rows): if not rows: return with open(filename, a, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnameslist(rows[0].keys())) if f.tell() 0: writer.writeheader() writer.writerows(rows)这里用utf-8-sig编码而不是utf-8是因为Excel打开时对前者识别更准确不会出现中文乱码。如果你打算用pandas做后续清洗CSV文件还要注意字段不要包含逗号和换行符简介里常见的换行会导致CSV解析错位清洗时统一处理。3. 数据清洗与存储层的坑3.1 真实数据远比你想的脏第一次爬完数据打开CSV你大概率会看到这些情况字段值为null、作者是空字符串、简介里全是营销对话、字数单位时而“万”时而数字、同一本书在不同分类下重复出现。这些脏数据如果不清理直接入库做可视化图表会出现“作者为空”的柱子、“字数异常”的离群值、分类占比疯涨答辩时根本没法看。数据清洗的第一步是去重。以book_id为主键保留先采集到的记录删除后出现的重复项。注意不要只按书名去重不同作者可能存在同名作品book_id才是唯一标识。第二步是去空书名和作者为空的记录直接剔除因为这两项是所有统计分析的基础。第三步是类型转换字数从字符串转整数、评分为空时填充0或平均值、状态字段统一映射为“连载/完结”。import pandas as pd import re df pd.read_csv(novel_data.csv) df df.drop_duplicates(subset[book_id]) df df.dropna(subset[book_name, author]) df[word_count] pd.to_numeric(df[word_count], errorscoerce).fillna(0) df[score] pd.to_numeric(df[score], errorscoerce).fillna(0) df[summary] df[summary].apply(lambda x: re.sub(r\s, , str(x)))用pandas清洗的最大好处是代码少且每一步都可验证。清洗完打印df.info()看看每个字段的非空数量心里就有数了。清洗后的数据可以另存为novel_clean.csv这样原始数据和水洗数据分开论文里还能截个“数据清洗前后对比”的图。3.2 数据库表结构设计和入库实现清洗后的数据要落数据库。以SQLite为例建表语句如下CREATE TABLE IF NOT EXISTS novel ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id TEXT NOT NULL UNIQUE, book_name TEXT NOT NULL, author TEXT, category TEXT, word_count INTEGER DEFAULT 0, serial_status TEXT, score REAL DEFAULT 0, summary TEXT, crawl_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_category ON novel(category); CREATE INDEX idx_word_count ON novel(word_count);book_id设置唯一约束后即使爬虫端去重漏掉了数据库端也能兜底。category和word_count加上索引是因为可视化模块的聚合查询GROUP BY category、ORDER BY word_count会频繁使用这两个字段没有索引时数据量大后查询明显变慢。批量入库优先用executemany而不是一条条execute速度差别很大。import sqlite3 def insert_novels(db_path, rows): conn sqlite3.connect(db_path) cursor conn.cursor() sql INSERT OR IGNORE INTO novel (book_id, book_name, author, category, word_count, serial_status, score, summary) VALUES (?, ?, ?, ?, ?, ?, ?, ?) data [ (r[book_id], r[book_name], r[author], r[category], r[word_count], r[serial_status], r[score], r[summary]) for r in rows ] cursor.executemany(sql, data) conn.commit() conn.close()这里用INSERT OR IGNORE配合唯一索引重复数据自动跳过比先查再插效率高得多。开发时留意一点SQLite默认不强制类型你传字符串给word_count也能塞进去所以入库前做好类型转换否则后续SQL聚合会报错或结果异常。3.3 存储环节高频踩坑数据库相关的报错里中文乱码排在第一位。SQLite因为是本地文件中文字符一般没问题但如果你切到MySQL连接串里不写charsetutf8mb4中文大概率变成问号。另外CSV用Excel打开看着正常用pandas读进来却乱多半是编码格式不一致统一用utf-8-sig读写就不会有这种问题。还有一类坑来自数据里的特殊字符比如书名中含有英文双引号、简介中含换行符这会导致CSV列错位、SQL语句拼接出错。处理办法是清洗阶段把所有换行符和制表符替换为空格数据库写入时统一走参数化查询绝不拼SQL字符串。4. 可视化分析与Web系统落地4.1 图表不是越多越好每个图都要回答一个问题很多人的毕设可视化页面放了一堆图表却说不清每个图说明了什么答辩时被老师一句“这张图有什么结论”问倒。正确的做法是先定业务问题再选图表类型。比如全站哪类小说数量最多——分类占比饼图热度最高的10本书是什么——横向柱状图读者更偏好长篇还是短篇——字数区间直方图完结和连载作品的比例如何——环形图书名和简介里出现最多的关键词是什么——词云每个图表配上一句分析结论写进论文“系统实现”章节系统和论文就都有了灵魂。4.2 pyecharts绘图代码量小到离谱pyecharts的链式写法非常友好画一个分类饼图只需要十几行from pyecharts.charts import Pie from pyecharts import options as opts def category_pie(data_pairs): pie ( Pie() .add(, data_pairs) .set_global_opts(title_optsopts.TitleOpts(title小说分类占比)) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {c})) ) return piedata_pairs是形如[(玄幻, 120), (都市, 96)]的列表直接从数据库GROUP BY category统计出来。柱状图、折线图、词云的写法大同小异都是.add()加数据、.set_global_opts设置标题和图例。绘图代码集中放在analysis/charts.py里Flask路由调用时只负责拼数据职责分离代码清爽。有一个必须提醒的坑词云图中文显示。pyecharts的默认词云基于JavaScript渲染页面没有加载中文字体时中文会变成方块。解决办法是在HTML模板头部指定中文字体或者词云图用第三方wordcloud库先生成PNG图片再以图片形式放进模板。毕设演示建议用后者稳定不出错。4.3 Flask集成与页面路由设计项目目录结构建议这样组织清晰的层次本身就是文档project/ ├── app.py # Flask主程序 ├── spiders/ │ └── novel_spider.py # 爬虫模块 ├── analysis/ │ └── charts.py # 图表生成模块 ├── templates/ │ └── index.html # 前端模板 ├── data/ │ ├── novel.db # SQLite数据库 │ └── novel_clean.csv # 清洗后数据 └── requirements.txtapp.py里最关键的是把pyecharts生成的图表嵌入模板。pyecharts提供render_embed()方法返回一段可直接渲染的HTML代码把它传进模板变量即可from flask import Flask, render_template from analysis.charts import category_pie, top_bar, word_cloud app Flask(__name__) app.route(/) def index(): # 数据库查询在这里执行组装好图表数据 pie_html category_pie(category_data).render_embed() bar_html top_bar(top_data).render_embed() return render_template(index.html, pie_htmlpie_html, bar_htmlbar_html) if __name__ __main__: app.run(debugTrue, port5000)模板里用safe过滤器告诉Jinja2不要转义div classchart-box{{ pie_html | safe }}/div div classchart-box{{ bar_html | safe }}/div很多同学忘记加| safe结果页面上显示的不是图表而是一堆HTML源码。这个细节值10分。4.4 让系统看起来“完整”的小细节可视化系统如果只有图表整体会显得单薄。我建议在页面顶部加四个统计卡片采集书籍总数、覆盖分类数量、平均字数、完结占比。这些数字通过简单的聚合SQL就能拿到但呈现出来后“系统”的感觉立刻不一样了。再引入Bootstrap或者一份开源的CSS模板把图表排列成响应式网格页面颜值和答辩观感都会提升一个档次。开发顺序建议先做数据库查询接口确认返回数据正确后再画图表最后套模板CSS。每一步都有可验证的输出不会陷入“写代码2小时、改bug一整天”的困境。5. 论文写作、答辩应对与问题排查实录5.1 论文结构照这个写导师挑不出大毛病毕业设计论文有相对固定的框架照着写至少不会缺项第一章 绪论研究背景与意义、国内外研究现状。这段不用太长核心是说清楚“为什么选这个题”。第二章 相关技术介绍Python、requests、Flask、pyecharts、SQLite每项技术介绍三五行再写一段“技术选型分析”。第三章 系统分析与设计需求分析、可行性分析、系统架构、数据库设计。第四章 系统实现爬虫模块、数据清洗、可视化模块、Web端每个模块截图核心代码功能说明。第五章 系统测试功能测试用例表、异常场景测试、兼容性测试。第六章 总结与展望写实际学到的东西再提一两个没做的方向。很多同学只重视第四章其实第三章和第五章是拉分项。数据库ER图、用例表、测试表都是加分素材而且写起来不费劲。5.2 答辩时被追问的高频问题和答复思路“为什么不用Scrapy”标准答法本系统数据量级有限requests足以满足需求代码简洁可控Scrapy适合大规模分布式采集但会引入更多复杂度对系统目标属于过度设计。“如果网站改版了怎么办”答系统将数据源封装在爬虫模块内部接口地址和解析逻辑集中管理改版时只更新对应配置或解析函数不影响可视化层和存储层。同时爬虫包含完整的异常捕获和日志记录可以快速定位失败位置。“数据如何保证时效性”答每个字段记录采集时间程序支持定时触发增量爬取对比book_id更新已变化的数据。“系统有哪些创新点”注意不要编造炫酷名词把实际做过的细节包装一下即可多维度的可视化看板、数据清洗流程的完整性、爬虫与Web展示的模块化解耦。5.3 常见问题排查速查表现象可能原因解决办法爬虫请求无数据接口地址或参数不对用浏览器的完整请求头测试打印resp.text看返回请求返回403缺少Referer等头部复制浏览器请求的全部headers保持频率克制字段全是空值JSON层级解析错误先print(json.dumps(payload, ensure_asciiFalse)[:1000])看结构数据库中文乱码连接串没有指定编码MySQL连接加charsetutf8mb4图表在页面不显示模板缺少safe过滤模板变量改成{{ chart词云中文变方块页面缺少中文字体换成wordcloud生成图片或指定中文字体文件数据重复去重只按书名改为按book_id去重数据库建唯一索引这张表我建议直接写进论文第五章的“系统测试”或“常见问题”小节既真实又有干货感。6. 避坑清单与可以扩展的方向6.1 我从项目里总结的操作顺序带过很多同学跑这类毕设我发现最有效的流程是先小批量、再全量先跑通、再优化。具体来说是分三步走。第一步只爬1页、20条数据从请求到入库到页面显示全部跑通验证链路完整。第二步扩大爬取范围加到10页、200条数据顺便测试异常处理和限速是否有效。第三步再把数据规模放到几千条这时候检查数据库查询性能和图表渲染速度。每一步都有明确的验收标准出了问题能立刻定位是请求、解析、存储还是展示的哪个环节比闷头写完整套再统一调试高效得多。还有一个容易被忽略的习惯数据比代码值钱。爬了好几千条数据程序崩一次重跑要花几十分钟所以CSV原始备份务必保留数据库文件也定期复制一份。哪怕代码全丢了只要有数据重建项目只是时间问题。6.2 手里只有“一条龙”源码该怎么办我知道不少同学手里拿着的是一份打包好的毕设号称程序、文档、讲解一条龙齐全。但必须泼一盆冷水直接提交别人的代码风险不只是查重更严重的是答辩时老师深挖任何一个实现细节你说不出所以然很容易露馅。建议拿到源码后做三件事。第一把项目先跑起来用真实数据验证各个页面是否有输出这一步排除环境问题。第二梳理数据流从爬虫入口到数据库到图表画出每个模块的调用关系自己能复述一遍。第三挑一个模块做二次开发比如新增一张“作者产量Top10”的图表或者增加一个数据导出功能。改完以后代码就成了“你的作品”答辩时被问到也完全讲得清楚。时间再紧张至少做到第一件和第二件这是底线。6.3 这套系统还能往哪些方向升级如果时间充裕有几个不错的扩展方向。其一加入定时调度用APScheduler让爬虫每隔一段时间自动跑一次数据库持续更新页面展示“数据更新于xx分钟前”系统的时效感立刻有了。其二在清洗后的文本上做关键词提取和情感分析给每本书生成标签云或者推荐指数论文里能多写一节“基于内容的标签分析”。其三把系统部署到云服务器上用真实数据库建好索引再做一个登录页区分普通用户和管理员系统的完整性又上了一个台阶。这些方向都不用动主架构属于增量开发性价比很高。最后分享一点个人体会。毕设项目不在于技术多炫而在于闭环完整、逻辑自洽、代码能跑、讲得清楚。“爬取数据可视化”这个组合是少有的能把数据获取、处理、展示完整串起来的题目。你在做它的过程中把requests、pandas、pyecharts、Flask这几个工具用熟基本就把数据分析工作日常的链路体验了一遍这是比拿学位更实在的收获。如果时间紧先把“小批量跑通、再全量、先复现、再改造”这个节奏刻在脑子里我保证你至少省下一半的折腾时间。