简介:面向计算机专业毕业设计或课程设计场景,这套以网易新闻为对象的数据采集与分析可视化项目,完整覆盖了从Scrapy爬虫抓取标题、发布时间、栏目、摘要等结构化信息,到MySQL入库、Python后端清洗与统计、再到Vue+ECharts大屏联动展示的闭环流程。压缩包为zip格式,共82个文件、约6.06MB,其中43个Python脚本承担爬虫与数据分析逻辑,前端以Vue、ECharts、JavaScript及CSS/SCSS样式构成,SQL文件与数据库文档可快速还原MySQL库表,另附双启动脚本、依赖清单、配置文件和演示视频,便于直接运行与答辩演示。目前已有41人学习/下载。项目内包含高频词统计、时间趋势、频道占比、TOP10热度及关键词云等多维可视化图表,适合需要完整实战参考的毕业生或想要快速上手爬虫可视化全流程的开发者,只需按文档配置即可复现新闻数据看板。 又到了毕业设计选题的季节,每年都会被问到同一类问题:想做一个和爬虫、数据分析有关的项目,但不知道选什么题,也不知道做到什么程度算完整。网易新闻爬虫+数据分析+大屏可视化这套方案,是我反复推荐过的经典组合。它把一条完整的数据链路走通了:requests爬虫从网易新闻采集数据,清洗后写入MySQL,再用SQL和Python完成多维分析,最后通过Flask接口和ECharts大屏把结果展示出来。完整的交付物包括演示视频、MySQL数据库文档、前后端源码,可以说毕设需要的材料都齐了。这篇文章从一个实际带项目的角度,把从选题、设计、编码到答辩的完整流程和踩坑经验一次讲清楚。正在纠结毕设方向的同学,以及想从零跑通一个全链路数据项目的开发者,都可以直接照着做。
1. 为什么是网易新闻:选题价值与系统架构拆解
1.1 网易新闻作为数据源的三点优势
第一,页面结构足够规整。网易新闻的列表页和详情页基本都是服务端渲染的HTML,用requests拿到源码之后,通过XPath或者正则就能把标题、链接、时间提取出来。相比那些需要处理JS动态渲染、接口加密、滑块验证的站点,网易新闻对新手极其友好,能让你把精力集中在爬虫逻辑本身,而不是无休止地对抗前端混淆。
第二,数据字段天然丰富。新闻数据自带标题、正文、发布时间、来源媒体、栏目分类这些结构化字段,再加上评论数这个可以当热度指标的数值,做数据分析时的维度一下就打开了:可以按小时看发布规律,按分类看内容占比,按来源看媒体排行,还可以对标题和正文做分词统计热点词。数据源本身的质量,直接决定了后期分析和大屏展示能做出多少东西。
第三,反爬强度适中。只要不把请求频率调得太离谱,网易新闻基本不会在爬虫层面设置太多障碍。对比那些电商平台,网易的风控策略算是非常温和的,特别适合在有限时间内完成一个毕业设计。你在项目里只需要做好基本的请求头伪装、请求间隔控制、异常重试,就能稳定拿到数据。
1.2 系统架构与关键技术选型
整套系统的核心链路可以概括为一条数据流水线:Python爬虫采集网易新闻 → 数据清洗后写入MySQL → 基于SQL和Python做多维分析 → Flask提供JSON接口 → ECharts完成大屏可视化。数据从网上来,最终落到浏览器的大屏上,每一层职责清晰,答辩时非常好讲。
| 模块 | 技术选型 | 选型理由 |
|---|---|---|
| 爬虫采集 | Python + requests + lxml | 轻量直接,调试成本低,代码量可控 |
| 数据存储 | MySQL 8.0 | 数据结构化固定,关系型数据库处理聚合统计最顺手 |
| 数据分析 | pandas + jieba + SQL | 聚合计算用SQL,文本分词用jieba,各司其职 |
| 后端接口 | Flask | 轻量级,写几个路由就能把数据库里的数据变成JSON |
| 前端展示 | HTML + CSS + ECharts | ECharts生态成熟,大屏图表配置案例多,中文文档友好 |
| 演示交付 | OBS录屏 + 演示视频 | 答辩时不可能现场等爬虫慢慢跑,提前录好视频最稳妥 |
至于为什么不选更重的技术栈,我的判断是:毕业设计考察的是你对完整流程的理解,而不是框架数量。Scrapy确实功能更强,但对这个量级的数据采集,requests加线程池已经完全够用;前后端不拆成Vue和SpringBoot,是因为Flask加原生HTML就能快速出效果,省下来的时间可以投入到数据分析和可视化细节上。技术选型的核心原则是:在满足功能的前提下,选择你自己能讲明白的方案。
2. 爬虫采样层:从列表页到正文落库的完整细节
2.1 首页栏目导航分析:找到所有列表入口
爬虫的第一步不是写代码,而是先打开网易新闻首页,用浏览器的开发者工具观察页面结构。你会发现首页顶部和中间区域分布着大量栏目链接,比如国内、国际、社会、体育、娱乐、科技这些,每个栏目对应一个独立的列表页URL。采集策略就是:先请求首页拿到所有栏目入口,再逐个请求栏目列表页,从列表页里提取新闻的标题、链接、发布时间等基础信息。
import requests from lxml import etree headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" } def get_category_links(): resp = requests.get("https://news.163.com/", headers=headers, timeout=10) resp.encoding = "utf-8" html = etree.HTML(resp.text) # 以实际页面结构为准,通常新闻栏目的链接都带明显的关键词 links = html.xpath("//a[contains(@href, 'news.163.com')]/@href") return list(set(links))这里有一个很容易被忽略的细节:resp.encoding。网易新闻页面本身是UTF-8编码,但有些页面会在响应头里给出错误的charset,导致解析出来全是乱码。最稳妥的做法是先打印resp.encoding和resp.apparent_encoding对比一下,再强制指定正确编码。如果你发现标题和正文出现乱码,优先检查这一行。
列表页拿到之后,同样用XPath提取每条新闻的标题和详情链接。提取时要注意去重,因为同一个链接可能在首页和栏目页重复出现。我的做法是用一个set()保存已经见过的URL,遇到重复直接跳过,避免重复请求浪费流量和时间。
2.2 详情页字段采集与清洗规则
列表页能拿到标题、链接、发布时间,但正文内容和详细来源需要进入详情页才能抓到。详情页的HTML结构相对统一,重点提取几个字段:新闻标题、正文内容、来源媒体、发布时间、评论数。评论数在列表页或者详情页的某些节点里可以找到,如果改版后不好定位,也可以跳过,不硬撑。
import re from datetime import datetime def clean_text(raw): """去掉HTML标签和多余空白,保留干净文本""" text = re.sub(r"<[^>]+>", "", raw) text = re.sub(r"\s+", " ", text) return text.strip() def parse_time(raw): """把各种格式的时间字符串统一成datetime""" raw = raw.strip() for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y年%m月%d日 %H:%M"): try: return datetime.strptime(raw, fmt) except ValueError: continue return None清洗规则是整个爬虫阶段最容易被低估的部分。从页面上抓下来的文本经常带着广告、无关推荐、特殊字符,如果直接入库,后续做词频统计时会混入大量垃圾词。我的建议是建立三层清洗:第一层用正则去掉HTML标签,第二层去掉空白字符和特殊符号,第三层根据正文长度过滤掉过短或过长的异常内容。时间字段一定要统一转成datetime类型再入库,否则后期按小时、按天聚合时会非常痛苦。
2.3 反爬节奏控制与常见采集异常
很多人在爬虫阶段会遇到一个很诡异的现象:程序运行结束没有报错,控制台只显示Process finished with exit code 0,但什么数据都没抓到。出现这种情况,第一反应不该是怀疑代码逻辑,而是先确认服务器到底回了什么内容。把resp.text[:300]打印出来看一眼,如果发现返回的是验证页、跳转页或者空HTML,说明你的请求被识别或者页面结构已经变了。
应对反爬的基本动作要做扎实:请求头里带上完整的User-Agent和Referer,使用requests.Session()保持连接,两次请求之间用random.uniform(1, 3)做随机延时,单次请求失败后重试三次。考虑到网易新闻的反爬强度,做到这些就已经足够了,没必要上代理池和验证码识别,那些属于加分项,不属于必选项。
增量爬取也是一个必须考虑的问题。毕业设计的数据量不需要追求全量,每天定时跑一次脚本,只爬最近几页的更新就够。判断是否重复采集时,最简单的方案是拿URL去MySQL里查一下是否已存在,存在就跳过。这样爬虫持续运行一周,数据库里就有了好几天的数据,做时间趋势分析时素材就很丰富了。
另外务必提醒一句:爬虫项目只采集公开信息、控制请求频率、用于学习研究,不碰个人隐私数据,也不给目标站点造成访问压力。这套设计里单次采集几百条数据,请求间隔都在一秒以上,远低于正常用户访问量,属于合理的技术学习行为。
3. MySQL建模与多维分析:把原始数据变成可展示的指标
3.1 新闻表设计与索引取舍
数据库设计看起来简单,其实很多细节直接影响后续开发效率。新闻表的核心字段包括标题、分类、来源、作者、发布时间、URL、正文、评论数、入库时间。建表语句参考如下:
CREATE TABLE news ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, category VARCHAR(50) DEFAULT '', source VARCHAR(100) DEFAULT '', author VARCHAR(50) DEFAULT '', publish_time DATETIME DEFAULT NULL, url VARCHAR(500) NOT NULL, content MEDIUMTEXT, comment_count INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_url (url(191)), KEY idx_category (category), KEY idx_publish_time (publish_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;几个关键取舍说一下。字符集必须用utf8mb4,别用utf8。之前有学生在入库时遇到Incorrect string value报错,查了半天发现是标题里带了一个特殊表情符号,换成utf8mb4之后问题立刻消失。评论数这个字段用INT而不是VARCHAR,这一点很多人会忽略——数据库里存数字类型,排序、求和、聚合才能用数据库函数,如果用字符串存,ORDER BY comment_count排出来全是字典序,MAX()取出来的也不对。发布时间用DATETIME而不是VARCHAR,这点和评论数同理,按小时分组GROUP BY HOUR(publish_time)是必须依赖时间类型的。
URL字段加唯一索引是做去重的关键,配合INSERT IGNORE语法实现重复数据自动跳过。索引不是越多越好,但category和publish_time这两个字段因为经常出现在GROUP BY和ORDER BY里,加索引收益很高。content字段是大文本,不要对它做索引,那是浪费空间。
3.2 数据入库的去重策略与批量写入
爬虫每采集一批新闻,就要写一次MySQL。逐条INSERT不是不行,但效率太低,我习惯用executemany批量写入。配合URL唯一索引,重复的新闻直接忽略,不用在Python里做一次查询判断。
import pymysql def batch_insert(items): conn = pymysql.connect( host="localhost", user="root", password="123456", database="news_db", charset="utf8mb4" ) cursor = conn.cursor() sql = """ INSERT IGNORE INTO news (title, category, source, author, publish_time, url, content, comment_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) """ cursor.executemany(sql, items) conn.commit() cursor.close() conn.close()这里有一个容易踩的坑:pymysql.connect里的charset参数必须和数据库字符集保持一致,写上utf8mb4,否则即使表结构是utf8mb4,连接层的编码不对照样乱码。另外,如果你的某个字段是None,直接传进executemany没问题,但如果是datetime对象,一定确保已经从字符串转换好了,别把字符串丢给DATETIME字段,MySQL会报错或者存进去一个乱七八糟的值。
3.3 分析维度设计:从SQL聚合到中文分词
数据入库只是中间步骤,真正的价值在于分析。我设定的分析维度有四个,每个维度都对应大屏上的一块图表。
第一是分类分布。用GROUP BY category统计每个栏目的新闻数量,可以看出不同栏目在样本周期内的内容产量差异。第二是发布时间规律。用GROUP BY HOUR(publish_time)统计全天24小时每个小时的发稿量,能直观看出新闻发布的波峰和波谷。第三是来源媒体排行。用GROUP BY source加ORDER BY cnt DESC LIMIT 10拿到活跃媒体Top10。这三个维度都是纯SQL能搞定的。
第四是热词分析,需要Python配合。用jieba对标题和正文做分词,去掉停用词,再用collections.Counter统计词频,最后取频率最高的前50个词,这个结果可以直接喂给词云组件。
import jieba from collections import Counter stopwords = set(["的", "了", "在", "是", "我", "有", "和", "就", "不", "人"]) words = [] for title in titles: words.extend(jieba.lcut(title)) words = [w for w in words if len(w) > 1 and w not in stopwords] counter = Counter(words).most_common(50)分析结果最终要落到前端展示,所以我会把这些统计结果统一封装成JSON格式,每个维度一个接口。SQL聚合结果直接转成列表,词频结果转成[{"name": "关键词", "value": 次数}]的结构,方便ECharts直接消费。
4. 大屏可视化与前后端串联:让数据动起来
4.1 大屏视觉布局与背景方案
大屏展示是整套系统里视觉冲击力最强、也最容易被答辩老师记住的部分。布局上我推荐经典的1920x1080设计稿,页面整体分为上下两段,上方是标题栏,下方是主体内容区,主体内容区再纵向切成三列。
中间一列放核心指标:总新闻数、总评论数、分类数这些数字翻牌器,下面放词云。左侧放来源媒体Top10柱状图、评论数Top10新闻列表。右侧放分类占比饼图和24小时发布趋势折线图。这样的布局信息密度高,又不显得拥挤。
背景方案上,很多人会去找现成的"大屏背景模板",其实纯CSS完全够用。深蓝色到深黑色的linear-gradient渐变打底,再叠加一层淡淡的网格线背景,面板用半透明背景色加backdrop-filter: blur()做出毛玻璃效果,科技感一下就出来了。边缘可以加一些细线条装饰,但不要过度,否则会喧宾夺主。
4.2 ECharts图表选型与数据接口设计
Flask后端的主要职责是把MySQL里的统计结果变成JSON接口。我会设计五个核心接口:/api/overview返回核心指标,/api/category_stats返回分类占比,/api/hourly_stats返回24小时发布趋势,/api/source_top返回来源媒体Top10,/api/word_cloud返回热词数据。每个接口内部就是执行一条SQL或者若干Python逻辑,然后jsonify返回。
from flask import Flask, jsonify import pymysql app = Flask(__name__) @app.route("/api/category_stats") def category_stats(): conn = pymysql.connect(host="localhost", user="root", password="123456", database="news_db", charset="utf8mb4") cursor = conn.cursor() cursor.execute("SELECT category, COUNT(*) FROM news GROUP BY category ORDER BY cnt DESC") rows = cursor.fetchall() cursor.close() conn.close() data = [{"name": r[0], "value": r[1]} for r in rows] return jsonify(data)前端用一个通用的fetchData函数统一处理这些接口请求。拿到数据之后,通过setOption动态更新图表配置,实现数据驱动渲染。ECharts的配置项比较繁琐,但我建议把每个图表的公共配置抽出来,比如tooltip、legend这些,不要每个图表都重复写一遍,代码会清爽很多。
4.3 前端刷新与自适应适配
大屏展示最忌讳的是静态页面,一眼看过去没有任何动态效果。我会在页面加载时请求一遍所有接口,然后设置一个定时器,每60秒重新拉取一次数据,图表自动更新。这样即使后台有新的爬虫数据入库,大屏上也能自动反映出来,演示时的效果会非常加分。
setInterval(loadAllData, 60000); // 每60秒刷新一次自适应是大屏开发里的一个老大难问题。不同演示屏幕的分辨率不一样,如果只用百分比布局,ECharts图表内部的文字大小和间距很难统一。我的方案是把设计稿固定为1920x1080,然后用CSS的transform: scale()对整体页面做等比缩放,根据浏览器窗口的宽高计算缩放比例。这样无论投到多大的显示器上,页面都能保持设计稿的比例关系,不会出现错位。
前端请求后端接口时,如果直接打开HTML文件,会遇到跨域问题。最简单的解决办法是给Flask添加flask-cors扩展,或者干脆把HTML文件放在Flask的static目录下,通过Flask直接访问页面,就不存在跨域了。这个细节提一下,省得到时候卡半天。
5. 毕业设计交付物整理与答辩高频追问
5.1 演示视频录制与源码文档的结构
很多学生项目做完了,但不会“卖”。答辩现场的时间通常只有几分钟,如果你现场打开爬虫开始跑,等着数据一条条入库,那时间肯定不够用。演示视频的价值就在于把整个流程压缩到最短时间展示出来。
我建议视频按三个片段剪辑:第一段展示爬虫脚本的运行过程,不需要完整跑完,只要看到控制台有采集日志输出,然后切到MySQL客户端,SELECT COUNT(*)验证数据已经入库即可;第二段展示数据分析的过程,可以演示几条SQL语句的执行结果;第三段是重点,把画面切到大屏页面,完整展示几个图表的动态效果,适当停顿让答辩老师看清细节。视频总长度控制在5分钟以内,配上你自己的语音讲解,效果远好于现场操作。
源码和文档的结构也要讲究。源码要达到“拿到就能跑”的标准,README.md里写清楚环境要求、安装依赖的命令、数据库初始化步骤、爬虫启动命令和后端启动命令。数据库文档单独整理一份,包含ER图、表结构说明、字段注释、索引设计说明。文档不是给老师看的,是给未来拿到这套项目的人看的,哪天你自己打开这个项目,也能一眼看懂。
5.2 答辩追问清单与应对思路
依据我带项目的经验,答辩老师对这类系统的追问基本集中在几个固定问题上,提前准备好答案,现场就不慌。
爬虫合法性的问题一定会被问到。回答思路是:只采集公开信息,控制请求频率,遵守网站的robots.txt协议,不采集个人隐私数据,项目仅用于学习研究,数据量级远低于正常用户访问,不构成对目标站点的压力。态度诚恳、逻辑清晰就能过关。
为什么用MySQL不用MongoDB?回答要点是:新闻数据结构化固定,字段类型明确,关系型数据库在聚合统计和排序查询上有天然优势,SQL写起来比在文档数据库里做聚合要直接得多。
为什么用requests而不用Scrapy?回答要点是:数据量级在几万条左右,requests配合executemany批量入库完全够用;Scrapy的调度器、中间件体系对这个小项目来说属于额外复杂度;但你要表明自己了解Scrapy,能说出Scrapy的Spider、Pipeline、Middleware这些概念,会让老师觉得你是做过选型对比的,而不是只会一个工具。
数据量大了怎么办?这个追问考察你的扩展思路。可以从几个方向回答:爬虫层引入Scrapy和代理池,存储层引入分表或者迁移到ClickHouse,可视化层引入Redis做缓存,后端接口做异步任务和缓存策略。讲清楚思路即可,不需要真的实现。
整套项目做完之后,我个人最大的感受是:毕业设计的难点从来不在某个具体技术,而在于把一条完整的数据链路从头到尾跑通。爬虫采集、数据清洗、入库、聚合分析、接口开发、可视化展示,每一步单独拿出来都不算难,但组合在一起需要的工程能力和排错经验,恰恰是平时课堂上学不到的东西。如果你正打算做类似的项目,不妨按我这条链路一步一步搭建,遇到问题记住先看响应内容、再查编码、最后查SQL,绝大部分坑都能靠这三板斧解决。最后再分享一个小技巧:录制演示视频的时候,建议把大屏页面的操作放在最后录,并且录的时候保持鼠标移动平稳、切换图表自然,这样即使答辩现场出了突发状况,视频也能成为你的兜底方案。
本文还有配套的精品资源,点击获取