1. 先搞清楚这个项目到底能帮你解决什么实际问题
如果你正在学习Python数据分析,或者想找一个完整的实战项目来练手,这个Flask+requests+Pandas的猫眼电影数据分析系统值得重点关注。它最大的价值不是单个技术点的演示,而是把数据采集、清洗、分析、存储到可视化展示的完整链路串起来了。
很多人在学完Pandas或Flask后,最大的困惑就是不知道如何把这些技术组合成一个实际可用的系统。这个项目正好填补了这个空白:它教你如何从猫眼电影网站抓取真实数据,用Pandas进行清洗分析,最后通过Flask搭建一个带图表展示的Web应用。
我建议先关注这几个核心能力点:
- 数据采集:如何用requests库稳定抓取猫眼电影列表和详情页数据
- 数据清洗:Pandas处理真实数据时的常见问题和解法
- 可视化集成:Flask如何与ECharts配合实现动态图表
- 工程化思维:从单脚本到完整Web系统的代码组织方式
2. 环境准备:别在配置环节卡住
开始前需要确保你的开发环境满足基本要求。我一般会先检查这几个点:
2.1 基础环境要求
- Python 3.7+(推荐3.8或3.9,兼容性更好)
- MySQL 5.7+或8.0(数据库版本影响不大,但要注意认证方式)
- 操作系统:Windows/macOS/Linux都可以,但路径处理稍有差异
2.2 核心依赖库版本
安装时最容易出问题的是环境冲突。如果你用Anaconda,建议创建独立环境:
BASH
1
conda create -n movie_analysis python=3.9
2
conda activate movie_analysis
3
pip install -r requirements.txt
如果遇到ModuleNotFoundError: No module named 'requests'这类错误,先检查虚拟环境是否激活,再确认pip list里是否有对应包。
3. 数据采集:从单页抓取到批量爬虫的稳妥路径
猫眼电影网站有反爬机制,直接暴力抓取很容易触发429错误(Too Many Requests)。根据实测经验,我建议按这个顺序来:
3.1 先搞定单页请求
PYTHON
2
from bs4 import BeautifulSoup
7
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
8
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
11
def get_single_page(url):
14
time.sleep(random.uniform(1, 3))
15
response = requests.get(url, headers=headers, timeout=10)
16
if response.status_code == 200:
19
print(f"请求失败,状态码:{response.status_code}")
21
except Exception as e:
关键点在于:
- User-Agent要模拟真实浏览器
- 每次请求间隔1-3秒,不要连续快速请求
- 设置超时时间,避免卡死
- 做好异常处理,记录失败原因
3.2 解析页面数据的稳妥方法
PYTHON
1
def parse_movie_list(html):
2
soup = BeautifulSoup(html, 'html.parser')
6
items = soup.select('.movie-item')
9
name = item.select_one('.movie-name').text.strip()
10
score = item.select_one('.score').text.strip() if item.select_one('.score') else '暂无评分'
18
except AttributeError as e:
19
print(f"解析字段出错:{e},跳过该条目")
这里最容易踩的坑是网页结构变化。如果发现抓不到数据,先用浏览器开发者工具检查当前页面的HTML结构,确认选择器是否还有效。
3.3 批量抓取时的容错处理
PYTHON
1
def batch_crawl(start_page=1, end_page=10):
5
for page in range(start_page, end_page + 1):
6
url = f'https://maoyan.com/films?offset={(page-1)*30}'
7
html = get_single_page(url)
10
movies = parse_movie_list(html)
12
all_movies.extend(movies)
13
print(f"第{page}页采集成功,获取{len(movies)}部电影")
15
print(f"第{page}页解析失败")
16
failed_pages.append(page)
18
print(f"第{page}页请求失败")
19
failed_pages.append(page)
23
save_to_csv(all_movies, f'movies_page_{page}.csv')
25
return all_movies, failed_pages
批量采集时一定要有断点续采机制。不要等全部页面采完才保存,应该按批次持久化到文件或数据库。
4. 数据清洗:用Pandas处理真实数据的实用技巧
原始爬虫数据往往存在各种问题,直接分析会得到错误结果。Pandas清洗时要重点关注这几个方面:
4.1 读取和初步检查
PYTHON
3
def load_and_check_data(csv_file):
5
df = pd.read_csv(csv_file, encoding='utf-8')
8
print(f"总行数:{len(df)}")
9
print(f"列名:{df.columns.tolist()}")
17
print(df.isnull().sum())
先整体了解数据状况,再决定清洗策略。常见的猫眼电影数据问题包括:
- 评分字段混有"暂无评分"文本
- 票房数据包含"万""亿"等单位
- 上映时间格式不统一
- 主演/导演字段包含多余标签
4.2 针对性清洗处理
PYTHON
1
def clean_movie_data(df):
6
cleaned_df['score'] = pd.to_numeric(cleaned_df['score'], errors='coerce')
9
def parse_box_office(value):
10
if pd.isna(value) or value == '':
13
return float(value.replace('亿', '')) * 10000
15
return float(value.replace('万', ''))
18
cleaned_df['box_office'] = cleaned_df['box_office'].apply(parse_box_office)
21
cleaned_df['release_time'] = pd.to_datetime(cleaned_df['release_time'], errors='coerce')
24
text_columns = ['name', 'actor', 'director', 'type']
25
for col in text_columns:
26
cleaned_df[col] = cleaned_df[col].astype(str).str.strip()
29
cleaned_df = cleaned_df.dropna(how='all')
清洗后要验证效果:
PYTHON
1
def validate_cleaned_data(cleaned_df):
3
print(f"有效电影数量:{len(cleaned_df)}")
4
print(f"有评分电影:{cleaned_df['score'].notna().sum()}")
5
print(f"有票房数据:{cleaned_df['box_office'].notna().sum()}")
8
if cleaned_df['score'].notna().any():
9
print(f"评分范围:{cleaned_df['score'].min():.1f} - {cleaned_df['score'].max():.1f}")
11
if cleaned_df['box_office'].notna().any():
12
print(f"票房范围:{cleaned_df['box_office'].min():.0f} - {cleaned_df['box_office'].max():.0f} 万元")
5. 数据存储:MySQL表设计和批量写入优化
清洗好的数据需要持久化存储,为后续分析和可视化提供稳定数据源。
5.1 数据库表结构设计
SQL
2
id INT AUTO_INCREMENT PRIMARY KEY,
3
name VARCHAR(255) NOT NULL COMMENT '电影名称',
4
type VARCHAR(100) NOT NULL COMMENT '电影类型',
5
actor TEXT NOT NULL COMMENT '主演',
6
director VARCHAR(255) NOT NULL COMMENT '导演',
7
release_time DATE NOT NULL COMMENT '上映时间',
8
country VARCHAR(100) NOT NULL COMMENT '国家/地区',
9
score FLOAT COMMENT '评分',
10
box_office DECIMAL(15,2) COMMENT '票房(万元)',
11
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
12
UNIQUE KEY unique_movie (name, release_time)
13
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
设计要点:
- 设置唯一约束,避免重复插入相同电影
- 票房字段用DECIMAL确保精度
- 使用utf8mb4字符集支持所有中文
- 添加创建时间字段用于追踪
5.2 批量写入的Python实现
PYTHON
1
from sqlalchemy import create_engine
4
def save_to_mysql(df, table_name='movies'):
6
engine = create_engine('mysql+pymysql://username:password@localhost:3306/movie_analysis')
13
for i in range(0, total_rows, chunk_size):
14
chunk = df.iloc[i:i+chunk_size]
16
chunk.to_sql(table_name, engine, if_exists='append', index=False)
17
print(f"已写入 {min(i+chunk_size, total_rows)}/{total_rows} 行")
21
except Exception as e:
批量写入时要注意:
- 使用SQLAlchemy而不是直接pymysql,更方便处理数据类型转换
- 分批写入避免内存溢出
- 做好异常处理,记录失败情况
- 首次运行使用if_exists='replace',后续用'append'
6. Flask Web应用搭建:从静态页面到动态可视化
有了数据基础,接下来用Flask构建Web应用展示分析结果。
6.1 基础Flask应用结构
TEXT
5
│ ├── charts.html # 图表页面
6
│ └── layout.html # 基础模板
12
│ ├── database.py # 数据库操作
13
│ └── analysis.py # 数据分析
14
└── requirements.txt # 依赖列表
6.2 核心路由设计
PYTHON
1
from flask import Flask, render_template, jsonify, request
3
from utils.database import get_db_connection
10
return render_template('index.html')
12
@app.route('/api/movies')
15
page = request.args.get('page', 1, type=int)
16
limit = request.args.get('limit', 20, type=int)
17
offset = (page - 1) * limit
19
conn = get_db_connection()
22
total = conn.execute('SELECT COUNT(*) FROM movies').fetchone()[0]
25
movies = conn.execute(
26
'SELECT * FROM movies ORDER BY release_time DESC LIMIT ? OFFSET ?',
31
result = [dict(movie) for movie in movies]
42
@app.route('/api/box_office_rank')
43
def box_office_rank():
45
year = request.args.get('year', '2023')
46
top_n = request.args.get('top_n', 10, type=int)
48
conn = get_db_connection()
51
SELECT name, box_office
53
WHERE strftime('%Y', release_time) = ? AND box_office > 0
54
ORDER BY box_office DESC
57
result = conn.execute(query, (year, top_n)).fetchall()
60
'names': [row['name'] for row in result],
61
'values': [float(row['box_office']) for row in result]
64
return jsonify({'code': 0, 'data': data})
6.3 前端与ECharts集成
在templates/charts.html中:
HTML
6
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
16
<div id="boxOfficeChart" class="chart-container"></div>
20
var chart = echarts.init(document.getElementById('boxOfficeChart'));
23
fetch('/api/box_office_rank?year=2023&top_n=10')
24
.then(response => response.json())
26
if (data.code === 0) {
28
title: { text: '2023年电影票房TOP10' },
32
data: data.data.names,
33
axisLabel: { rotate: 45 }
35
yAxis: { type: 'value', name: '票房(万元)' },
39
data: data.data.values
42
chart.setOption(option);
47
window.addEventListener('resize', function() {
7. 常见问题排查:从启动失败到图表不显示的解决方案
在实际运行过程中,你可能会遇到这些问题:
7.1 应用启动失败
问题现象:Error: Could not locate a Flask application.
排查步骤:
- 检查app.py中是否正确定义了Flask应用实例
- 确认环境变量FLASK_APP设置正确:
export FLASK_APP=app.py
- 检查是否有语法错误或导入问题
7.2 数据库连接失败
问题现象:pymysql.err.OperationalError: (2003, "Can't connect to MySQL server")
解决方案:
PYTHON
2
def get_db_connection():
4
conn = pymysql.connect(
7
password='your_password',
8
database='movie_analysis',
12
except Exception as e:
确保MySQL服务已启动,数据库和用户已创建,权限配置正确。
7.3 图表数据不显示
问题现象:页面正常打开,但图表区域空白
排查顺序:
- 打开浏览器开发者工具,查看Network标签页中API请求是否成功
- 检查API返回的数据格式是否符合ECharts要求
- 确认JavaScript中没有语法错误
- 查看ECharts版本是否兼容
7.4 爬虫被限制
问题现象:频繁收到429状态码(Too Many Requests)
应对策略:
- 降低请求频率,增加随机延迟
- 使用代理IP轮询
- 模拟更真实的浏览器行为
- 考虑使用官方API(如果提供)
8. 项目扩展思路:从学习demo到实用工具
完成基础功能后,可以考虑这些扩展方向:
8.1 数据维度扩展
- 增加用户评论情感分析
- 集成豆瓣评分对比
- 添加电影预告片和海报
- 收集导演、演员的历史作品数据
8.2 分析功能增强
- 电影类型与票房关系分析
- 上映时间与票房规律分析
- 演员号召力量化评估
- 地区市场偏好分析
8.3 系统性能优化
- 添加数据缓存机制(Redis)
- 实现异步数据更新
- 增加数据导出功能
- 优化前端加载速度
8.4 部署上线考虑
- 使用Gunicorn替代Flask开发服务器
- 配置Nginx反向代理
- 设置定期数据更新任务
- 添加用户权限管理
这个项目的价值在于它提供了一个完整的数据处理流水线样板。你可以基于这个框架,替换数据源和分析目标,快速搭建其他类似的分析系统。比如把猫眼电影换成豆瓣读书、电商商品、新闻数据等,核心的技术栈和架构思路都是相通的。
最重要的是先让基础版本跑起来,再根据实际需求逐步添加功能。不要一开始就追求完美,先把数据采集、清洗、展示的闭环打通,后续的优化就有明确方向了。