ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Python的电影票房数据分析系统:从数据清洗到可视化

基于Python的电影票房数据分析系统:从数据清洗到可视化 简介一套面向电影行业数据分析场景的Python毕业设计/课程作业资源适合学习数据采集、可视化与系统设计的学生及开发者。系统覆盖用户权限管理、票房数据抓取、实时统计、趋势图表与地区分布并支持同档期对比、历史数据导入等多维度分析。压缩包共578个文件、26.75MB前端以Vue组件、SVG图表和JavaScript逻辑为主后端由Python与Pyc文件实现接口与数据处理另含SQL数据库脚本、HTML页面、CSS样式、配置文件以及安装/运行批处理命令目录结构完整便于直接部署与二次开发。已有166人学习下载。通过源码、数据库脚本和项目部署文件可快速搭建一套完整的票房数据分析平台同时项目中的角色权限设计、票务接口抓取、可视化方案等实现细节也可作为课程作业或毕业设计的有力参考。1. 电影票房数据分析这个选题为什么值得做成“系统”而不是一个脚本电影票房数据分析在国内是个看起来简单、做起来磨人的选题。你打开任何一个公开票房统计平台都能看到漂亮的趋势图和Top榜但真到自己动手就会明白绝大多数时间花在“把字段凑齐、把口径对齐、把图调到能看”这三件事上。基于Python的电影票房数据分析系统核心不是爬虫也不是模型而是把采集、清洗、存储、分析和可视化串成一条可复用的流水线让数据从Excel表格变成能支撑判断的图表和查询接口。这篇笔记适合两类人一类是拿它做毕业设计或课程设计的同学另一类是转行数据分析、想用一个完整项目证明自己动手能力的新人。你不需要Spark不需要大数据环境一个Python解释器加pandas、matplotlib、SQLite就够用。别急着上模型先把数据洗干净比什么算法都管用——这是这个项目里我最大的“后悔药”。2. 系统设计先行数据源、目录结构和数据库表怎么定2.1 公开数据集、爬虫还是手工收集三种数据源怎么选很多人在这个项目上第一步就犹豫数据从哪来我见过不少同学一上来就写爬虫爬到一半发现目标页面改版或者字段对不上整个项目卡死。这里先把三条路摆出来对比清楚再动手。方案新鲜度工作量和难度合规与稳定适合场景公开数据集可能滞后几个月低高毕设、练手最稳爬虫采集实时中高中需遵守站点条款和控制频率想展示完整数据采集链路手工整理CSV完全可控低但样本量小高先把系统跑通后续替换我给的建议是如果目标是“把系统完整做完”首选公开数据集比如开源社区和数据竞赛平台上的电影票房历史数据直接下载导入如果目标是“展示爬虫能力”就明确告诉评审或面试官你的数据来源页面抓取时做好基本礼仪——设置合理超时时间、两次请求之间sleep 0.5到1秒、不要高并发。这一步强调一下数据合规问题在答辩和简历项目里都会被问到提前想好说法比到时候支支吾吾强得多。还有一种很务实的路线先用一份手工整理的CSV把系统全部跑通再回头补爬虫逻辑。我自己的习惯是第一步永远先确认表结构能支撑分析而不是先纠结数据全不全。表结构设计错了数据再多都是垃圾进垃圾出。2.2 项目目录结构把“脚本”改成“系统”的第一步一个项目叫“系统”还是“脚本”看目录结构能判断个七八分。把所有代码塞进一个main.py的人后期加功能一定头疼。我一般会按职责拆成下面的结构。box_office_system/ ├── data/ │ ├── raw/ # 原始数据落地 │ └── processed/ # 清洗后的标准表 ├── scripts/ │ ├── collect.py # 数据采集 │ ├── clean.py # 数据清洗 │ └── analyze.py # 分析出图 ├── output/ # 图表和导出结果 └── box_office.db # SQLite数据库文件这段命令不是必须逐行敲完用mkdir建出目录结构就行。data/raw放原始数据data/processed放清洗结果两个目录分开的最大好处是出问题时能追溯清洗逻辑改完重跑不会污染原始文件。output目录放生成的图表scripts目录下三个脚本各自独立collect.py换数据源、clean.py加清洗规则、analyze.py加图表互不干扰。这也是“系统设计”里最容易讲清楚的分层思路采集层、处理层、分析展示层。答辩时能把这个结构讲明白已经能说明你有工程意识而不是只会写代码片段。后续如果要把系统扩展成Web应用在这个结构上直接加一个app.py就完事。提示如果只用SQLite做本地开发不需要安装任何额外数据库服务Python自带的sqlite3模块就够用。2.3 用SQLite做底层存储字段设计决定后期分析上限数据分析系统最容易被低估的是存储层。直接用CSV做存储不是不行但到后期几万条数据做条件查询时你会怀念数据库。常见做法是选用SQLite零配置、单文件、Python自带驱动对毕设和简历项目都够用。等到数据量到百万级别、需要多人并发访问时再换MySQL也不迟——SQL逻辑基本不用改。字段设计上我建议做成“事实表 维度属性”而不是一列一个指标。下面这张表是daily_box_office的典型结构。字段类型说明idINTEGER PRIMARY KEY自增主键movieTEXT片名dateTEXT数据日期格式YYYY-MM-DDday_box_officeREAL当日票房单位元show_countINTEGER当日场次audience_countINTEGER当日观影人次avg_priceREAL平均票价单位元sourceTEXT数据来源标记为什么把当日票房存成“元”而不是“亿/万”因为单位换算应该在展示层做存储层统一数值单位sum、order by、group by都不会出错。source字段是我踩过坑之后才加的不同来源的统计口径不一样有了它出问题时能快速过滤定位。建表脚本我也一并给出# create_db.py import sqlite3 conn sqlite3.connect(box_office.db) cur conn.cursor() cur.execute( CREATE TABLE IF NOT EXISTS daily_box_office ( id INTEGER PRIMARY KEY AUTOINCREMENT, movie TEXT NOT NULL, date TEXT NOT NULL, day_box_office REAL, show_count INTEGER, audience_count INTEGER, avg_price REAL, source TEXT ) ) cur.execute( CREATE INDEX IF NOT EXISTS idx_movie_date ON daily_box_office(movie, date) ) conn.commit() conn.close() print(database initialized)这段代码就是建一张票房事实表并给movie和date建联合索引。AUTOINCREMENT让每次插入的记录拥有自增idNOT NULL约束movie和date两列防止缺数据的脏行入库。索引的意义这张表后续查询基本都带着movie和date条件没有索引时全表扫描数据量几万条还没感觉几十万条就会明显变慢。建完表后可以用sqlite3的命令行工具或Db Browser打开看一眼确认表结构真实存在再往下走。3. 把原始票房数据洗干净从CSV到标准表3.1 读入CSV先别急着分析先看三个指标拿到CSV后第一步不是去重也不是画图而是先搞清楚两个问题这张表里有什么、缺什么。我见过有人把脏数据全丢进数据库后面图表全是错的这就是洗数据之前没做探查的翻车现场。# clean.py 第一步探查 import pandas as pd df pd.read_csv( data/raw/box_office_sample.csv, encodingutf-8, parse_dates[date] ) print(df.info()) print(df.head()) print(df.isna().sum())info()告诉你每列的类型和空值数量head()显示前五行数据长什么样isna().sum()算出每一列的缺失总量。这三个输出看完你就大致知道该改哪里。参数说明encoding在中文CSV里最常踩坑文件可能是utf-8也可能是gbk先试utf-8报UnicodeDecodeError就换成encodinggbkparse_dates把日期列解析成datetime类型这比事后用pd.to_datetime去转更省事。真实数据里的情况通常是这样的电影名是中文日期可能是“2024/5/1”这样的格式当日票房写成“1250.3万”场次和人次是纯数字票价带“元”后缀。探查结果决定清洗策略不要一上来就照抄别人的清洗模板不同的数据源脏法完全不一样。3.2 清洗四件套去空、去重、单位归一、规范日期探查完之后下面四步是票房数据里逃不掉的。第一去空票房、场次、人次任一为空的那一行直接丢掉因为这类缺失没法靠均值填补电影票房没有“平均一下”的说法。第二去重同一部电影同一天的记录只能保留一条重复时取最后一条因为有的数据源会修订更新历史数据。第三单位归一原始数据里的“1250.3万”“1.2亿”必须统一转成“元”否则没法求和。第四规范日期把date列统一成YYYY-MM-DD格式避免同一天两种写法导致分组出错。# 清洗核心逻辑 import pandas as pd def parse_box_office(value): 把 1250.3万 / 1.2亿 转成 float 元 if isinstance(value, (int, float)): return float(value) text str(value).strip() if text.endswith(亿): return float(text[:-1]) * 1e8 if text.endswith(万): return float(text[:-1]) * 1e4 return float(text) df df.dropna(subset[day_box_office, show_count, audience_count]) df df.drop_duplicates(subset[movie, date], keeplast) df[day_box_office] df[day_box_office].apply(parse_box_office) df[date] pd.to_datetime(df[date]).dt.strftime(%Y-%m-%d)dropna的subset参数指定了检查空值的列范围只要有指定列为空就删掉整行。这里绝不填0因为0在票房里代表“没有数据”而不是“真的卖零元”填0会把均值拉低一大截。drop_duplicates的subset指定去重粒度这里按movie和date联合去重keeplast保留修订后的最新版本这个细节在多轮抓取场景下特别重要。parse_box_office函数是字符串转数值的兜底方案endswith判断单位后缀再乘以对应倍数逻辑直白几十万行数据内执行性能没有问题。这四步按顺序执行有讲究先去空再去重因为空值行可能干扰去重粒度的判断先洗数据再入库保证数据库里永远只有干净数据。清洗完成后再跑一次df.describe()观察均值、最大最小值是否合理平均票价应该在二三十到一百多元之间如果出现0或负数说明前面还有脏数据没清干净回去继续查。注意清洗好的数据再入库千万别在写入前省掉这一步校验。3.3 写入SQLite用to_sql替代逐条insert清洗完的数据建议直接入库。很多人会用for循环逐条insert数据量少还行几万行就慢得让人想砸键盘。pandas的to_sql一条语句就能完成批量写入。import sqlite3 conn sqlite3.connect(box_office.db) df.to_sql( daily_box_office, conn, if_existsreplace, indexFalse ) conn.close() print(f写入 {len(df)} 条记录)to_sql的if_existsreplace会先删旧表再建新表适合本地开发反复测试如果你的场景是增量更新应该换成if_existsappend否则旧数据会被覆盖。indexFalse是丢掉DataFrame的行索引不要把0、1、2当成一列写进表里。写完再查一次SELECT COUNT(*)看行数是否和清洗后的df行数一致——这种“写后校验”是防低级错误最省钱的办法我每次都会做因为曾经吃过亏代码报错但数据库没报错结果表里只有一半数据图表全歪了。4. 用Python做票房数据分析与可视化五种视角和三个必画图4.1 先把核心指标算明白票房、场次、人次和均价的关系数据入库后先别急着画图把四个基础指标的关系捋一遍票房等于人次乘以平均票价而人次的背后是场次乘以场均上座水平。分析时如果发现票房涨而人次降说明是票价在拉动市场如果人次涨而票房平说明票价在跌。这四个指标单独看都没意义放在一起看才有说服力。# analyze.py 基础聚合 import sqlite3 import pandas as pd conn sqlite3.connect(box_office.db) df pd.read_sql_query( SELECT movie, date, day_box_office, show_count, audience_count, avg_price FROM daily_box_office , conn) conn.close() daily df.groupby(date).agg( total_box(day_box_office, sum), total_show(show_count, sum), total_audience(audience_count, sum) ).reset_index() daily[avg_price] daily[total_box] / daily[total_audience] daily[avg_show_audience] daily[total_audience] / daily[total_show]groupby按日期聚合出全天总和agg的写法是新列名加括号对比直接df.groupby().sum()更清晰。聚合之后用两列相除得到均价和场均人次这两个派生指标才是判断大盘冷热的关键。如果你的数据带城市维度可以在groupby里加一列[date, city]后面的分析逻辑不用改这一套聚合模式能直接复用。这一步做出来的是“数字结论”比如某天均价突然从35元跳到52元那就要去看是不是有IMAX或特效厅的大片集中上映。放映电影的市场价格波动往往比人次波动更有故事可讲。4.2 三个必画图趋势、结构占比和Top榜分析做完了可视化是把结论传达给非技术人员的那一步。下面这组图表是我给这类项目定的“三件套”单日大盘趋势、累计票房Top10、分影片票房占比。这三张图能覆盖“这个市场怎么走、谁在赚钱、头部有多集中”三个核心问题。import matplotlib.pyplot as plt # 中文字体配置防止画出来是方块 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(14, 5)) # 左图单日全国票房趋势 axes[0].plot(daily[date], daily[total_box] / 1e8, color#c0392b, linewidth2) axes[0].set_title(单日全国票房趋势亿元) axes[0].tick_params(axisx, rotation45) # 右图累计票房Top10 top10 df.groupby(movie)[day_box_office].sum().nlargest(10) top10.plot(kindbarh, axaxes[1], color#2980b9) axes[1].set_title(累计票房 Top10元) plt.tight_layout() plt.savefig(output/box_office_overview.png, dpi150)plt.rcParams两行是中文环境的必备配置SimHei在Windows下通常可用。如果不生效先执行from matplotlib.font_manager import fontManager把可用字体列表打出来换成你系统里实际存在的字体名。总票房除以1e8是把元换算成亿元Y轴读数更友好barh用水平柱状图是因为片名较长垂直柱状图会把片名挤成一团。nlargest(10)取累计票房前十名语义上比sort_values加tail更明确。图不是画完就结束要做“读图验证”趋势线的尖峰是否对应某个节假日或者热门大片的上映日期Top榜第一位是否和你印象中的爆款匹配。如果对不上大概率不是图的问题而是数据源或者清洗逻辑有遗漏这时候回查数据比反复调样式更有效。savefig的dpi参数设150清晰度和文件大小比较平衡。output目录一定要提前建好否则保存的时候直接报错这个错误很基础但真的很常见。4.3 档期视角把日数据切成周、月看规律票房分析不能只看单日电影消费有明显的周期规律周末抬升、节假日前爆发、档期结束断崖下跌。把日粒度数据重采样到周、月能看出更长周期的走势写报告时这个视角比单纯列数字有说服力得多。daily daily.set_index(date) weekly daily[total_box].resample(W).sum() monthly daily[total_box].resample(ME).sum() print(weekly.tail()) print(monthly.tail())resample(W)按周汇总ME是月度末汇总新版pandas里M已经退役用ME可以避免报警告。重采样之后观察序列里是否存在“某周突然放大”的异常点这通常是头部影片上映或档期造成的结构性波动写进报告里能让你的分析结论更有深度。参数说明如果希望按自然周一开始聚合用resample(W-MON)默认是周日结束的。两种口径在报告里要保持统一不能前一半用周一到周日后一半用周七到下周六。5. 避坑实录编码、单位、重复和统计口径是最磨人的五个坑5.1 数据读取阶段的三个经典坑现象一pd.read_csv直接报UnicodeDecodeError代码看起来没问题页面卡在这里一查就是半小时。原因CSV文件实际是GBK或GB2312编码而read_csv默认用utf-8去解码中文字符一多必然报错。更隐蔽的场景是文件在Windows的Excel里另存过编码被悄悄改了文件名看不出任何差别。解决先读前几行判断编码。常见做法是try utf-8 except gbk或者直接用encodingutf-8-sig读取带BOM的utf-8文件。如果想省事可以用chardet.detect自动识别但别完全依赖它识别短文件时经常不准。这个坑我自己摔过不止一次现在写读取逻辑时会先把encoding单独抽成一个常量方便切换。现象二清洗后票房数值越算越离谱某部电影单日票房居然超过了全周大盘。原因抓取多轮数据后同一部电影同一天被记录了多次去重粒度没设对。如果按movie单独去重会把不同日期的记录误删如果按整行去重日期相同但场次字段微调过的记录全部留下了。解决去重一定要带日期条件df.drop_duplicates(subset[movie, date], keeplast)。如果数据带城市维度还要把city加进subset里。另外数据源修订历史数据时keeplast能保留最新版本这个参数比默认的keepfirst更符合票房场景。现象三“1250.3万”这种字符串根本没法求和一执行sum就变成字符串拼接输出一大串数字连在一起。原因票房和票价的原始数据是给人看的带了单位后缀转成数值前没做归一化处理。解决写一个parse函数endswith判断“亿”乘1e8、“万”乘1e4其余直接float。重点是不只处理票房列票价里出现“35元”字样也要先replace掉“元”。这种小函数放在clean.py顶部复用性最强后面新增数据源时还能直接调用。5.2 统计口径与展示层的两个坑现象四同一部电影在两张图表里票房对不上一个图显示10亿另一个图显示9.4亿。原因票房公开数据存在“含服务费”和“不含服务费”两套口径两者差距大约在5%到8%左右。项目里如果混合抓取了两个平台的数据或者同一平台前后调整了统计方式对比起来数字就会飘。这不是代码bug是数据源层面的口径问题但比bug更难排查。解决在表里加source字段记录“这条数据来自哪个平台、是否含服务费”。写分析SQL时统一过滤只取一个口径或者在报告里明确标注口径来源。这个字段在建表时就要预留临时补数据时也要记得填上。我见过同学因为没加这个字段排查口径问题排查了整整两天最后才发现是两批数据来源不同这类时间浪费本来完全可以避免。现象五matplotlib画出的中文全是方块图里的片名变成一排□□□□。原因matplotlib默认字体不含中文字形Linux服务器上尤其明显Windows上SimHei一般能用但某些精简版环境或虚拟环境里同样缺字体。解决设置plt.rcParams[font.sans-serif] [SimHei]再配合axes.unicode_minusFalse。如果设置后还是方块执行下面的代码看本机实际有哪些字体可用挑一个中文名字改用上from matplotlib.font_manager import fontManager for f in fontManager.ttflist: if CJK in f.name or Hei in f.name or YaHei in f.name: print(f.name)这段排错逻辑其实是通用的先确认字体在系统里是否存在再确认rcParams指到了正确的字体名。我实际项目中遇到过同事修改了matplotlibrc全局配置文件导致字体失效的案例改回来就恢复正常。遇到这类问题优先怀疑环境而不是代码。字体问题看起来很玄学但本质就这一条排查路径走完就解决。6. 再往前走一步用Flask把分析结果变成可查询的票房接口数据分析本身只是第一步把它变成一个可用的服务才算完整的“系统”。我习惯在最后加一层极简的查询接口输入片名返回每天的票房记录。这样前面做的清洗和分析成果都能通过接口复用也方便在演示时直接被验收——给对方一个URL比给对方看十个脚本文件直观得多。# app.py from flask import Flask, jsonify import sqlite3 app Flask(__name__) DB_PATH box_office.db app.route(/movie/name) def movie_detail(name): conn sqlite3.connect(DB_PATH) cur conn.cursor() cur.execute( SELECT date, day_box_office FROM daily_box_office WHERE movie ? ORDER BY date, (name,) ) rows [{date: r[0], box_office: r[1]} for r in cur.fetchall()] conn.close() return jsonify({movie: name, records: rows}) if __name__ __main__: app.run(debugTrue)路由的入参是路径里的片名查询时直接对box_office.db执行SQL按日期升序返回记录Flask再把Python列表转成JSON响应。SQL里的“?”是参数占位符比字符串拼接安全也能避免SQL注入问题。debugTrue只在本地开发时用部署到服务器必须关掉否则一旦报错会泄露源码路径。运行后访问本机端口输入片名就能看到该片每天票房的JSON数据这个效果比单纯在终端里打印表格更有“系统落地”的感觉。如果还想做得更丰富可以再加一个统计接口/summary?date某天返回当天大盘票房、场次、人次前端接上就能做成简单的查询看板。我自己的习惯是每个分析结论都留下对应的SQL语句和图表文件这样未来有人问“这个数字怎么来的”我能十分钟之内从数据库到画图完整复现。数据项目最怕的是黑匣子能复现、能追溯比任何花哨的可视化都重要。希望这篇笔记能帮你把这个项目做扎实也少走我当年绕的那些弯路。本文还有配套的精品资源点击获取
返回列表