在大数据与影视行业深度融合的背景下,电影票房数据的分析与可视化成为热门选题。本文将手把手教你搭建一套完整的电影票房可视化分析系统,通过 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
运行
# 核心依赖库 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
-- 用户表 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
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
运行
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 None3.1.2 票房数据采集(spider/box_office_spider.py)
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
运行
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
运行
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
运行
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
预览
<!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 数据库操作等核心技能,同时理解大数据分析项目的完整开发流程。
项目代码结构清晰、注释详细,可根据实际需求灵活拓展功能。建议在开发过程中注重代码规范性与可维护性,同时积累爬虫反爬、数据清洗、性能优化等实战经验,为后续就业或深造打下坚实基础。