资讯详情

资讯详情

Python爬虫+Flask+ECharts:豆瓣电影数据分析可视化毕设实战

简介这份资源是面向计算机专业学生与Python初学者的一套完整毕业设计项目主题为豆瓣电影数据分析与可视化采用Flask搭建Web后端、ECharts完成图表呈现并结合爬虫与pandas实现从数据采集、清洗处理到可视化展示的完整链路数据库使用MySQL适合用作课程设计、毕设参考或数据分析入门实战。压缩包共1054个文件约30.07MB以688个py源码文件为主体辅以pyc编译文件、html与js前端页面、jpg与png图片素材、csv数据样本及sql建库脚本另含论文文档与依赖安装相关文件结构完整便于直接运行与二次开发。目前已有257人学习下载。项目覆盖爬虫抓取、数据处理、多角度动态图表展示等环节可帮助读者理解数据分析全流程并借鉴其前后端组织方式与可视化实现思路。1. 从一份能跑起来的毕设说起豆瓣电影数据从爬到看的一条龙毕业设计最怕的不是不会写代码而是代码写完跑不起来、数据拿不到、图表画出来是空的。这套基于 Python 的豆瓣电影数据分析与可视化系统解决的正是这个场景用爬虫把电影数据抓下来用 pandas 清洗分析用 Flask 起一个 Web 服务前端用 ECharts 把评分分布、类型占比、年份趋势这些维度画成图最后配一份 SQL 脚本和论文形成一个能演示、能答辩、能二次开发的完整闭环。它适合两类人一是时间紧、需要一套能跑通的全栈毕设骨架的同学二是想练手「爬虫 数据分析 Web 可视化」这条链路的开发者。技术栈不花哨但每一环都是真实项目里会用到的跑通之后你对数据从采集到呈现的整条链路会有具体的手感而不是停留在调包层面。2. 技术选型与项目骨架Flask 为什么比 Django 更适合这类毕设2.1 四层结构各管什么这类系统的分层其实很清晰我一般会把它拆成四块采集层、存储层、分析层、展示层。采集层是爬虫负责把电影列表、详情、评分、评论这些数据抓回来存储层用 SQLite 或 MySQL配一份建表和插入的 SQL 脚本分析层用 pandas 做清洗和聚合算出类型分布、评分区间、年代趋势这些指标展示层是 Flask 提供接口、ECharts 在前端渲染。四层之间靠数据文件或数据库表衔接耦合度低改哪一层都不容易把别的层带崩。选 Flask 而不是 Django核心原因是这个项目不需要用户体系、不需要后台管理、不需要 ORM 全家桶。Flask 一个app.py加几个路由就能把接口跑起来路由返回 JSON前端 fetch 一下就能画图学习成本和调试成本都低。Django 那套 settings、app 注册、模板继承对毕设体量来说是负担。常见做法是 Flask 只做 API 服务页面用静态 HTML ECharts CDN 引入前后端职责分明答辩时讲起来也顺。2.2 目录结构长什么样一个能直接跑的版本目录大致是这样组织的douban_movie_system/ ├── app.py # Flask 入口注册路由 ├── config.py # 数据库连接、路径配置 ├── requirements.txt # 依赖清单 ├── spider/ │ ├── movie_spider.py # 爬虫主逻辑 │ └── parse.py # 页面解析函数 ├── analysis/ │ ├── clean.py # pandas 清洗 │ └── stats.py # 聚合统计产出图表数据 ├── static/ │ ├── js/echarts.min.js │ └── css/style.css ├── templates/ │ ├── index.html # 首页总览 │ └── detail.html # 单部电影详情 ├── data/ │ └── movies.csv # 爬取落地的原始数据 └── sql/ └── init.sql # 建表 示例数据这个结构的好处是每一块都能单独跑爬虫单独执行生成 CSV分析脚本读 CSV 产出 JSONFlask 读 JSON 或查库出接口。调试时不用每次都把整条链路拉起来哪一步出问题就单独盯哪一步这是我在项目里反复强调的习惯。2.3 依赖与版本约束requirements.txt里核心就几个包版本不要追新追新容易在爬虫和 pandas 上翻车Flask2.3.3 requests2.31.0 beautifulsoup44.12.2 pandas2.0.3 lxml4.9.3requests负责发请求beautifulsoup4配lxml解析器做 HTML 解析pandas做数据清洗和聚合。这里有个血泪经验pandas 2.x 和 1.x 在部分 API 上有差异比如append在 2.0 已经移除网上很多老教程还在用df.append()直接报错。我一般统一用pd.concat()兼容性稳。另外lxml是 C 扩展Windows 上装不上时优先换html.parser虽然慢一点但能跑别在环境上卡太久。3. 爬虫与数据落地请求、解析、存储三段式怎么写3.1 请求头与翻页控制爬虫第一件事是把请求伪装好否则很容易拿到空页面或者被限流。核心不是多复杂的反爬对抗而是把 headers 带全、把请求间隔拉开import requests import time import random HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9, } def fetch_page(url, retry3): for i in range(retry): try: resp requests.get(url, headersHEADERS, timeout10) if resp.status_code 200: return resp.text except requests.RequestException: pass time.sleep(random.uniform(1.5, 3.0)) # 随机间隔降低请求频率 return Nonefetch_page做了三件事带 headers、设超时、失败重试。timeout10是必须的不设超时遇到慢响应会一直挂着retry3配合随机 sleep比固定间隔更稳。参数上random.uniform(1.5, 3.0)这个区间是我一般会用的太短容易触发限流太长抓几百条要等很久按数据量自己调。注意这里没有做并发毕设数据量通常几千条串行加间隔完全够用上多线程反而增加被封的风险得不偿失。3.2 列表页解析与字段抽取拿到 HTML 后用 BeautifulSoup 定位条目把标题、评分、链接、简介这些字段抽出来。解析函数要写得防御性强一点某个字段缺失不能让整条记录崩掉from bs4 import BeautifulSoup def parse_list(html): soup BeautifulSoup(html, lxml) items [] for node in soup.select(div.item): title_node node.select_one(span.title) rating_node node.select_one(span.rating_num) link_node node.select_one(a) items.append({ title: title_node.get_text(stripTrue) if title_node else , rating: float(rating_node.get_text(stripTrue)) if rating_node else 0.0, url: link_node[href] if link_node else , }) return itemsselect用的是 CSS 选择器比一层层find好维护。每个字段都做了if node else兜底缺失时给空串或 0这样后续 pandas 处理时不会因为None报类型错误。get_text(stripTrue)去掉首尾空白避免标题里混进换行。这里的选择器是示意实际页面结构会变改的时候只动select里的表达式就行解析逻辑不用动这也是把解析单独放一个函数的价值。3.3 落地 CSV 与入库两条路抓完的数据先落 CSV再决定要不要入库。CSV 的好处是肉眼可查、pandas 直接读调试阶段非常方便import pandas as pd def save_csv(records, pathdata/movies.csv): df pd.DataFrame(records) df.drop_duplicates(subset[title], inplaceTrue) # 按标题去重 df.to_csv(path, indexFalse, encodingutf-8-sig)encodingutf-8-sig是为了 Excel 打开不乱码带 BOM 头Windows 上很实用。drop_duplicates按标题去重因为翻页时可能有重复条目。如果要做入库sql/init.sql里建一张movies表字段和 CSV 列对齐然后用 pandas 的to_sql或逐条 insert 都行。我一般演示用 CSV答辩要求数据库时再切to_sql切换成本很低因为字段结构是一致的。4. pandas 分析与 ECharts 接口指标怎么算、JSON 怎么出4.1 清洗缺失值、类型、异常评分原始数据一定脏评分可能是空、年份可能是字符串、类型字段可能是多个值拼在一起。清洗这一步决定了后面图表准不准import pandas as pd def clean_data(pathdata/movies.csv): df pd.read_csv(path) df[rating] pd.to_numeric(df[rating], errorscoerce) # 非数字转 NaN df df.dropna(subset[rating]) # 丢掉无评分记录 df df[(df[rating] 0) (df[rating] 10)] # 过滤异常评分 df[year] df[year].astype(str).str.extract(r(\d{4})) # 从文本里抠年份 return dferrorscoerce把转不了的值变成 NaN 而不是抛异常这是清洗里最常用的手法。dropna只针对评分列其他列缺失可以保留。评分区间过滤是防脏数据比如抓到 0 分或 11 分这种明显不对的值。年份用正则从文本里抽四位数字因为原始字段常是「1994(中国大陆)」这种混合格式。每一步都对应一个具体的脏数据场景不是为写而写。4.2 三个核心统计口径图表数据来源就三类聚合算清楚这三类前端基本够用def build_stats(df): # 评分区间分布 bins [0, 6, 7, 8, 9, 10] labels [6分以下, 6-7分, 7-8分, 8-9分, 9-10分] rating_dist pd.cut(df[rating], binsbins, labelslabels).value_counts().sort_index() # 类型占比类型字段按分隔符拆开再统计 genre_series df[genre].dropna().str.split(/).explode() genre_top genre_series.value_counts().head(10) # 年代趋势 year_trend df.groupby(year)[rating].mean().dropna() return { rating_dist: {labels: rating_dist.index.tolist(), values: rating_dist.values.tolist()}, genre_top: {labels: genre_top.index.tolist(), values: genre_top.values.tolist()}, year_trend: {labels: year_trend.index.tolist(), values: year_trend.values.round(2).tolist()}, }pd.cut做分箱value_counts统计频次这是评分分布的标准写法。类型字段用str.split(/).explode()把「剧情/爱情」拆成两行再统计否则一个组合类型会被当成独立类别占比就失真了。年代趋势用groupby求均值round(2)保留两位小数前端显示更干净。返回结构统一成labels valuesECharts 的xAxis.data和series.data直接对接前端不用再做转换。4.3 Flask 接口与前端对接分析结果通过 Flask 路由暴露成 JSON前端 fetch 后塞给 EChartsfrom flask import Flask, jsonify, render_template from analysis.clean import clean_data from analysis.stats import build_stats app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/api/stats) def api_stats(): df clean_data() return jsonify(build_stats(df)) if __name__ __main__: app.run(debugTrue, port5000)jsonify会自动设置Content-Type: application/json前端fetch(/api/stats).then(r r.json())就能拿到对象。debugTrue开发时开改代码自动重载但部署或答辩演示时记得关掉否则出错页会暴露堆栈。端口默认 5000被占用就改port。前端 ECharts 初始化时把labels赋给xAxis.data、values赋给series[0].data一个柱状图或折线图就出来了剩下的就是调样式。5. 避坑与排查跑不起来时先看这几处5.1 爬虫返回空列表现象是parse_list返回[]CSV 里一条数据都没有。原因多半是页面结构变了或者请求被拦返回了验证页。解决分两步先把resp.text打印出来看前几百字符确认拿到的是正常 HTML 还是拦截页如果是拦截页补全 headers、加大 sleep 间隔如果是结构变了用浏览器开发者工具重新定位选择器只改select表达式。别一上来就怀疑代码逻辑九成是请求层或选择器的问题。5.2 pandas 读 CSV 中文乱码现象是read_csv后列名或内容变成乱码。原因是写入时编码和读取时编码不一致。解决是写入用utf-8-sig读取时显式指定pd.read_csv(path, encodingutf-8-sig)。如果 CSV 是别人给的 GBK 编码读的时候换encodinggbk。这个坑很常见尤其 Windows 上用 Excel 另存过的文件编码经常被改掉读之前先确认来源。5.3 ECharts 图表空白现象是页面出来了但图是空的。原因通常是接口没返回数据、字段名对不上或者 ECharts 容器没有高度。排查顺序浏览器 Network 面板看/api/stats返回的 JSON 是否有值有值就看前端取值路径是否和返回结构一致都正常就看图表容器的 CSS 有没有设heightECharts 容器高度为 0 时图不渲染这是新手最常踩的坑。给容器加个styleheight:400px基本就解决了。5.4 端口被占用启动失败现象是app.run报Address already in use。原因是 5000 端口被别的进程占了macOS 上尤其常见。解决是换端口app.run(port5001)或者找到占用进程杀掉。演示前我一般会先跑一次确认端口可用别到答辩现场才发现起不来这种翻车完全可以提前避免。6. 二次开发怎么改从换数据源到加一个新图表拿到这套代码后最值钱的改造方向是换数据源和加分析维度而不是重写框架。换数据源时只需要改spider/parse.py里的选择器和字段映射保持输出 CSV 的列名不变后面的清洗、统计、接口全都不用动这就是分层带来的好处。加图表时在build_stats里多算一个口径返回结构里加一个 key前端多初始化一个 ECharts 实例三步就能扩一个维度比如加「导演作品数 Top10」或者「评分与评论数散点图」。验证改造是否成功我习惯用一个小数据集先跑通全链路手动造 20 条 CSV 记录跑clean_data和build_stats看输出 JSON 结构对不对再起 Flask 看页面。小数据能快速暴露字段缺失、类型错误这类问题比拿几千条真实数据调试快得多。下面这个检查脚本可以常备from analysis.clean import clean_data from analysis.stats import build_stats df clean_data(data/movies.csv) print(清洗后行数:, len(df)) stats build_stats(df) for k, v in stats.items(): print(k, 标签数:, len(v[labels]), 数值数:, len(v[values]))行数为 0 说明清洗过滤太狠或数据源有问题标签数和数值数不一致说明聚合时索引对不齐这种问题在图表上表现为错位肉眼很难发现脚本一跑就露馅。我一般改完统计逻辑都强制走一遍这个检查比直接开页面看靠谱。还有一个容易被忽略的点是 SQL 脚本和代码的同步。sql/init.sql里的表结构如果和 CSV 列名不一致切数据库时就会插入失败。我的习惯是字段名只在一处定义CSV 表头和建表语句保持同名同序改一处就同步另一处别让两边各说各话。从那以后我每次动数据结构都先把建表脚本和 CSV 表头对一遍再往下写省下的排查时间远比这一步多。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →