Python爬虫实战:猫眼电影票房数据采集与可视化分析完整指南
这次我们来看一个Python爬虫实战项目——爬取猫眼电影票房数据并进行可视化分析。这个案例特别适合Python初学者和想要掌握爬虫核心技能的朋友,因为它涵盖了数据抓取、解析、存储和可视化的完整流程。
猫眼电影作为国内重要的电影信息平台,其票房数据对分析电影市场趋势很有价值。通过这个项目,你可以学会如何用Python从网站获取结构化数据,再用图表直观展示分析结果。整个过程不需要复杂的环境配置,普通电脑就能跑起来。
本文会带你完成从环境准备到数据可视化的全流程,重点解决几个关键问题:如何绕过常见的反爬机制、如何解析动态加载的数据、如何设计合理的请求频率避免被封IP,以及如何用matplotlib和pandas做直观的数据图表。每个步骤都会提供可运行的代码示例和效果验证方法。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 技术栈 | Python + requests + BeautifulSoup + pandas + matplotlib |
| 数据源 | 猫眼电影票房榜单 |
| 主要功能 | 数据抓取、数据解析、数据存储、可视化分析 |
| 硬件要求 | 普通电脑即可,无需GPU |
| 核心难点 | 反爬虫处理、数据解析、请求频率控制 |
| 输出形式 | CSV数据文件、柱状图、折线图等多种图表 |
| 适合场景 | 爬虫学习、数据分析练习、电影市场研究 |
2. 适用场景与使用边界
这个爬虫项目最适合Python爬虫入门学习。如果你是刚学完Python基础语法,想找一个有实际价值的练手项目,这个案例非常合适。它涵盖了爬虫的核心技术点:发送HTTP请求、解析HTML、处理反爬机制、数据清洗和存储。
在实际应用层面,这个工具可以用于:
- 电影市场趋势分析
- 竞品电影票房对比
- 节假日档期票房研究
- 导演或演员作品票房统计
但需要注意使用边界:
- 仅限个人学习和研究使用
- 必须控制请求频率,避免对目标网站造成压力
- 不得用于商业用途或大规模数据采集
- 遵守网站的robots.txt协议
- 尊重数据版权和网站服务条款
3. 环境准备与前置条件
开始之前,确保你的电脑已经准备好以下环境:
操作系统要求:
- Windows 10/11、macOS 10.14+ 或 Ubuntu 18.04+
- 至少4GB内存,10GB可用磁盘空间
Python环境配置:
# 检查Python版本,需要3.7及以上 python --version # 如果没有安装Python,从官网下载安装包 # 推荐使用Python 3.8或3.9版本必要库安装:
# 使用pip安装所需库 pip install requests beautifulsoup4 pandas matplotlib lxml # 如果下载速度慢,可以使用国内镜像源 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests beautifulsoup4 pandas matplotlib lxml开发工具准备:
- 代码编辑器:VSCode、PyCharm或Jupyter Notebook
- 浏览器开发者工具:用于分析网页结构
- 网络抓包工具:如浏览器F12开发者工具
4. 爬虫核心代码实现
4.1 网页结构分析
首先我们需要分析猫眼电影票房页面的结构。打开猫眼电影专业版票房页面,使用F12开发者工具查看网络请求和HTML结构。
关键发现:
- 票房数据通常通过API接口返回,而不是静态HTML
- 需要模拟浏览器请求头避免被识别为爬虫
- 数据格式为JSON,便于解析
4.2 请求头设置与反爬处理
import requests import json import time import pandas as pd from datetime import datetime, timedelta class MaoyanSpider: def __init__(self): self.session = requests.Session() # 设置请求头,模拟浏览器访问 self.headers = { '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', 'Referer': 'https://piaofang.maoyan.com/dashboard', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Connection': 'keep-alive' } self.base_url = "https://piaofang.maoyan.com/api" def get_boxoffice_data(self, date=None): """ 获取指定日期的票房数据 date格式: YYYY-MM-DD,默认为当天 """ if date is None: date = datetime.now().strftime('%Y-%m-%d') url = f"{self.base_url}/boxoffice/daily" params = { 'date': date, 'type': 0 # 0表示单日票房 } try: response = self.session.get(url, headers=self.headers, params=params, timeout=10) response.raise_for_status() data = response.json() if data.get('success'): return data.get('data', {}).get('list', []) else: print(f"获取数据失败: {data.get('msg')}") return [] except requests.exceptions.RequestException as e: print(f"请求失败: {e}") return []4.3 数据解析与清洗
def parse_movie_data(self, raw_data): """ 解析原始数据,提取关键信息 """ movies = [] for item in raw_data: movie_info = { '排名': item.get('rank', 0), '电影名称': item.get('movieName', ''), '综合票房(万)': round(item.get('boxInfo', 0) / 10000, 2), # 转换为万元 '票房占比': round(item.get('boxRate', 0) * 100, 2), # 转换为百分比 '排片占比': round(item.get('showRate', 0) * 100, 2), '场均人次': item.get('avgShowView', 0), '上座率': round(item.get('avgViewBox', 0) * 100, 2), '累计票房(万)': round(item.get('sumBoxInfo', 0) / 10000, 2), '上映天数': item.get('releaseInfo', {}).get('releaseDays', 0), '数据日期': item.get('date', '') } movies.append(movie_info) return movies4.4 多日数据采集与存储
def get_multiple_days_data(self, start_date, days=7): """ 获取连续多天的票房数据 """ all_data = [] current_date = datetime.strptime(start_date, '%Y-%m-%d') for i in range(days): date_str = current_date.strftime('%Y-%m-%d') print(f"正在获取 {date_str} 的数据...") raw_data = self.get_boxoffice_data(date_str) if raw_data: daily_movies = self.parse_movie_data(raw_data) all_data.extend(daily_movies) # 重要:添加延时,避免请求过于频繁 time.sleep(2) current_date += timedelta(days=1) return all_data def save_to_csv(self, data, filename=None): """ 将数据保存为CSV文件 """ if filename is None: filename = f"maoyan_boxoffice_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" df = pd.DataFrame(data) df.to_csv(filename, index=False, encoding='utf-8-sig') print(f"数据已保存到: {filename}") return filename5. 数据可视化分析
5.1 单日票房TOP10可视化
import matplotlib.pyplot as plt import seaborn as sns class DataVisualizer: def __init__(self): plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文字体 plt.rcParams['axes.unicode_minus'] = False sns.set_style("whitegrid") def plot_daily_top10(self, data, date): """ 绘制单日票房TOP10柱状图 """ # 筛选指定日期的数据 daily_data = [movie for movie in data if movie['数据日期'] == date] top10 = sorted(daily_data, key=lambda x: x['排名'])[:10] if not top10: print(f"未找到 {date} 的数据") return fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6)) # 票房柱状图 movie_names = [movie['电影名称'] for movie in top10] box_office = [movie['综合票房(万)'] for movie in top10] bars = ax1.bar(range(len(movie_names)), box_office, color=sns.color_palette("husl", 10)) ax1.set_title(f'{date} 单日票房TOP10', fontsize=14, fontweight='bold') ax1.set_xlabel('电影名称') ax1.set_ylabel('票房(万元)') ax1.set_xticks(range(len(movie_names))) ax1.set_xticklabels(movie_names, rotation=45, ha='right') # 在柱子上添加数值标签 for bar, value in zip(bars, box_office): ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + max(box_office)*0.01, f'{value:.1f}', ha='center', va='bottom', fontsize=9) # 票房占比饼图 box_rates = [movie['票房占比'] for movie in top10] ax2.pie(box_rates, labels=movie_names, autopct='%1.1f%%', startangle=90) ax2.set_title('票房占比分布') plt.tight_layout() plt.savefig(f'daily_top10_{date}.png', dpi=300, bbox_inches='tight') plt.show()5.2 多日趋势分析
def plot_trend_analysis(self, data, movie_name, days=7): """ 绘制指定电影的多日票房趋势图 """ # 筛选指定电影的数据 movie_data = [movie for movie in data if movie['电影名称'] == movie_name] movie_data = sorted(movie_data, key=lambda x: x['数据日期'])[-days:] if len(movie_data) < 2: print(f"{movie_name} 的数据不足,无法进行趋势分析") return dates = [movie['数据日期'] for movie in movie_data] box_office = [movie['综合票房(万)'] for movie in movie_data] rankings = [movie['排名'] for movie in movie_data] fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8)) # 票房趋势图 ax1.plot(dates, box_office, marker='o', linewidth=2, markersize=8, color='#FF6B6B') ax1.set_title(f'{movie_name} {days}日票房趋势', fontsize=14, fontweight='bold') ax1.set_ylabel('票房(万元)') ax1.grid(True, alpha=0.3) # 在折线图上添加数值标签 for i, (date, value) in enumerate(zip(dates, box_office)): ax1.annotate(f'{value:.1f}', (date, value), textcoords="offset points", xytext=(0,10), ha='center', fontsize=9) # 排名趋势图 ax2.plot(dates, rankings, marker='s', linewidth=2, markersize=8, color='#4ECDC4') ax2.set_title(f'{movie_name} {days}日排名变化') ax2.set_xlabel('日期') ax2.set_ylabel('排名') ax2.invert_yaxis() # 排名数值越小越好,所以倒置Y轴 ax2.grid(True, alpha=0.3) # 在排名图上添加数值标签 for i, (date, rank) in enumerate(zip(dates, rankings)): ax2.annotate(f'第{rank}名', (date, rank), textcoords="offset points", xytext=(0,10), ha='center', fontsize=9) plt.tight_layout() plt.savefig(f'trend_{movie_name}_{days}days.png', dpi=300, bbox_inches='tight') plt.show()5.3 综合对比分析
def plot_comparison_analysis(self, data, date, top_n=5): """ 多维度对比分析 """ daily_data = [movie for movie in data if movie['数据日期'] == date] top_movies = sorted(daily_data, key=lambda x: x['排名'])[:top_n] if not top_movies: return metrics = ['综合票房(万)', '票房占比', '排片占比', '上座率'] movie_names = [movie['电影名称'] for movie in top_movies] fig, axes = plt.subplots(2, 2, figsize=(15, 10)) axes = axes.flatten() colors = sns.color_palette("Set2", top_n) for i, metric in enumerate(metrics): values = [movie[metric] for movie in top_movies] axes[i].bar(movie_names, values, color=colors) axes[i].set_title(f'{metric}对比') axes[i].set_xticklabels(movie_names, rotation=45, ha='right') # 添加数值标签 for j, value in enumerate(values): axes[i].text(j, value + max(values)*0.01, f'{value:.1f}', ha='center', va='bottom', fontsize=9) plt.suptitle(f'{date} 电影市场多维度分析', fontsize=16, fontweight='bold') plt.tight_layout() plt.savefig(f'comparison_analysis_{date}.png', dpi=300, bbox_inches='tight') plt.show()6. 完整流程测试与验证
6.1 主程序入口
def main(): """ 主函数:完整的爬取和可视化流程 """ print("=== 猫眼电影票房数据爬虫启动 ===") # 初始化爬虫 spider = MaoyanSpider() visualizer = DataVisualizer() # 获取最近7天的数据 start_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d') print(f"开始获取从 {start_date} 开始的票房数据...") all_data = spider.get_multiple_days_data(start_date, days=7) if not all_data: print("数据获取失败,请检查网络连接或反爬设置") return print(f"成功获取 {len(all_data)} 条电影票房数据") # 保存数据 csv_file = spider.save_to_csv(all_data) # 数据可视化 latest_date = max(set(movie['数据日期'] for movie in all_data)) # 1. 最新单日TOP10可视化 print(f"生成 {latest_date} TOP10 可视化图表...") visualizer.plot_daily_top10(all_data, latest_date) # 2. 选取一部电影进行趋势分析 if all_data: sample_movie = all_data[0]['电影名称'] print(f"生成 {sample_movie} 趋势分析图表...") visualizer.plot_trend_analysis(all_data, sample_movie, days=7) # 3. 多维度对比分析 print("生成多维度对比分析图表...") visualizer.plot_comparison_analysis(all_data, latest_date) print("=== 爬虫任务完成 ===") print(f"数据文件: {csv_file}") print("可视化图表已保存到当前目录") if __name__ == "__main__": main()6.2 测试运行验证
运行上述代码后,你应该能看到以下输出和文件:
控制台输出验证:
=== 猫眼电影票房数据爬虫启动 === 开始获取从 2024-01-01 开始的票房数据... 正在获取 2024-01-01 的数据... 正在获取 2024-01-02 的数据... ... 成功获取 105 条电影票房数据 数据已保存到: maoyan_boxoffice_20240108_143022.csv 生成 2024-01-07 TOP10 可视化图表... 生成 《电影名称》 趋势分析图表... 生成多维度对比分析图表... === 爬虫任务完成 ===生成文件验证:
maoyan_boxoffice_20240108_143022.csv- 原始数据文件daily_top10_2024-01-07.png- 单日TOP10图表trend_电影名称_7days.png- 趋势分析图表comparison_analysis_2024-01-07.png- 多维度对比图表
7. 高级功能扩展
7.1 实时数据监控
def real_time_monitor(spider, visualizer, interval=3600): """ 实时监控票房数据变化 interval: 监控间隔(秒) """ import schedule import time def monitoring_job(): current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') print(f"[{current_time}] 执行监控任务...") # 获取最新数据 latest_data = spider.get_boxoffice_data() if latest_data: parsed_data = spider.parse_movie_data(latest_data) # 这里可以添加数据变化报警逻辑 print(f"当前票房冠军: {parsed_data[0]['电影名称']} - {parsed_data[0]['综合票房(万)']}万元") # 定时任务 schedule.every(interval).seconds.do(monitoring_job) print(f"启动实时监控,每 {interval} 秒执行一次") while True: schedule.run_pending() time.sleep(1)7.2 数据异常检测
def detect_anomalies(data, window=3, threshold=2.0): """ 检测票房数据异常波动 """ anomalies = [] # 按电影分组 movies = {} for record in data: name = record['电影名称'] if name not in movies: movies[name] = [] movies[name].append(record) for name, records in movies.items(): records.sort(key=lambda x: x['数据日期']) # 计算移动平均和标准差 if len(records) > window: box_office = [r['综合票房(万)'] for r in records] for i in range(window, len(box_office)): window_data = box_office[i-window:i] mean = sum(window_data) / window std = (sum((x - mean) ** 2 for x in window_data) / window) ** 0.5 current = box_office[i] if std > 0 and abs(current - mean) > threshold * std: anomalies.append({ '电影名称': name, '日期': records[i]['数据日期'], '异常值': current, '预期范围': f"{mean-threshold*std:.1f} - {mean+threshold*std:.1f}", '偏离程度': abs(current - mean) / std }) return anomalies8. 常见问题与排查方法
8.1 请求被拒绝或返回空数据
问题现象:
- 返回状态码403或418
- 返回数据为空或包含验证码页面
排查步骤:
- 检查User-Agent是否有效
- 验证Referer设置是否正确
- 检查IP是否被暂时封禁
- 查看返回的HTML内容判断具体原因
解决方案:
# 更新请求头 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer': 'https://piaofang.maoyan.com/', 'Accept': 'application/json, text/plain, */*', } # 添加请求延时 time.sleep(random.uniform(1, 3)) # 使用代理IP(如果需要) proxies = { 'http': 'http://your_proxy:port', 'https': 'https://your_proxy:port' }8.2 数据解析错误
问题现象:
- JSON解析失败
- 字段缺失或格式变化
排查方法:
# 打印原始响应内容检查结构 print(response.text[:500]) # 查看前500字符 # 使用try-except处理解析错误 try: data = response.json() except json.JSONDecodeError as e: print(f"JSON解析错误: {e}") print(f"响应内容: {response.text}")8.3 可视化图表显示问题
中文字体显示乱码:
# 解决方案:明确设置中文字体 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans'] plt.rcParams['axes.unicode_minus'] = False图表尺寸不合适:
# 调整图表尺寸 plt.figure(figsize=(12, 8)) # 宽12英寸,高8英寸 # 自动调整布局 plt.tight_layout()9. 最佳实践与使用建议
9.1 爬虫伦理与合规使用
- 尊重robots.txt:检查目标网站的robots.txt文件,遵守爬虫规则
- 控制请求频率:每次请求后添加1-3秒延时,避免对服务器造成压力
- 设置超时时间:避免长时间等待,设置合理的超时参数
- 错误处理机制:网络异常时自动重试,但限制重试次数
9.2 数据存储与管理
# 建议的数据存储结构 data/ ├── raw/ # 原始数据 ├── processed/ # 处理后的数据 ├── charts/ # 生成的图表 └── logs/ # 运行日志 # 使用时间戳命名文件,避免覆盖 filename = f"boxoffice_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"9.3 性能优化建议
- 使用会话保持:重复使用Session对象减少连接开销
- 批量处理数据:避免频繁的文件读写操作
- 内存管理:处理大量数据时使用生成器或分块处理
- 异步请求:如果需要高速采集,考虑使用aiohttp进行异步请求
这个猫眼电影票房爬虫项目涵盖了Python爬虫的核心技术点,从基础请求发送到复杂数据分析都有涉及。通过这个案例,你不仅能学会爬虫技术,还能掌握数据分析可视化的完整流程。建议先运行基础版本,理解每个环节的原理,再逐步添加高级功能。
实际使用时记得遵守爬虫伦理,控制请求频率,将学到的技术用在正当的学习和研究用途上。这个项目的代码结构清晰,模块化程度高,你可以很方便地修改和扩展功能,比如添加新的数据源、优化可视化效果或者集成到更大的数据分析系统中。
