1. 先搞清楚这个项目到底解决什么问题
这个项目标题看起来很长,但核心其实就三件事:用 Flask 做 Web 框架、用 requests 抓取猫眼电影数据、用 Pandas 和可视化库做数据分析展示。它最适合的是需要快速完成一个完整数据分析项目的人——比如毕业设计、简历项目、或者想验证自己 Python 数据爬取和分析能力的学习者。
我最开始接触这类项目时发现,很多人容易陷入两个误区:要么花太多时间在爬虫细节上,结果数据分析部分草草了事;要么直接套用现成代码,但完全不懂每个环节为什么要这样设计。这个项目的价值在于,它把数据获取、处理、展示串成了一个可落地的闭环,而且用的都是 Python 生态里最基础的库,不需要额外学习复杂框架。
但要注意,猫眼这类网站对爬虫有一定限制,直接高频请求很容易遇到 429 状态码(请求过多)。所以这个项目真正考验的不是你能爬多少数据,而是如何稳定获取足够分析的样本量,并且把分析逻辑讲清楚。
2. 环境准备:别在依赖版本上踩坑
我建议先用虚拟环境隔离项目依赖,避免和系统其他 Python 项目冲突。如果你用 Anaconda,可以这样创建:
BASH
1
conda create -n maoyan python=3.8
如果不用 Conda,也可以用 venv:
BASH
1
python -m venv maoyan_env
2
source maoyan_env/bin/activate
然后安装核心依赖。这里要特别注意版本兼容性,很多教程不写版本,结果跑起来各种报错:
BASH
1
pip install flask==2.3.3
2
pip install requests==2.31.0
3
pip install pandas==1.5.3
4
pip install pyecharts==2.0.3
为什么选这些版本?因为这是经过大量项目验证的相对稳定组合。Flask 2.3.3 兼容性好,requests 2.31.0 修复了一些安全漏洞,pandas 1.5.3 在数据处理上足够稳定,pyecharts 2.0.3 的接口比较成熟。如果你非要装最新版,就要做好调试兼容性问题的准备。
项目结构我建议这样安排:
TEXT
16
└── processed/ # 处理后的数据
这种结构的好处是功能分离清晰,后续要加缓存、加数据库都容易扩展。
3. 爬虫部分:如何稳定获取数据而不被封
猫眼电影的数据接口其实比较规范,但关键是要模拟真实浏览器请求,并且控制请求频率。直接连续快速请求的话,很快会收到 429 Too Many Requests 错误。
先看一个最基础的请求示例:
PYTHON
5
def get_movie_data(page=1):
7
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
8
'Accept': 'application/json, text/plain, */*',
9
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
10
'Connection': 'keep-alive'
14
time.sleep(random.uniform(1, 3))
16
url = f'https://maoyan.com/ajax/films?page={page}'
19
response = requests.get(url, headers=headers, timeout=10)
20
if response.status_code == 200:
21
return response.json()
23
print(f'请求失败,状态码:{response.status_code}')
25
except requests.exceptions.RequestException as e:
这里有几个关键点:
- User-Agent 要用常见的浏览器标识,不要用默认的 Python-requests
- 每次请求前休眠 1-3 秒,避免触发频率限制
- 设置超时时间,避免卡死
- 做好异常处理,网络请求失败是常态
如果要爬取多页数据,我建议不要一次性爬完所有页面。可以先爬 5-10 页测试整个流程,数据分析阶段如果发现样本不够再补充。毕业设计项目通常有 200-300 条电影数据就足够做有意义的分析了。
爬取到的原始数据建议立即保存为 JSON 文件:
PYTHON
2
from datetime import datetime
4
def save_raw_data(data, page):
5
filename = f"data/raw/maoyan_page_{page}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
6
with open(filename, 'w', encoding='utf-8') as f:
7
json.dump(data, f, ensure_ascii=False, indent=2)
这样即使后续分析程序出错,原始数据也不会丢失,而且有时间戳便于管理不同批次的数据。
4. 数据清洗:用 Pandas 处理真实世界的数据
爬取到的电影数据通常不会完美符合分析需求,需要清洗和转换。这就是 Pandas 发挥价值的地方。
假设我们爬取到的数据结构是这样的:
JSON
7
"releaseDate": "2024-01-01",
首先要把 JSON 数据转换为 Pandas DataFrame:
PYTHON
4
def load_and_clean_data():
6
raw_files = [f for f in os.listdir('data/raw') if f.endswith('.json')]
10
with open(f'data/raw/{file}', 'r', encoding='utf-8') as f:
12
if data and 'data' in data and 'films' in data['data']:
13
all_films.extend(data['data']['films'])
16
df = pd.DataFrame(all_films)
20
df['score'] = pd.to_numeric(df['score'], errors='coerce')
23
df['box_office'] = df['boxInfo'].str.extract(r'(\d+\.?\d*)')[0]
24
df['box_office'] = pd.to_numeric(df['box_office'], errors='coerce')
26
df.loc[df['boxInfo'].str.contains('亿', na=False), 'box_office'] = df['box_office'] * 100000000
29
df['release_date'] = pd.to_datetime(df['releaseDate'], errors='coerce')
32
type_split = df['type'].str.split('/', expand=True)
33
df['main_type'] = type_split[0]
36
actors_split = df['actors'].str.split(',', expand=True)
37
df['actor1'] = actors_split[0]
38
df['actor2'] = actors_split[1]
41
clean_df = df[['name', 'score', 'release_date', 'box_office', 'main_type', 'actor1', 'actor2']]
44
clean_df.to_csv('data/processed/cleaned_movie_data.csv', index=False, encoding='utf-8')
数据清洗时最容易出错的地方是类型转换和异常值处理。比如票房数据可能有"1.2亿""3456万""暂无"等多种格式,要分别处理。评分可能是数字字符串,也可能是"暂无评分"。
我建议每步转换后都检查一下数据质量:
PYTHON
1
def check_data_quality(df):
2
print(f"总数据量:{len(df)}")
3
print(f"评分缺失:{df['score'].isna().sum()}")
4
print(f"票房缺失:{df['box_office'].isna().sum()}")
5
print(f"日期异常:{df['release_date'].isna().sum()}")
9
print(df[['score', 'box_office']].describe())
5. 数据分析:从基础统计到深入洞察
有了干净的数据,就可以开始真正的分析了。数据分析不是简单算几个平均数,而是要发现有意思的模式和洞察。
先做基础统计分析:
PYTHON
1
def basic_analysis(df):
4
'平均评分': df['score'].mean(),
5
'评分中位数': df['score'].median(),
6
'最高评分': df['score'].max(),
7
'最低评分': df['score'].min(),
8
'评分标准差': df['score'].std()
13
'总票房': df['box_office'].sum(),
14
'平均票房': df['box_office'].mean(),
15
'票房中位数': df['box_office'].median(),
16
'最高票房': df['box_office'].max()
20
type_distribution = df['main_type'].value_counts()
23
df['year'] = df['release_date'].dt.year
24
yearly_count = df['year'].value_counts().sort_index()
27
'score_stats': score_stats,
28
'box_office_stats': box_office_stats,
29
'type_distribution': type_distribution,
30
'yearly_count': yearly_count
但基础统计只是开始,更有价值的是关联分析:
PYTHON
1
def advanced_analysis(df):
3
score_box_corr = df[['score', 'box_office']].corr().iloc[0,1]
6
type_analysis = df.groupby('main_type').agg({
7
'score': ['mean', 'count'],
12
yearly_trend = df.groupby('year').agg({
16
}).rename(columns={'name': 'movie_count'})
19
'score_box_correlation': score_box_corr,
20
'type_analysis': type_analysis,
21
'yearly_trend': yearly_trend
数据分析的关键是要提出具体问题,比如:
- 高评分是否意味着高票房?
- 哪种类型的电影更受欢迎?
- 电影产量和质量有什么趋势?
- 有没有叫好不叫座或者叫座不叫好的电影?
6. 可视化:用图表讲好数据故事
可视化不是简单的画图,而是要选择合适的图表类型来回答问题。我推荐使用 PyEcharts,因为它的交互性更好,适合 Web 展示。
首先配置 PyEcharts 与 Flask 的集成:
PYTHON
1
from pyecharts import options as opts
2
from pyecharts.charts import Bar, Pie, Line, Scatter
3
from pyecharts.globals import ThemeType
5
def create_score_distribution_chart(df):
7
score_ranges = ['0-2', '2-4', '4-6', '6-8', '8-10']
9
len(df[(df['score'] >= 0) & (df['score'] < 2)]),
10
len(df[(df['score'] >= 2) & (df['score'] < 4)]),
11
len(df[(df['score'] >= 4) & (df['score'] < 6)]),
12
len(df[(df['score'] >= 6) & (df['score'] < 8)]),
13
len(df[(df['score'] >= 8) & (df['score'] <= 10)])
17
Bar(init_opts=opts.InitOpts(theme=ThemeType.MACARONS))
18
.add_xaxis(score_ranges)
19
.add_yaxis("电影数量", score_counts)
21
title_opts=opts.TitleOpts(title="电影评分分布"),
22
xaxis_opts=opts.AxisOpts(name="评分区间"),
23
yaxis_opts=opts.AxisOpts(name="电影数量")
28
def create_type_distribution_chart(type_distribution):
31
Pie(init_opts=opts.InitOpts(theme=ThemeType.MACARONS))
34
[list(z) for z in zip(type_distribution.index.tolist(), type_distribution.values.tolist())],
38
title_opts=opts.TitleOpts(title="电影类型分布"),
39
legend_opts=opts.LegendOpts(orient="vertical", pos_top="15%", pos_left="2%")
41
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c} ({d}%)"))
45
def create_score_vs_boxoffice_chart(df):
48
Scatter(init_opts=opts.InitOpts(theme=ThemeType.MACARONS))
49
.add_xaxis(df['score'].tolist())
50
.add_yaxis("票房", df['box_office'].tolist())
52
title_opts=opts.TitleOpts(title="电影评分与票房关系"),
53
xaxis_opts=opts.AxisOpts(name="评分"),
54
yaxis_opts=opts.AxisOpts(name="票房(元)"),
55
tooltip_opts=opts.TooltipOpts(
58
return '评分: ' + params.value[0] + '<br/>票房: ' +
59
(params.value[1] / 100000000).toFixed(2) + '亿元';
可视化时要考虑用户体验:
- 重要图表放在前面
- 添加清晰的标题和标签
- 考虑颜色对比度
- 交互式提示信息要友好
- 响应式设计适应不同屏幕
7. Flask 集成:把分析结果变成 Web 应用
现在把各个模块整合到 Flask 应用中。Flask 的优势是轻量灵活,适合这种数据展示类项目。
基本应用结构:
PYTHON
1
from flask import Flask, render_template
9
df = load_and_clean_data()
12
basic_results = basic_analysis(df)
13
advanced_results = advanced_analysis(df)
16
score_chart = create_score_distribution_chart(df)
17
type_chart = create_type_distribution_chart(basic_results['type_distribution'])
18
scatter_chart = create_score_vs_boxoffice_chart(df)
22
'score_chart': score_chart.dump_options(),
23
'type_chart': type_chart.dump_options(),
24
'scatter_chart': scatter_chart.dump_options()
27
return render_template(
29
basic_stats=basic_results,
30
advanced_stats=advanced_results,
31
charts_json=json.dumps(charts_json)
34
if __name__ == '__main__':
35
app.run(debug=True, host='0.0.0.0', port=5000)
模板文件 templates/index.html 的基本结构:
HTML
5
<title>猫眼电影数据分析</title>
6
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
8
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
9
.chart-container { height: 400px; margin: 20px 0; }
10
.stats-card { background: #f5f5f5; padding: 15px; margin: 10px 0; border-radius: 5px; }
14
<div class="container">
17
<div class="stats-card">
19
<p>平均评分: {{ basic_stats.score_stats.平均评分 | round(2) }}</p>
20
<p>总票房: {{ (basic_stats.box_office_stats.总票房 / 100000000) | round(2) }} 亿元</p>
23
<div class="chart-container" id="scoreChart"></div>
24
<div class="chart-container" id="typeChart"></div>
25
<div class="chart-container" id="scatterChart"></div>
29
var chartsData = {{ charts_json | safe }};
31
var scoreChart = echarts.init(document.getElementById('scoreChart'));
32
scoreChart.setOption(JSON.parse(chartsData.score_chart));
34
var typeChart = echarts.init(document.getElementById('typeChart'));
35
typeChart.setOption(JSON.parse(chartsData.type_chart));
37
var scatterChart = echarts.init(document.getElementById('scatterChart'));
38
scatterChart.setOption(JSON.parse(chartsData.scatter_chart));
41
window.addEventListener('resize', function() {
44
scatterChart.resize();
8. 项目优化和扩展思路
基础功能完成后,可以考虑这些优化方向:
性能优化:
- 添加数据缓存,避免每次访问都重新爬取和分析
- 使用 Redis 或文件缓存存储处理好的数据
- 对静态资源使用 CDN 加速
功能扩展:
- 添加时间范围筛选
- 支持按类型、评分区间过滤
- 添加电影搜索功能
- 导出分析报告功能
代码优化:
- 添加错误处理和日志记录
- 配置化管理请求参数、数据库连接等
- 编写单元测试
- 使用代码质量工具(flake8、black)
部署考虑:
- 使用 Gunicorn 代替 Flask 开发服务器
- 配置 Nginx 反向代理
- 环境变量管理敏感信息
- 使用 Docker 容器化部署
对于毕业设计或简历项目,我建议先保证核心功能稳定,再选择 1-2 个有特色的扩展功能深入实现,这样既能展示技术能力,又不会过度工程化。
9. 常见问题排查指南
在实际运行中,你可能会遇到这些问题:
爬虫相关问题:
- 429 Too Many Requests:降低请求频率,添加随机延迟,使用代理 IP
- 数据解析错误:检查网页结构是否变化,更新解析逻辑
- 编码问题:统一使用 UTF-8 编码
数据分析问题:
- 内存不足:分批处理大数据集,使用 Pandas 的 chunksize 参数
- 计算速度慢:使用向量化操作代替循环,考虑使用 Dask 处理大数据
Flask 应用问题:
- 模板找不到:检查 templates 目录结构和文件名
- 静态资源404:确认 static 目录配置正确
- 图表不显示:检查浏览器控制台错误,确认 ECharts 加载成功
部署问题:
- 端口被占用:更换端口或杀死占用进程
- 外部无法访问:检查防火墙设置,确保绑定 0.0.0.0
遇到问题时,按这个顺序排查:先看日志错误信息,再检查数据流(爬取→清洗→分析→展示),最后验证环境配置。
10. 项目价值体现和简历表达
完成这个项目后,要在简历中有效展示你的能力。不要只写"做了电影数据分析项目",而要具体说明:
- 技术栈:Python、Flask、Requests、Pandas、PyEcharts、HTML/CSS/JS
- 数据规模:处理了多少条电影数据,包含哪些维度
- 分析深度:进行了描述统计、关联分析、趋势分析等
- 可视化效果:实现了交互式图表、响应式设计
- 工程化考虑:模块化设计、错误处理、性能优化
面试时可能会问:
- 为什么选择这些技术栈?
- 遇到的最大挑战是什么?
- 如何保证数据的准确性?
- 如果数据量增加10倍,系统如何扩展?
对这些问题的准备,比项目本身更重要。
这个项目的真正价值不在于用了多高级的技术,而在于完整展示了数据从获取到展示的全流程,并且每个环节都有明确的技术选型理由和实现考虑。这种端到端的项目经验,在实际工作中比孤立的技术点更有价值。