Python爬虫实战:Flask+requests+Pandas猫眼电影数据采集与可视化分析
2026/9/7 21:47:04 网站建设 项目流程

这次我们来看一个完整的Python爬虫实战项目——基于Flask+requests+Pandas的猫眼电影数据爬取与可视化分析。这个项目不仅包含了数据采集的核心技术,还涉及Web服务搭建和数据分析可视化,适合想要系统学习Python爬虫和数据分析的开发者。

项目最核心的价值在于:一站式解决从数据获取到可视化展示的全流程。使用requests库进行网页数据抓取,通过Pandas进行数据清洗和分析,最后用Flask搭建Web服务展示可视化图表。整个项目代码结构清晰,依赖简单,在普通电脑上就能运行,非常适合作为爬虫入门到进阶的实战案例。

1. 核心能力速览

能力项说明
技术栈Python + Flask + requests + Pandas + Matplotlib
数据来源猫眼电影网站公开数据
主要功能电影数据爬取、数据清洗、数据分析、可视化图表生成
硬件要求普通PC即可,无需GPU,内存4GB以上
依赖管理pip安装标准库,无特殊环境要求
启动方式命令行启动Flask服务
可视化展示Web页面图表展示,支持多维度数据分析
适合场景爬虫学习、数据分析练习、毕业设计、个人项目

2. 适用场景与使用边界

这个项目特别适合以下几类开发者:

  • Python初学者想要通过实战项目巩固requests、Pandas等库的使用
  • 需要完成课程设计或毕业设计的学生
  • 想要学习完整数据流程(采集-清洗-分析-可视化)的开发者
  • 需要快速搭建数据展示原型的项目人员

使用边界提醒

  • 仅用于学习和技术交流目的
  • 爬取频率要合理,避免对目标网站造成压力
  • 遵守网站robots.txt协议
  • 不得将爬取数据用于商业用途
  • 注意数据版权和个人隐私保护

3. 环境准备与前置条件

在开始项目之前,需要确保开发环境满足以下要求:

3.1 系统要求

  • Windows 10/11、macOS 10.14+ 或 Ubuntu 18.04+
  • 至少4GB可用内存
  • 5GB可用磁盘空间

3.2 Python环境

  • Python 3.8或更高版本
  • pip包管理工具最新版

检查Python环境:

python --version pip --version

3.3 必要依赖包

项目需要以下Python库,建议使用虚拟环境安装:

# 创建虚拟环境(可选) python -m venv movie_env source movie_env/bin/activate # Linux/macOS movie_env\Scripts\activate # Windows # 安装核心依赖 pip install flask==2.3.3 pip install requests==2.31.0 pip install pandas==2.0.3 pip install matplotlib==3.7.2 pip install beautifulsoup4==4.12.2

4. 项目结构与代码实现

4.1 项目目录结构

movie_analysis/ ├── app.py # Flask主程序 ├── spider.py # 爬虫核心代码 ├── data_analysis.py # 数据分析模块 ├── templates/ # HTML模板 │ └── index.html ├── static/ # 静态资源 │ ├── css/ │ └── images/ ├── data/ # 数据存储 │ ├── raw_data.csv # 原始数据 │ └── cleaned_data.csv # 清洗后数据 └── charts/ # 生成图表

4.2 爬虫核心代码实现

首先实现数据爬取功能,使用requests库模拟浏览器请求:

import requests import pandas as pd from bs4 import BeautifulSoup import time import random class MaoyanSpider: def __init__(self): self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' } self.base_url = 'https://maoyan.com/films' def get_movie_list(self, offset=0): """获取电影列表数据""" url = f'{self.base_url}?offset={offset}' try: response = requests.get(url, headers=self.headers, timeout=10) response.raise_for_status() return self.parse_html(response.text) except requests.exceptions.RequestException as e: print(f"请求失败: {e}") return [] def parse_html(self, html): """解析HTML页面,提取电影信息""" soup = BeautifulSoup(html, 'html.parser') movies = [] # 解析电影列表 movie_items = soup.find_all('div', class_='movie-item') for item in movie_items: try: movie = {} # 提取电影名称 name_tag = item.find('span', class_='name') movie['name'] = name_tag.text if name_tag else '未知' # 提取评分 score_tag = item.find('span', class_='score') movie['score'] = float(score_tag.text) if score_tag else 0.0 # 提取上映时间 time_tag = item.find('span', class_='releasetime') movie['release_time'] = time_tag.text if time_tag else '未知' # 提取类型 type_tag = item.find('span', class_='type') movie['type'] = type_tag.text if type_tag else '未知' movies.append(movie) except Exception as e: print(f"解析电影信息失败: {e}") continue return movies def crawl_multiple_pages(self, page_count=10): """爬取多页数据""" all_movies = [] for page in range(page_count): print(f'正在爬取第{page+1}页...') movies = self.get_movie_list(offset=page*30) all_movies.extend(movies) # 添加随机延迟,避免请求过快 time.sleep(random.uniform(1, 3)) return all_movies

4.3 数据清洗与分析模块

使用Pandas进行数据清洗和统计分析:

import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from datetime import datetime class MovieAnalyzer: def __init__(self, data_file='data/raw_data.csv'): self.df = pd.read_csv(data_file) def clean_data(self): """数据清洗处理""" # 去除重复数据 self.df = self.df.drop_duplicates() # 处理缺失值 self.df['score'] = self.df['score'].fillna(0) self.df['type'] = self.df['type'].fillna('未知') # 提取年份信息 self.df['year'] = self.df['release_time'].str.extract(r'(\d{4})') self.df['year'] = pd.to_numeric(self.df['year'], errors='coerce') # 评分分段 self.df['score_level'] = pd.cut(self.df['score'], bins=[0, 3, 6, 8, 10], labels=['差评', '一般', '良好', '优秀']) return self.df def analyze_by_type(self): """按电影类型分析""" type_analysis = self.df.groupby('type').agg({ 'name': 'count', 'score': 'mean' }).rename(columns={'name': 'count', 'score': 'avg_score'}) return type_analysis.sort_values('count', ascending=False) def analyze_by_year(self): """按年份分析""" year_analysis = self.df.groupby('year').agg({ 'name': 'count', 'score': 'mean' }).rename(columns={'name': 'count', 'score': 'avg_score'}) return year_analysis.dropna() def create_visualizations(self): """创建可视化图表""" plt.style.use('seaborn-v0_8') fig, axes = plt.subplots(2, 2, figsize=(15, 12)) # 1. 电影类型分布 type_counts = self.df['type'].value_counts().head(10) axes[0,0].pie(type_counts.values, labels=type_counts.index, autopct='%1.1f%%') axes[0,0].set_title('电影类型分布') # 2. 评分分布直方图 axes[0,1].hist(self.df['score'], bins=20, alpha=0.7, color='skyblue') axes[0,1].set_xlabel('评分') axes[0,1].set_ylabel('数量') axes[0,1].set_title('评分分布') # 3. 年度电影数量 year_counts = self.df['year'].value_counts().sort_index() axes[1,0].plot(year_counts.index, year_counts.values, marker='o') axes[1,0].set_xlabel('年份') axes[1,0].set_ylabel('电影数量') axes[1,0].set_title('年度电影数量趋势') # 4. 类型与评分关系 type_score = self.df.groupby('type')['score'].mean().sort_values(ascending=False).head(10) axes[1,1].barh(type_score.index, type_score.values, color='lightgreen') axes[1,1].set_xlabel('平均评分') axes[1,1].set_title('各类型电影平均评分') plt.tight_layout() plt.savefig('charts/movie_analysis.png', dpi=300, bbox_inches='tight') plt.close()

4.4 Flask Web服务搭建

创建Flask应用来展示数据分析结果:

from flask import Flask, render_template, jsonify import pandas as pd import os app = Flask(__name__) @app.route('/') def index(): """首页展示数据分析结果""" try: # 加载分析结果 df = pd.read_csv('data/cleaned_data.csv') # 基础统计信息 total_movies = len(df) avg_score = df['score'].mean() max_score = df['score'].max() min_score = df['score'].min() # 类型统计 type_stats = df['type'].value_counts().head(5).to_dict() # 年度统计 year_stats = df['year'].value_counts().head(10).sort_index().to_dict() return render_template('index.html', total_movies=total_movies, avg_score=round(avg_score, 2), max_score=max_score, min_score=min_score, type_stats=type_stats, year_stats=year_stats) except Exception as e: return f"数据加载失败: {str(e)}" @app.route('/api/movie_data') def get_movie_data(): """提供电影数据API接口""" df = pd.read_csv('data/cleaned_data.csv') return jsonify(df.to_dict(orient='records')) @app.route('/api/type_analysis') def get_type_analysis(): """类型分析数据API""" df = pd.read_csv('data/cleaned_data.csv') type_analysis = df.groupby('type').agg({ 'name': 'count', 'score': 'mean' }).rename(columns={'name': 'count', 'score': 'avg_score'}) return jsonify(type_analysis.reset_index().to_dict(orient='records')) if __name__ == '__main__': # 确保目录存在 os.makedirs('data', exist_ok=True) os.makedirs('charts', exist_ok=True) os.makedirs('templates', exist_ok=True) os.makedirs('static/css', exist_ok=True) os.makedirs('static/images', exist_ok=True) app.run(host='127.0.0.1', port=5000, debug=True)

5. 完整执行流程

5.1 数据爬取执行

创建主执行文件main.py

from spider import MaoyanSpider from data_analysis import MovieAnalyzer import pandas as pd import os def main(): # 创建必要目录 os.makedirs('data', exist_ok=True) os.makedirs('charts', exist_ok=True) # 第一步:爬取数据 print("开始爬取猫眼电影数据...") spider = MaoyanSpider() movies = spider.crawl_multiple_pages(page_count=5) # 爬取5页数据 # 保存原始数据 df = pd.DataFrame(movies) df.to_csv('data/raw_data.csv', index=False, encoding='utf-8-sig') print(f"爬取完成,共获取{len(movies)}条数据") # 第二步:数据清洗分析 print("开始数据清洗和分析...") analyzer = MovieAnalyzer('data/raw_data.csv') cleaned_df = analyzer.clean_data() cleaned_df.to_csv('data/cleaned_data.csv', index=False, encoding='utf-8-sig') # 第三步:生成可视化图表 print("生成可视化图表...") analyzer.create_visualizations() # 显示分析结果 type_analysis = analyzer.analyze_by_type() year_analysis = analyzer.analyze_by_year() print("\n=== 分析结果摘要 ===") print(f"电影总数: {len(cleaned_df)}") print(f"平均评分: {cleaned_df['score'].mean():.2f}") print(f"电影类型数量: {cleaned_df['type'].nunique()}") print(f"数据时间跨度: {cleaned_df['year'].min()} - {cleaned_df['year'].max()}") print("\n热门电影类型TOP5:") print(type_analysis.head()) if __name__ == '__main__': main()

5.2 启动Web服务

创建启动脚本run.py

from app import app import webbrowser import threading import time def open_browser(): """自动打开浏览器""" time.sleep(2) webbrowser.open('http://127.0.0.1:5000') if __name__ == '__main__': # 在独立线程中打开浏览器 threading.Thread(target=open_browser).start() # 启动Flask应用 app.run(host='127.0.0.1', port=5000, debug=True)

6. 功能测试与效果验证

6.1 爬虫功能测试

测试爬虫的基本功能是否正常:

# test_spider.py import unittest from spider import MaoyanSpider class TestMaoyanSpider(unittest.TestCase): def setUp(self): self.spider = MaoyanSpider() def test_get_movie_list(self): """测试获取电影列表功能""" movies = self.spider.get_movie_list(offset=0) self.assertIsInstance(movies, list) if movies: # 如果有数据返回 self.assertIn('name', movies[0]) self.assertIn('score', movies[0]) def test_crawl_multiple_pages(self): """测试多页爬取功能""" movies = self.spider.crawl_multiple_pages(page_count=2) self.assertTrue(len(movies) <= 60) # 2页最多60条数据 if __name__ == '__main__': unittest.main()

6.2 数据分析验证

验证数据分析结果的正确性:

# test_analysis.py import pandas as pd from data_analysis import MovieAnalyzer def test_analysis(): # 创建测试数据 test_data = [ {'name': '电影A', 'score': 8.5, 'type': '动作', 'release_time': '2023-01-01'}, {'name': '电影B', 'score': 7.2, 'type': '喜剧', 'release_time': '2023-02-01'}, {'name': '电影C', 'score': 9.1, 'type': '动作', 'release_time': '2023-03-01'} ] df = pd.DataFrame(test_data) df.to_csv('test_data.csv', index=False) analyzer = MovieAnalyzer('test_data.csv') cleaned_df = analyzer.clean_data() print("数据清洗验证:") print(f"原始数据条数: {len(test_data)}") print(f"清洗后数据条数: {len(cleaned_df)}") print(f"是否有缺失值: {cleaned_df.isnull().sum().sum() == 0}") type_analysis = analyzer.analyze_by_type() print("\n类型分析验证:") print(type_analysis) test_analysis()

7. Web界面设计与展示

创建HTML模板文件templates/index.html

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>猫眼电影数据分析平台</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px; } .stat-card { background: #f8f9fa; padding: 20px; border-radius: 8px; text-align: center; } .chart-container { margin: 30px 0; } canvas { max-width: 100%; } </style> </head> <body> <div class="container"> <h1>🐱 猫眼电影数据分析平台</h1> <!-- 统计信息卡片 --> <div class="stats-grid"> <div class="stat-card"> <h3>电影总数</h3> <p style="font-size: 2em; color: #007bff;">{{ total_movies }}</p> </div> <div class="stat-card"> <h3>平均评分</h3> <p style="font-size: 2em; color: #28a745;">{{ avg_score }}</p> </div> <div class="stat-card"> <h3>最高评分</h3> <p style="font-size: 2em; color: #dc3545;">{{ max_score }}</p> </div> <div class="stat-card"> <h3>最低评分</h3> <p style="font-size: 2em; color: #ffc107;">{{ min_score }}</p> </div> </div> <!-- 可视化图表区域 --> <div class="chart-container"> <h2>📊 数据分析图表</h2> <img src="{{ url_for('static', filename='images/movie_analysis.png') }}" alt="电影数据分析图表" style="width: 100%;"> </div> <!-- 类型分布 --> <div class="chart-container"> <h2>🎬 电影类型分布</h2> <div id="typeChart"></div> </div> <!-- 数据表格 --> <div class="chart-container"> <h2>📋 电影数据列表</h2> <div id="movieTable"></div> </div> </div> <script> // 使用Chart.js或ECharts进行动态图表展示 // 这里可以添加JavaScript代码来动态加载图表 </script> </body> </html>

8. 常见问题与排查方法

8.1 爬虫相关问题

问题现象可能原因排查方式解决方案
请求返回403错误反爬虫机制触发检查请求头是否完整更新User-Agent,添加Referer
获取不到数据网页结构变化检查HTML解析逻辑更新选择器和解析方法
连接超时网络问题或网站限制检查网络连接增加超时时间,添加重试机制

8.2 数据分析问题

问题现象可能原因排查方式解决方案
数据清洗失败数据格式不一致检查原始数据格式添加数据验证和异常处理
图表生成失败依赖库版本问题检查matplotlib版本更新到兼容版本
内存占用过高数据量过大监控内存使用分块处理数据,使用生成器

8.3 Flask服务问题

问题现象可能原因排查方式解决方案
端口被占用5000端口已被使用检查端口占用情况更换端口号
模板找不到文件路径错误检查templates目录结构确保模板文件位置正确
静态资源404静态文件配置错误检查static目录验证静态文件路径配置

9. 性能优化与扩展建议

9.1 爬虫性能优化

# 优化后的爬虫类 class OptimizedMaoyanSpider(MaoyanSpider): def __init__(self): super().__init__() self.session = requests.Session() # 使用会话保持 def crawl_with_retry(self, url, max_retries=3): """带重试机制的爬取方法""" for attempt in range(max_retries): try: response = self.session.get(url, headers=self.headers, timeout=10) response.raise_for_status() return response.text except requests.exceptions.RequestException as e: if attempt == max_retries - 1: raise e time.sleep(2 ** attempt) # 指数退避 def async_crawl(self, page_count=10): """异步爬取提高效率""" import asyncio import aiohttp async def fetch_page(session, page): url = f'{self.base_url}?offset={page*30}' async with session.get(url, headers=self.headers) as response: return await response.text() async def main(): async with aiohttp.ClientSession() as session: tasks = [fetch_page(session, page) for page in range(page_count)] return await asyncio.gather(*tasks) return asyncio.run(main())

9.2 数据分析扩展

可以扩展的分析维度:

  • 情感分析:对电影评论进行情感倾向分析
  • 关联分析:分析导演、演员与票房的关系
  • 预测模型:基于历史数据预测电影评分
  • 实时监控:定时爬取最新数据更新分析结果

9.3 Web功能增强

# 增强的Flask应用 @app.route('/api/real_time_stats') def real_time_stats(): """实时统计接口""" df = pd.read_csv('data/cleaned_data.csv') # 实时计算各种统计指标 stats = { 'total_movies': len(df), 'avg_score': round(df['score'].mean(), 2), 'score_distribution': df['score_level'].value_counts().to_dict(), 'latest_movies': df.nlargest(5, 'year').to_dict('records') } return jsonify(stats) @app.route('/download/data') def download_data(): """数据下载接口""" return send_file('data/cleaned_data.csv', as_attachment=True, download_name='maoyan_movies.csv')

10. 项目部署与生产化

10.1 使用Docker部署

创建Dockerfile:

FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . EXPOSE 5000 CMD ["python", "app.py"]

创建docker-compose.yml:

version: '3.8' services: movie-analysis: build: . ports: - "5000:5000" volumes: - ./data:/app/data - ./charts:/app/charts restart: unless-stopped

10.2 添加日志监控

import logging from logging.handlers import RotatingFileHandler def setup_logging(): """配置日志系统""" logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ RotatingFileHandler('app.log', maxBytes=10485760, backupCount=3), logging.StreamHandler() ] ) # 在Flask应用中使用 @app.before_first_request def initialize(): setup_logging() logging.info("Flask应用启动完成")

这个完整的猫眼电影数据分析项目展示了从数据采集到可视化展示的全流程,每个环节都提供了详细的代码实现和说明。项目结构清晰,代码可读性强,非常适合作为Python爬虫和数据分析的学习案例。

在实际使用中,建议先小规模测试爬虫功能,确保遵守网站的使用条款。数据分析部分可以根据实际需求进行定制扩展,比如添加更多的统计维度或者更复杂的可视化图表。Flask Web服务提供了良好的数据展示界面,也可以进一步扩展为完整的数据分析平台。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询