在大数据与影视行业深度融合的背景下,电影票房数据的分析与可视化成为热门选题。本文将手把手教你搭建一套完整的电影票房可视化分析系统,通过 Python 爬虫采集权威票房数据,利用 Flask 框架构建 Web 服务,结合 Echarts 实现多维度数据可视化,支持用户注册登录、档期筛选、地域分布分析等核心功能,适合作为计算机专业毕业设计或课程实践项目。
一、项目整体介绍
1.1 项目背景与价值
随着影视行业的蓬勃发展,票房数据成为衡量电影市场表现的核心指标。本系统通过采集主流票房平台数据,实现多维度可视化分析,帮助用户快速洞察不同档期、不同影片的票房表现、地域分布特征及市场占比情况,为影视投资者、发行方及爱好者提供数据支撑。
1.2 技术栈选型
| 技术模块 | 核心技术 | 作用说明 |
|---|---|---|
| 数据采集 | Python+requests+BeautifulSoup | 爬取权威票房平台数据,包括影片票房、场次、人次、地域分布等信息 |
| 数据存储 | MySQL + 数据库连接池 | 高效存储结构化数据,支持批量插入与快速查询 |
| Web 框架 | Flask+Jinja2 模板引擎 | 构建用户交互界面,实现登录注册、数据筛选、可视化展示等功能 |
| 可视化展示 | Echarts+HTML/CSS/JavaScript | 生成折线图、饼图、柱状图等,直观呈现多维度数据 |
| 辅助工具 | datetime+json+logging | 处理时间格式、解析接口数据、记录系统日志 |
1.3 核心功能模块
系统分为前台用户交互 与后台管理两大模块,功能结构如下:
前台功能
- 用户注册 / 登录:实现账号认证,未登录用户无法访问核心功能
- 数据筛选:支持按年份(2022-2024)、档期(春节档 / 五一档 / 暑期档等)、影片名称筛选数据
- 可视化分析:
- 趋势分析:票房 / 场次 / 人次的时间变化趋势(折线图)
- 占比分析:单档期内各影片的票房 / 场次 / 人次占比(饼图)
- 地域分析:影片在一线 / 二线 / 三线等城市的分布情况(柱状图)
- 数据导出:支持将分析结果导出为 Excel 文件(拓展功能)
后台功能
- 用户管理:查看注册用户列表,支持用户账号启用 / 禁用操作
- 数据管理:手动同步最新票房数据,支持数据增删改查维护
- 日志管理:记录用户操作日志与数据采集日志(拓展功能)
二、环境搭建与准备
2.1 开发环境配置
- 基础环境:Python 3.8+(推荐 3.9 版本,兼容性更佳)
- 依赖库安装:
bash
运行
bash
# 核心依赖库
pip install flask requests beautifulsoup4 pymysql flask-sqlalchemy
pip install pandas openpyxl # 数据处理与Excel导出
pip install logging # 日志记录
- 数据库准备:
- 安装 MySQL 8.0+,创建数据库
movie_box_office_db - 执行以下 SQL 创建核心数据表(用户表 + 票房数据表 + 地域分布表):
- 安装 MySQL 8.0+,创建数据库
sql
sql
-- 用户表
CREATE TABLE `user` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) NOT NULL UNIQUE,
`password` VARCHAR(100) NOT NULL,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`status` TINYINT DEFAULT 1 COMMENT '1-正常 0-禁用'
);
-- 票房数据表
CREATE TABLE `movie_box_office` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`thedate` DATE NOT NULL COMMENT '数据日期',
`rank` INT COMMENT '当日排名',
`movie_id` VARCHAR(20) NOT NULL COMMENT '影片ID',
`movie_name` VARCHAR(100) NOT NULL COMMENT '影片名称',
`box_office` BIGINT COMMENT '当日票房(元)',
`total_box_office` BIGINT COMMENT '累计票房(元)',
`total_show_count` INT COMMENT '累计场次',
`total_audience_count` INT COMMENT '累计人次',
`box_office_percent` DECIMAL(5,2) COMMENT '票房占比(%)',
`show_count_percent` DECIMAL(5,2) COMMENT '场次占比(%)',
`audience_count_percent` DECIMAL(5,2) COMMENT '人次占比(%)',
`release_date` DATE COMMENT '上映日期'
);
-- 地域分布表
CREATE TABLE `movie_area_distribution` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`thedate` DATE NOT NULL COMMENT '数据日期',
`movie_id` VARCHAR(20) NOT NULL COMMENT '影片ID',
`movie_name` VARCHAR(100) NOT NULL COMMENT '影片名称',
`city_level` VARCHAR(20) NOT NULL COMMENT '城市级别(一线/二线/三线/四线/其它)',
`box_office` BIGINT COMMENT '当日票房(元)',
`show_count` INT COMMENT '当日场次',
`audience_count` INT COMMENT '当日人次'
);
2.2 项目目录结构
plaintext
python
movie_analysis_system/
├── app.py # 项目入口文件(Flask核心配置)
├── config.py # 配置文件(数据库连接、密钥等)
├── spider/ # 爬虫模块
│ ├── __init__.py
│ ├── box_office_spider.py # 票房数据采集脚本
│ └── utils.py # 爬虫工具函数(请求头、数据清洗)
├── models/ # 数据模型
│ ├── __init__.py
│ ├── user_model.py # 用户相关操作
│ └── data_model.py # 数据存储与查询操作
├── static/ # 静态资源
│ ├── css/ # 样式文件
│ ├── js/ # JavaScript文件(Echarts核心)
│ └── images/ # 图片资源
├── templates/ # 前端模板
│ ├── login.html # 登录页面
│ ├── register.html # 注册页面
│ ├── index.html # 可视化主页面
│ └── admin/ # 后台管理页面
├── utils/ # 通用工具
│ ├── __init__.py
│ ├── db_pool.py # 数据库连接池
│ └── excel_utils.py # Excel导出工具
└── logs/ # 日志文件
三、核心模块实现
3.1 数据采集模块(爬虫实现)
本模块通过 requests 库采集权威票房平台数据(以艺恩数据为例),包含票房详情与地域分布数据,支持指定日期范围批量采集。
3.1.1 爬虫工具配置(spider/utils.py)
python
运行
python
import requests
from fake_useragent import UserAgent
# 生成随机请求头,避免被反爬
def get_headers():
ua = UserAgent()
return {
'User-Agent': ua.random,
'Referer': 'https://ys.endata.cn/',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': 'application/json, text/javascript, */*; q=0.01'
}
# 通用请求函数
def send_request(url, data=None, method='POST'):
headers = get_headers()
try:
if method == 'POST':
response = requests.post(url, headers=headers, data=data, timeout=10)
else:
response = requests.get(url, headers=headers, params=data, timeout=10)
response.raise_for_status() # 抛出HTTP错误
return response.json()
except Exception as e:
print(f"请求失败:{str(e)}")
return None
3.1.2 票房数据采集(spider/box_office_spider.py)
python
运行
python
import datetime
from spider.utils import send_request
from utils.db_pool import MySQLPool
# 数据库连接
db = MySQLPool()
def get_daily_box_office(date):
"""采集指定日期的票房详情数据"""
url = 'https://ys.endata.cn/enlib-api/api/movie/getMovie_BoxOffice_Day_List.do'
data = {
'r': '0.6690016180153391',
'datetype': 'Day',
'date': date,
'sdate': date,
'edate': date,
'bserviceprice': '1',
'pageindex': '1',
'pagesize': '200',
'order': '103',
'ordertype': 'desc'
}
response = send_request(url, data=data)
if not response or 'data' not in response:
return
movie_list = response['data']['table1']
box_office_data = []
for movie in movie_list:
item = {
'thedate': date,
'rank': movie.get('Irank'),
'movie_id': movie.get('MovieID'),
'movie_name': movie.get('MovieName'),
'box_office': movie.get('BoxOffice'),
'total_box_office': movie.get('TotalBoxOffice'),
'total_show_count': movie.get('TotalShowCount'),
'total_audience_count': movie.get('TotalAudienceCount'),
'box_office_percent': movie.get('BoxOfficePercent'),
'show_count_percent': movie.get('ShowCountPercent'),
'audience_count_percent': movie.get('AudienceCountPercent'),
'release_date': movie.get('ReleaseDate')
}
box_office_data.append(item)
# 批量插入数据库
if box_office_data:
db.batch_insert('movie_box_office', box_office_data)
print(f"成功采集{date}票房数据,共{len(box_office_data)}部影片")
def get_area_distribution(date):
"""采集指定日期的地域分布数据"""
url = 'https://ys.endata.cn/enlib-api/api/movie/getMovie_BoxOffice_Day_Chart.do'
data = {
'r': '0.6690016180153391',
'datetype': 'Day',
'date': date,
'sdate': date,
'edate': date,
'bserviceprice': '1'
}
response = send_request(url, data=data)
if not response or 'data' not in response:
return
area_list = response['data']['table1']
area_data = []
for area in area_list:
item = {
'thedate': date,
'movie_id': area.get('MovieID'),
'movie_name': area.get('MovieName'),
'city_level': area.get('CityLevel'),
'box_office': area.get('BoxOffice'),
'show_count': area.get('ShowCount'),
'audience_count': area.get('AudienceCount')
}
area_data.append(item)
# 批量插入数据库
if area_data:
db.batch_insert('movie_area_distribution', area_data)
print(f"成功采集{date}地域分布数据,共{len(area_data)}条记录")
def batch_collect_data(start_date, end_date):
"""批量采集指定日期范围的数据"""
# 转换日期格式
start = datetime.datetime.strptime(start_date, '%Y-%m-%d').date()
end = datetime.datetime.strptime(end_date, '%Y-%m-%d').date()
delta = datetime.timedelta(days=1)
current_date = start
while current_date <= end:
date_str = current_date.strftime('%Y-%m-%d')
get_daily_box_office(date_str)
get_area_distribution(date_str)
current_date += delta
if __name__ == '__main__':
# 采集2023年春节档数据(1月21日-1月27日)
batch_collect_data('2023-01-21', '2023-01-27')
3.1.3 数据库连接池(utils/db_pool.py)
python
运行
python
import pymysql
from pymysql.err import OperationalError
from config import DB_CONFIG
class MySQLPool:
def __init__(self):
self.pool = self.create_pool()
def create_pool(self):
"""创建数据库连接池"""
return pymysql.connect(
host=DB_CONFIG['host'],
port=DB_CONFIG['port'],
user=DB_CONFIG['user'],
password=DB_CONFIG['password'],
database=DB_CONFIG['db_name'],
charset='utf8mb4',
autocommit=True
)
def get_connection(self):
"""获取连接(自动重连)"""
try:
if self.pool.ping():
return self.pool
except OperationalError:
self.pool = self.create_pool()
return self.pool
def batch_insert(self, table, data_list):
"""批量插入数据"""
if not data_list:
return
conn = self.get_connection()
cursor = conn.cursor()
# 获取字段名和占位符
fields = list(data_list[0].keys())
placeholders = ','.join(['%s'] * len(fields))
sql = f"INSERT INTO {table} ({','.join(fields)}) VALUES ({placeholders})"
# 处理数据(None值转换为NULL)
values = []
for item in data_list:
value = [item.get(field) for field in fields]
values.append(value)
try:
cursor.executemany(sql, values)
except Exception as e:
print(f"批量插入失败:{str(e)}")
conn.rollback()
finally:
cursor.close()
# 配置文件(config.py)
DB_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': '你的数据库密码',
'db_name': 'movie_box_office_db'
}
3.2 Flask Web 服务实现(app.py)
实现用户注册登录、数据筛选、可视化页面渲染等核心功能,采用 Flask-Session 管理用户会话。
python
运行
python
from flask import Flask, render_template, request, redirect, url_for, session, jsonify
from models.user_model import UserModel
from models.data_model import DataModel
import hashlib
app = Flask(__name__)
app.secret_key = 'movie_analysis_system_2024' # 密钥(建议修改为随机字符串)
# 实例化模型
user_model = UserModel()
data_model = DataModel()
# 密码加密函数
def encrypt_password(password):
md5 = hashlib.md5()
md5.update(password.encode('utf-8'))
return md5.hexdigest()
# 登录装饰器(验证用户登录状态)
def login_required(func):
def wrapper(*args, **kwargs):
if 'username' not in session:
return redirect(url_for('login'))
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
return wrapper
# 登录页面
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
encrypted_pwd = encrypt_password(password)
# 验证用户
user = user_model.get_user_by_username(username)
if user and user['password'] == encrypted_pwd and user['status'] == 1:
session['username'] = username
return redirect(url_for('index'))
else:
return render_template('login.html', error='账号或密码错误')
return render_template('login.html')
# 注册页面
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
confirm_pwd = request.form.get('confirm_password')
# 验证密码一致性
if password != confirm_pwd:
return render_template('register.html', error='两次密码不一致')
# 检查用户名是否已存在
if user_model.get_user_by_username(username):
return render_template('register.html', error='用户名已存在')
# 注册用户
encrypted_pwd = encrypt_password(password)
user_model.add_user(username, encrypted_pwd)
return redirect(url_for('login'))
return render_template('register.html')
# 退出登录
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('login'))
# 可视化主页面
@app.route('/index')
@login_required
def index():
# 获取年份和档期选项
years = data_model.get_distinct_years()
schedules = [
{'name': '春节档', 'date_range': '01-21至01-27'},
{'name': '五一档', 'date_range': '04-29至05-03'},
{'name': '暑期档', 'date_range': '06-01至08-31'},
{'name': '中秋国庆档', 'date_range': '09-29至10-07'}
]
return render_template('index.html', years=years, schedules=schedules)
# 获取可视化数据(AJAX接口)
@app.route('/get_visual_data', methods=['POST'])
@login_required
def get_visual_data():
year = request.form.get('year')
schedule = request.form.get('schedule')
movie_name = request.form.get('movie_name')
# 根据档期获取日期范围
schedule_date_map = {
'春节档': (f'{year}-01-21', f'{year}-01-27'),
'五一档': (f'{year}-04-29', f'{year}-05-03'),
'暑期档': (f'{year}-06-01', f'{year}-08-31'),
'中秋国庆档': (f'{year}-09-29', f'{year}-10-07')
}
start_date, end_date = schedule_date_map.get(schedule)
# 获取各类数据
trend_data = data_model.get_box_office_trend(start_date, end_date, movie_name) # 趋势数据
proportion_data = data_model.get_box_office_proportion(start_date, end_date) # 占比数据
area_data = data_model.get_area_distribution(start_date, end_date, movie_name) # 地域数据
return jsonify({
'trend_data': trend_data,
'proportion_data': proportion_data,
'area_data': area_data
})
# 后台管理页面
@app.route('/admin/users')
@login_required
def admin_users():
# 仅管理员可访问(简化版,实际项目需添加角色权限控制)
if session['username'] != 'admin':
return redirect(url_for('index'))
users = user_model.get_all_users()
return render_template('admin/user_management.html', users=users)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
3.3 数据模型实现(models/data_model.py)
封装数据库查询逻辑,为前端提供可视化所需的数据。
python
运行
python
from utils.db_pool import MySQLPool
class DataModel:
def __init__(self):
self.db = MySQLPool()
def get_distinct_years(self):
"""获取所有数据年份"""
conn = self.db.get_connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = "SELECT DISTINCT YEAR(thedate) AS year FROM movie_box_office ORDER BY year DESC"
cursor.execute(sql)
result = [str(item['year']) for item in cursor.fetchall()]
cursor.close()
return result
def get_box_office_trend(self, start_date, end_date, movie_name=None):
"""获取票房趋势数据(折线图)"""
conn = self.db.get_connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = """
SELECT thedate, SUM(box_office) AS total_box, SUM(show_count) AS total_show,
SUM(audience_count) AS total_audience
FROM movie_box_office
WHERE thedate BETWEEN %s AND %s
"""
params = [start_date, end_date]
# 筛选影片
if movie_name and movie_name != '全部':
sql += " AND movie_name = %s"
params.append(movie_name)
sql += " GROUP BY thedate ORDER BY thedate"
cursor.execute(sql, params)
result = cursor.fetchall()
cursor.close()
# 格式化数据(适配Echarts)
dates = [item['thedate'] for item in result]
box_office = [round(item['total_box']/10000, 2) for item in result] # 转换为万元
show_count = [item['total_show'] for item in result]
audience_count = [item['total_audience'] for item in result]
return {
'dates': dates,
'box_office': box_office,
'show_count': show_count,
'audience_count': audience_count
}
def get_box_office_proportion(self, start_date, end_date):
"""获取票房占比数据(饼图)"""
conn = self.db.get_connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = """
SELECT movie_name, SUM(box_office) AS total_box,
SUM(show_count) AS total_show, SUM(audience_count) AS total_audience
FROM movie_box_office
WHERE thedate BETWEEN %s AND %s
GROUP BY movie_name
ORDER BY total_box DESC
LIMIT 10
"""
cursor.execute(sql, [start_date, end_date])
result = cursor.fetchall()
cursor.close()
# 格式化数据
movie_names = [item['movie_name'] for item in result]
box_proportion = [round(item['total_box']/sum([x['total_box'] for x in result])*100, 2) for item in result]
show_proportion = [round(item['total_show']/sum([x['total_show'] for x in result])*100, 2) for item in result]
audience_proportion = [round(item['total_audience']/sum([x['total_audience'] for x in result])*100, 2) for item in result]
return {
'movie_names': movie_names,
'box_proportion': box_proportion,
'show_proportion': show_proportion,
'audience_proportion': audience_proportion
}
def get_area_distribution(self, start_date, end_date, movie_name=None):
"""获取地域分布数据(柱状图)"""
conn = self.db.get_connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
sql = """
SELECT city_level, SUM(box_office) AS total_box,
SUM(show_count) AS total_show, SUM(audience_count) AS total_audience
FROM movie_area_distribution
WHERE thedate BETWEEN %s AND %s
"""
params = [start_date, end_date]
# 筛选影片
if movie_name and movie_name != '全部':
sql += " AND movie_name = %s"
params.append(movie_name)
sql += " GROUP BY city_level ORDER BY FIELD(city_level, '一线城市', '二线城市', '三线城市', '四线城市', '其它')"
cursor.execute(sql, params)
result = cursor.fetchall()
cursor.close()
# 格式化数据
city_levels = [item['city_level'] for item in result]
box_office = [round(item['total_box']/10000, 2) for item in result]
show_count = [item['total_show'] for item in result]
audience_count = [item['total_audience'] for item in result]
return {
'city_levels': city_levels,
'box_office': box_office,
'show_count': show_count,
'audience_count': audience_count
}
3.4 前端可视化实现(templates/index.html)
结合 Echarts 实现多图表展示,支持数据筛选交互。
html
预览
XML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>电影票房数据分析可视化系统</title>
<!-- 引入Echarts和jQuery -->
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
</head>
<body>
<div class="header">
<h1>电影票房数据分析可视化系统</h1>
<div class="user-info">
欢迎您,{{ session.username }} | <a href="{{ url_for('logout') }}">退出登录</a>
</div>
</div>
<div class="filter-bar">
<div class="filter-item">
<label>年份选择:</label>
<select id="year-select">
{% for year in years %}
<option value="{{ year }}">{{ year }}年</option>
{% endfor %}
</select>
</div>
<div class="filter-item">
<label>电影档期:</label>
<select id="schedule-select">
{% for schedule in schedules %}
<option value="{{ schedule.name }}">{{ schedule.name }}({{ schedule.date_range }})</option>
{% endfor %}
</select>
</div>
<div class="filter-item">
<label>代表性电影:</label>
<select id="movie-select">
<option value="全部">全部</option>
<!-- 实际项目可通过AJAX动态加载影片列表 -->
</select>
</div>
<button id="search-btn">查询分析</button>
</div>
<div class="chart-container">
<!-- 趋势分析图表 -->
<div class="chart-item">
<h3>票房/场次/人次趋势分析</h3>
<div id="trend-chart" class="chart" style="width: 100%; height: 400px;"></div>
</div>
<!-- 占比分析图表 -->
<div class="chart-item">
<h3>影片票房/场次/人次占比</h3>
<div id="proportion-chart" class="chart" style="width: 100%; height: 400px;"></div>
</div>
<!-- 地域分布图表 -->
<div class="chart-item">
<h3>地域分布分析(一线/二线/三线/四线/其它)</h3>
<div id="area-chart" class="chart" style="width: 100%; height: 400px;"></div>
</div>
</div>
<script>
// 初始化图表
const trendChart = echarts.init(document.getElementById('trend-chart'));
const proportionChart = echarts.init(document.getElementById('proportion-chart'));
const areaChart = echarts.init(document.getElementById('area-chart'));
// 查询按钮点击事件
$('#search-btn').click(function() {
const year = $('#year-select').val();
const schedule = $('#schedule-select').val();
const movieName = $('#movie-select').val();
// 发送AJAX请求获取数据
$.ajax({
url: '/get_visual_data',
type: 'POST',
data: {
year: year,
schedule: schedule,
movie_name: movieName
},
success: function(data) {
// 更新趋势图表
updateTrendChart(data.trend_data);
// 更新占比图表
updateProportionChart(data.proportion_data);
// 更新地域图表
updateAreaChart(data.area_data);
},
error: function() {
alert('数据加载失败,请重试!');
}
});
});
// 更新趋势图表
function updateTrendChart(data) {
const option = {
title: { text: '票房/场次/人次时间趋势' },
tooltip: { trigger: 'axis' },
legend: { data: ['票房(万元)', '场次', '人次'] },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: data.dates
},
yAxis: [
{
type: 'value',
name: '票房(万元)',
axisLabel: { formatter: '{value}' }
},
{
type: 'value',
name: '场次/人次',
axisLabel: { formatter: '{value}' },
position: 'right'
}
],
series: [
{
name: '票房(万元)',
type: 'line',
data: data.box_office,
yAxisIndex: 0,
smooth: true
},
{
name: '场次',
type: 'line',
data: data.show_count,
yAxisIndex: 1,
smooth: true
},
{
name: '人次',
type: 'line',
data: data.audience_count,
yAxisIndex: 1,
smooth: true
}
]
};
trendChart.setOption(option);
}
// 更新占比图表
function updateProportionChart(data) {
const option = {
title: { text: '影片票房占比' },
tooltip: { trigger: 'item' },
legend: { orient: 'vertical', left: 'left' },
series: [
{
name: '票房占比(%)',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: { show: false, position: 'center' },
emphasis: {
label: { show: true, fontSize: 16, fontWeight: 'bold' }
},
labelLine: { show: false },
data: data.movie_names.map((name, index) => ({
name: name,
value: data.box_proportion[index]
}))
}
]
};
proportionChart.setOption(option);
}
// 更新地域图表
function updateAreaChart(data) {
const option = {
title: { text: '地域分布(票房/场次/人次)' },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: { data: ['票房(万元)', '场次', '人次'] },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: { type: 'category', data: data.city_levels },
yAxis: { type: 'value' },
series: [
{
name: '票房(万元)',
type: 'bar',
data: data.box_office,
itemStyle: { color: '#5470c6' }
},
{
name: '场次',
type: 'bar',
data: data.show_count,
itemStyle: { color: '#91cc75' }
},
{
name: '人次',
type: 'bar',
data: data.audience_count,
itemStyle: { color: '#fac858' }
}
]
};
areaChart.setOption(option);
}
// 页面加载时默认查询
$(function() {
$('#search-btn').trigger('click');
});
// 窗口大小变化时重置图表
window.addEventListener('resize', function() {
trendChart.resize();
proportionChart.resize();
areaChart.resize();
});
</script>
</body>
</html>
四、系统运行与效果展示
4.1 运行步骤
- 启动 MySQL 服务,确保数据库连接配置正确;
- 运行爬虫脚本采集数据:
python spider/box_office_spider.py; - 启动 Flask 服务:
python app.py; - 浏览器访问
http://localhost:5000/register注册账号,或使用管理员账号(admin/admin123)登录; - 在主页面选择年份、档期、影片,点击 "查询分析" 即可查看可视化结果。
4.2 核心效果展示
1. 登录与注册页面
- 支持账号注册、密码加密存储,登录失败给出友好提示;
- 未登录用户无法访问核心功能,自动跳转至登录页面。
2. 数据筛选功能
- 下拉选择框支持年份(2022-2024)、档期(春节档 / 五一档等)、影片名称筛选;
- 筛选后实时更新所有图表数据,响应速度快。
3. 可视化图表效果
- 趋势图:直观展示档期内每日票房、场次、人次变化,支持多指标对比;
- 饼图:展示单档期内 TOP10 影片的票房占比,清晰呈现头部影片市场份额;
- 柱状图:对比不同城市级别的票房、场次、人次分布,洞察地域消费特征。
4.3 后台管理功能
- 管理员可查看所有注册用户列表,支持用户状态管理(启用 / 禁用);
- 支持手动同步最新票房数据,确保数据时效性。
五、项目拓展与优化方向
-
数据采集优化:
- 添加定时采集任务(使用 APScheduler),自动同步每日最新数据;
- 增加异常重试机制与反爬策略(如 IP 代理池、请求间隔控制)。
-
功能拓展:
- 实现数据导出功能(Excel/PDF),支持用户下载分析结果;
- 添加影片类型分析(喜剧 / 动作 / 科幻等),丰富可视化维度;
- 集成机器学习模型(如线性回归),实现票房预测功能。
-
系统优化:
- 引入 Redis 缓存热门查询结果,提升系统响应速度;
- 完善用户权限管理(普通用户 / 管理员 / 超级管理员);
- 优化前端界面,支持响应式设计(适配手机 / 平板设备)。
六、总结
本项目基于 Python 实现了一套完整的电影票房可视化分析系统,涵盖数据采集、存储、Web 服务、可视化展示全流程,技术栈贴合企业实际应用场景,适合作为计算机专业毕业设计或大数据实践项目。通过本项目的开发,可熟练掌握 Python 爬虫、Flask Web 开发、Echarts 可视化、MySQL 数据库操作等核心技能,同时理解大数据分析项目的完整开发流程。
项目代码结构清晰、注释详细,可根据实际需求灵活拓展功能。建议在开发过程中注重代码规范性与可维护性,同时积累爬虫反爬、数据清洗、性能优化等实战经验,为后续就业或深造打下坚实基础。