首页 / 资讯中心 / 文章详情

猫眼电影数据分析可视化:Python爬虫到交互大屏的完整实践

猫眼电影数据分析可视化:Python爬虫到交互大屏的完整实践 ★ FEATURED ARTICLE
简介一份基于Python的猫眼电影数据分析可视化系统的毕业设计资源包主要面向计算机相关专业需完成数据分析类毕设或课程设计的学生也可供对爬虫、数据清洗、可视化开发感兴趣的开发者参考。资源以单个docx文档形式提供大小约3.31MB内容为完整的系统设计论文覆盖研究背景、国内外现状、需求分析、系统设计、数据爬取、数据预处理、可视化展示等章节。文档详细说明如何借助requests库获取猫眼电影API数据使用Pandas去除重复值、处理缺失值与异常值结合Matplotlib和Echarts对电影评分、票房趋势、类型分布等多维度进行可视化分析并利用Flask框架搭建Web系统直观展示分析结果。整篇论文结构完整既有技术实现细节也有系统测试与总结展望可作为毕业设计写作框架、技术路线以及项目实现思路的参考模板。目前已有381人学习下载适合正在筹备数据分析类项目的学生快速上手。1. 猫眼电影数据分析可视化系统从爬虫到交互大屏的完整落地路径做电影数据分析最烦的一件事就是数据明明摆在眼前却拿不走。猫眼票房、评分、上映天数这些信息散落在网页里手动复制几百部电影简直是在消耗耐心。基于Python猫眼电影数据分析可视化系统的核心思路是把这套流程固化成一条流水线requests 抓取、Pandas 清洗、MySQL 存储再用 PyECharts 渲染成交互图表让票房趋势、评分分布、类型占比一眼看清。这套方案不需要分布式爬虫也不需要算法模型适合数据分析师、Python 初学者和想接真实项目练手的学生一台普通笔记本就能跑通全流程。2. 数据采集层猫眼页面结构与反爬应对爬虫是这套系统的入口也是最容易翻车的一层。猫眼的反爬主要靠 User-Agent 检测、请求频率限制和滑块验证前两者用代码能解决后者要靠策略绕开或人工配合。动手前先把目标页面的数据结构弄清楚再写采集逻辑能少走很多弯路。2.1 榜单页与详情页的数据结构常见做法是优先用猫眼电影榜单页URL 形如https://maoyan.com/board/4每页返回 10 部电影翻页参数是 offset0、10、20。用浏览器开发者工具 Network 面板可以看到页面 HTML 里包含电影名、主演、上映时间、评分等字段。但榜单页信息有限要拿到实时票房和累计票房还需要详情页接口。详情页返回的是 JSON 数据字段比 HTML 干净得多。这里给出一段最小采集脚本抓取榜单页并用 BeautifulSoup 解析import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9, } def fetch_board(offset0): url fhttps://maoyan.com/board/4?offset{offset} resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) items [] for dd in soup.select(dd): title_tag dd.select_one(.name a) if not title_tag: continue star dd.select_one(.star) time_tag dd.select_one(.releasetime) score_tag dd.select_one(.score) items.append({ title: title_tag.get_text(stripTrue), star: star.get_text(stripTrue) if star else , release_time: time_tag.get_text(stripTrue) if time_tag else , score: score_tag.get_text(stripTrue) if score_tag else , }) return items if __name__ __main__: for i in range(3): data fetch_board(offseti * 10) print(foffset{i * 10}, 数量{len(data)})这段代码的逻辑是用 offset 控制分页requests 带 headers 发起 GET 请求BeautifulSoup 解析出电影名、主演、上映时间和评分。注意resp.encoding utf-8这个步骤不能省猫眼页面如果不强制指定编码requests 可能按默认 ISO-8859-1 解码中文全部乱码。参数方面UA 要填全至少包含浏览器和系统信息timeout 设 10 秒避免某个请求卡死整个采集任务。2.2 请求头、限速与重试机制榜单页只适合小批量验证真正放到生产环境跑一定要加限速和重试。猫眼对高频请求的封禁不是立刻发生的而是先让你连续请求几次都正常突然某一次返回 418 或者验证码页面这种“温水煮青蛙”式的封禁靠肉眼很难发现。我一般的做法是用 requests.Session 复用连接每次请求后 sleep 1 到 2 秒并对失败请求做指数退避重试。import time import random from requests.adapters import HTTPAdapter session requests.Session() session.headers.update(headers) adapter HTTPAdapter(max_retries3, pool_connections10, pool_maxsize10) session.mount(https://, adapter) def fetch_with_retry(url, max_attempts4): for attempt in range(max_attempts): try: resp session.get(url, timeout10) if resp.status_code 200: return resp if resp.status_code in (418, 403): print(f触发反爬状态码 {resp.status_code}等待后重试) except requests.RequestException as e: print(f请求异常: {e}) time.sleep(2 ** attempt random.uniform(0, 1)) return None这里的关键参数是 max_retries 和 sleep 时间。HTTPAdapter 的 max_retries3 解决连接层重试业务层再包一层是为了能感知 418 这样的业务状态码。sleep 用 2 的指数次幂叠加随机值比固定 sleep 更接近人的操作节奏也不容易被频率统计抓到规律。频率控制没有绝对安全的值不同 IP 的触发阈值也不一样这部分有点玄学味道只能靠日志边跑边调。采集 100 条以上的数据时建议把抓到的 HTML 先落盘成文件再二次解析避免网络抖动导致全量重跑。2.3 滑块验证的应对思路当请求频率降下来之后触发滑块的概率会明显下降但仍有可能偶发。滑块验证本质上是浏览器环境检测加行为轨迹分析requests 模拟不了真实滑动轨迹所以不要试图用代码硬刚。我实际项目里的处理方式是检测到验证码页面时暂停当前任务把页面链接写到本地队列文件里等人工在浏览器里完成验证后把 cookie 导出并回填到 session。# 将验证码页面的链接写入待处理队列 from pathlib import Path pending_file Path(pending_queue.txt) pending_file.write_text(resp.url \n, encodingutf-8, modea) # 人工处理完成后从浏览器复制 cookie 字符串回来 cookie_str 你的浏览器 cookie session.headers.update({Cookie: cookie_str})这个方案的逻辑很简单把异常路径从自动化流程中抽离出来由人工兜底。cookie 回填后session 会带着完整身份信息继续请求。注意 cookie 有有效期通常几小时到几天不等建议把 cookie 的过期时间也记录到配置文件里过期后重新走人工流程。cookie 多了以后建议用 Redis 队列统一管理配合 redis 可视化客户端能直观看到剩余有效 cookie 和请求失败重放的情况比手工维护文本文件靠谱。别小看这几行代码它能避免你在凌晨跑定时任务时被验证码卡死到天亮。3. 数据清洗与存储从脏数据到能直接查询的 MySQL采集下来的数据不能直接用原因有三个字段缺失、类型混乱、重复记录。这一步处理不好后面所有分析都是在对垃圾做可视化。清洗的基本原则是先摸清数据长什么样再定规则最后入库。用 Pandas 处理批量数据再用 SQLAlchemy 写入 MySQL是这套系统里最稳定的组合。3.1 先探查数据再清洗拿到 raw_data 列表后第一步不是写清洗逻辑而是转成 DataFrame 看每一列的非空数量和样本值。这一步能快速发现解析阶段留下的问题比如主演字段里混入了“主演”前缀上映时间里夹着城市名。import pandas as pd df pd.DataFrame(raw_data) print(df.info()) print(df.head()) print(df.isnull().sum())df.info()会输出每列的非空计数和数据类型isnull().sum()告诉你哪些字段缺得厉害。如果某个字段缺失超过 30%要考虑是不是解析选择器写漏了而不是简单填充。这个探查步骤是清洗的起点也是后面所有规则的依据别跳过。3.2 类型转换与去重猫眼评分的原始文本类似“9.7分”上映时间是“2024-05-01 上映”这些字符串不转成数值和日期类型后续无法参与计算。处理方式是写一个统一清洗函数把非数字字符剥掉再交给 astype 或 to_datetime 转换。def clean_score(text): if not isinstance(text, str): return None digits .join(ch for ch in text if ch.isdigit() or ch .) try: return float(digits) except ValueError: return None df[score] df[score].apply(clean_score) df[release_time] df[release_time].str.replace(上映, ).str.strip() df[release_time] pd.to_datetime(df[release_time], errorscoerce) df.drop_duplicates(subset[title, release_time], keepfirst, inplaceTrue)逻辑说明clean_score 用字符过滤的方式提取数字和小数点比正则更直观pd.to_datetime的 errorscoerce 会把无法解析的日期置为 NaT方便后续统一处理drop_duplicates以电影名加日期为唯一键去重保留第一条。这里的注意点是去重键的选择只用 title 去重可能会误删同名翻拍电影加上 release_time 才能保证同一天同一部电影才视为重复。如果你后续还要分析重映电影这里建议再加一个字段维度优先用电影的 imdb_id 或猫眼电影 id 做唯一键。3.3 MySQL 表设计与批量入库推荐把表设计成宽表一行一部电影字段包含 title、score、release_time、box_office、genre 等。宽表查询简单前期不用考虑规范化拆分等数据量上了千万级再考虑分表也不迟。建表语句和入库代码如下CREATE TABLE IF NOT EXISTS movie_info ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(100) NOT NULL, score DECIMAL(3,1), release_time DATE, box_office DECIMAL(12,2), genre VARCHAR(100), create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_title_time (title, release_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时用 utf8mb4 字符集避免 emoji 或特殊符号导致入库报错唯一索引 uk_title_time 从数据库层面兜底重复数据。入库用 pandas 的 to_sql 配合 SQLAlchemy避免自己拼 SQL 带来的注入和转义问题。from sqlalchemy import create_engine engine create_engine( mysqlpymysql://user:passwordlocalhost:3306/movie_db?charsetutf8mb4 ) df.to_sql(namemovie_info, conengine, if_existsappend, indexFalse)to_sql 的 if_exists 参数有 replace、append、fail 三个选项日常增量入库用 append如果清洗逻辑大改想重建表可以先手动 drop 再 append别用 replace因为它会直接删表重建你的索引和注释全没了。大批量写入时可以通过 chunksize5000 分批提交减少长事务对 MySQL 的压力。4. 分析指标与可视化落地让票房和评分自己说话数据入库之后分析就变得轻松了。分析的目标不是列一堆图表而是回答几个业务问题哪些类型最赚钱、评分和票房有没有关系、国产片和引进片的差距在哪。指标设计围绕这几个问题展开然后用 SQL 取数、用 PyECharts 绘图最后拼成一张能在浏览器里交互的 HTML 大屏。4.1 三个核心分析模块我一般把分析拆成票房分析、评分分析和类型分析三个模块。票房分析关注总票房 TOP10、每日票房走势和上映天数与票房的关系评分分析关注评分分布直方图和不同档期的评分对比类型分析则做类型交叉统计看动作、喜剧、爱情这些标签在票房上的表现差异。这三个模块基本覆盖了猫眼电影数据分析可视化系统的主要使用场景也方便后续加预测模型。指标定义上用 SQL 聚合比 Python 内存计算更稳。例如统计各类型的平均票房和平均评分SELECT genre, COUNT(*) AS movie_cnt, ROUND(AVG(box_office), 2) AS avg_box_office, ROUND(AVG(score), 2) AS avg_score FROM movie_info WHERE release_time 2023-01-01 GROUP BY genre ORDER BY avg_box_office DESC LIMIT 10;这个查询按类型分组统计数量、平均票房和平均评分并按票房降序排列。注意 genre 字段是多值标签如果一部电影同时属于“动作,冒险”这条 SQL 会把整个字符串当一组统计精度不够。要更精细的分析把 genre 按逗号拆分到关联表里再聚合。4.2 用 PyECharts 生成可视化图表PyECharts 是 ECharts 的 Python 封装数据准备好之后几行代码就能输出一个可交互的 HTML。下面的代码示例生成票房 TOP10 柱状图from pyecharts.charts import Bar from pyecharts import options as opts bar ( Bar() .add_xaxis(top10_titles) .add_yaxis(票房, top10_box_office, category_gap40%) .set_global_opts( title_optsopts.TitleOpts(title猫眼票房 TOP10), yaxis_optsopts.AxisOpts(name票房万元), toolbox_optsopts.ToolboxOpts(), ) ) bar.render(box_office_top10.html)add_xaxis 接收电影名列表add_yaxis 接收票房数值列表顺序必须对齐。set_global_opts 里配置标题、坐标轴名称和工具条toolbox 可以导出图片、切换数据视图这些是交互体验的关键。PyECharts 的红线是数据顺序从 SQL 查询结果里 zip 出两个列表时注意保持同一排序条件否则图表会张冠李戴。为了让系统看起来像一个整体通常会把多个图表拼到一个 HTML 大屏里。PyECharts 的 Page 组件可以把图表按顺序纵向排列配合 Tab 组件切换不同模块。常见做法是生成一个 dashboard.html左侧放票房趋势曲线右侧放类型分布饼图下方放评分分布直方图整体用深色背景衬托高亮色。这块不要在意复杂特效图表之间的视觉对齐和数据口径一致比炫酷更重要。4.3 图表选型的三个原则折线图用来展示时间序列比如每日票房走势柱状图适合品类对比比如类型票房排行饼图只用来展示部分占整体的关系比如国产片与引进片的票房占比如果分类超过 6 个就别用饼图了改成横向柱状图更清晰。评分分布这类连续型数据用直方图看集中趋势ECharts 的 histogram 接口需要自己提前分桶分桶个数一般取 10边界值注意包含左右端点。这些选型原则是我反复调了几个版本才定下来的早期把所有数据都画成饼图结果满屏都是挤在一起的标签用户根本读不出信息。5. 常见问题排查与避坑四个真实踩坑记录这套系统从 0 到 1 跑通的路上我遇到过不少问题下面四条是出现频率最高、也最有代表性的每一条都按现象到原因再到解决的方式记录希望你看完能少走弯路。5.1 采集几次后突然返回 418重试也没用现象脚本请求前几页正常某次开始持续返回 418按原来的重试逻辑怎么跑都失败。原因418 是猫眼反爬的典型状态码说明请求频率过快IP 被临时标记。requests 的重试只会重复相同的请求不会降低频率所以大概率一直失败。解决先停止采集 30 分钟以上让标记过期给采集脚本加上请求间隔随机化并在请求里带上完整的浏览器 headers另外把采集目标从榜单页换成接口接口的压力更小。重要的是把失败状态码 418 单独处理不要当作普通超时重试否则每次重试都在加剧封禁。5.2 入库后评分列全是 NULL或者出现 97 这种离谱的值现象清洗后评分字段要么为空要么出现 97、96 这样明显不对的数字。原因清洗时用的字符串过滤逻辑有问题。猫眼评分的 HTML 里评分“9.7”被拆成两个 span分别表示整数部分和小数部分如果直接取整段文本得到的是“97”过滤函数提取所有数字后就变成 97。解决解析时先拿到父级的完整文本再归一化或者直接按“.”分割后重新拼接。这条血泪经验告诉我清洗规则必须建立在真实样本上不要想当然地写正则。5.3 写入 MySQL 时报 UnicodeEncodeError 或乱码现象入库时报UnicodeEncodeError或者数据写入成功但用客户端查出来全是乱码。原因MySQL 连接字符串没有指定 charset或者表字符集不是 utf8mb4。中文乱码的隐蔽点在于表和连接只要有一端不是 utf8mb4就会出问题。解决建表语句统一用 utf8mb4连接字符串里加?charsetutf8mb4配置文件里也显式声明。如果你用可视化客户端连接数据库连接选项里也要确认字符集否则看到的表数据是乱码但程序里查出来又是正常的这种情况最容易让人怀疑代码写错了。5.4 每天定时增量采集数据库里重复数据越来越多现象每天定时任务跑完数据量都在涨但电影总数并没有明显增加查询结果出现大量重复行。原因to_sql用的 append 模式不会检查唯一键每次采集都会把当天的全量数据再插一遍。有的增量逻辑是“先删当天数据再插入”如果删除和插入之间有异常数据会悬空。解决在入库前用 MySQL 的INSERT IGNORE或ON DUPLICATE KEY UPDATE做幂等写入。pandas 不好直接拼这种 SQL可以用 SQLAlchemy 的 text 执行透传语句核心就是把唯一键冲突时的行为从报错改成忽略或更新。或者更简单一点入库前对 DataFrame 先和库里已有的主键集做一次差集过滤虽然多一次查询但逻辑直观。6. 进阶玩法定时增量采集与票房预测验证系统跑通后最值得做的两个进阶方向是定时自动化和票房预测。定时任务用 APScheduler 的 CronTrigger每天凌晨 1 点触发一次采集避免和真正的数据更新高峰抢时间。增量采集时先查库里最大日期只抓这一天之后的数据入库后自动完成分析和可视化刷新让 Web 页面每天早上都是最新数据。定时任务要配日志采集、清洗、入库每个阶段各打一条出问题能快速定位是哪个环节挂了。票房预测是很多人感兴趣但容易想复杂的方向。其实用线性回归做基线已经能满足大多数场景。特征选上映天数、首周票房、类型、评分目标值是累计票房。数据按 8:2 划分训练集和测试集用 scikit-learn 的 LinearRegression 拟合评估用平均绝对误差 MAE 和 R²。跑完之后你会发现问题集中在首周票房这个特征上上映几天后预测的误差远小于上映前预测的误差这是数据本身的规律不是模型选型的问题。验证方法上面有个小技巧用时间切片验证而不是随机划分即用前 80% 的时间段数据训练后 20% 预测这更贴近实际使用方式。随机划分会把未来的信息泄漏给训练集看起来分数很漂亮实际部署就露馅。我的习惯是每次改完特征或清洗逻辑都把模型回测结果记在一个文档里对比不同版本的表现避免“改了一版代码效果倒退却不知道改坏了什么”。这套系统的价值不在于某个环节多精巧而在于把数据从采集到展示的路径打通了。我最开始做的时候把大量精力花在爬虫上后来才发现真正影响分析结论的是清洗逻辑和数据口径。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站