尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python爬虫+数据清洗+pyecharts:电影票房评分可视化完整实战

Python爬虫+数据清洗+pyecharts:电影票房评分可视化完整实战 简介这是一套基于Python编程语言和Pyecharts可视化库的国产电影票房与评分分析项目源码主要面向Python数据分析学习者、影视数据爱好者以及需要完整数据采集与分析流程的开发者。项目以2018年大陆上映电影为对象从中国票房网获取票房数据借助豆瓣开放接口与详细页面补充导演、演员和评分并分别对接猫眼、时光网和IMDB收集多方评分随后通过脚本完成数据清洗、影人参演统计和多维度可视化包括票房排名、不同网站评分差异、票房-评分关系等图表。资源压缩包共34个文件包含7个Python脚本、3个CSV数据表、10个HTML交互报告、9张PNG图表以及1个Jupyter Notebook并附有README说明文档整体仅2.67MB目录结构清晰方便按步骤学习和二次开发。目前已有492人学习使用可有效帮助读者掌握requests数据采集、多源接口对接、Pyecharts绘图以及电影数据综合分析的方法。1. 用Python爬票房和评分做可视化这个项目到底解决什么问题很多刚接触数据分析的人拿到“国内上映电影票房评分可视化”这个题目时第一反应是先把pyecharts的官网示例跑一遍觉得图表好看就等于项目完成。但真实做下来你会发现这个项目最有价值的环节根本不是画图而是把分散在猫眼、豆瓣、艺恩等平台的半结构化数据清洗成一张可计算的宽表再用pyecharts把趋势、分布、相关性讲清楚。市面上大部分Python毕业设计和转行作品集都选这个方向就是因为它的数据获取、清洗、可视化链路完整能同时展示爬虫、Pandas和pyecharts三块能力又不至于复杂到一个人搞不定。这篇笔记我会按实际开发顺序把数据源怎么选、爬虫怎么写、清洗有哪些坑、图表参数怎么调一次讲透你照着做两三天就能跑出一份能演示的完整作品。2. 把数据弄到手爬虫选型与最小实现细节2.1 数据源怎么选猫眼、豆瓣、艺恩各自的坑做国内上映电影的票房和评分可视化第一步不是写代码是确定数据从哪来。常见做法是票房走猫眼专业版评分走豆瓣因为猫眼的票房数据更新及时且字段规整豆瓣的评分对“口碑”有参考价值两者一结合正好覆盖“票房榜”和“评分榜”两个维度。艺恩也做票房统计但很多接口有权限限制爬虫门槛偏高不建议在入门项目里用它。猫眼和豆瓣的页面结构完全不同。猫眼票房榜的数据通常藏在页面源码的script标签里以JSON字符串形式存在BeautifulSoup拿到的只是空壳HTML必须用正则或json模块提取。豆瓣评分则反过来数据直接渲染在HTML里但反爬比较严格频繁请求很容易被302重定向到验证页。我的建议是做项目不要追求把所有平台的数据都爬到先把猫眼票房和豆瓣评分两张表合并出一份分析数据集项目的展示效果就已经足够。2.2 requests加BeautifulSoup拉取热映电影列表最小可用爬虫下面是最小可用的爬虫方案目标是拿到“电影名、上映日期、当日票房、累计票房、场均人次”这几个核心字段。import requests import json import re import time import pandas as pd url https://piaofang.maoyan.com/dashboard 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/ } resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 # 票房数据不在HTML中而是内嵌在script标签的__INITIAL_STATE__里 match re.search(rwindow\.__INITIAL_STATE__\s*\s*(\{.*?\});, resp.text, re.S) state json.loads(match.group(1)) # 具体字段路径需要看一次实际返回但结构通常在state里可以找到 items state.get(movieList, {}).get(list, []) rows [] for item in items: rows.append({ movie_name: item.get(movieName), release_time: item.get(releaseTime), box_info: item.get(boxInfo), # 形如 1.2亿 total_box: item.get(totalBoxInfo), # 形如 5.6亿 avg_show: item.get(avgShow) # 场均人次 }) df pd.DataFrame(rows) df.to_csv(movies_raw.csv, indexFalse, encodingutf-8-sig) print(df.head())这段代码的核心逻辑是先用requests请求票房首页然后用正则把页面里window.__INITIAL_STATE__后面的JSON整体提取出来再用json.loads把它转成Python对象。为什么要这么做因为猫眼首页的票房列表是前端渲染的直接解析HTML只能拿到框架拿不到数字。参数说明User-Agent一定要设置成真实浏览器的值否则很容易触发反爬Referer填猫眼域名模拟从站内跳转过来的正常访问timeout10防止某个请求卡住拖死整个脚本。拿到数据后先存成CSV不要急着画图因为后面清洗要反复修改格式存档的原始数据就是后悔药。2.3 把评分和票房拼成一张宽表两次请求的合并逻辑有了票房后再向豆瓣要每个电影的评分。豆瓣没有现成的“按月份拉全部电影”接口常见做法是根据电影名去搜索页逐个查询取第一条搜索结果。import requests import time def fetch_douban_score(movie_name): search_url https://www.douban.com/search params {q: movie_name} headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Cookie: 你的登录Cookie可选但建议带上 } try: resp requests.get(search_url, paramsparams, headersheaders, timeout8) # 这里可以用BeautifulSoup定位到第一条结果的评分节点 # 豆瓣搜索页的评分在 .rating_nums 类下 from bs4 import BeautifulSoup soup BeautifulSoup(resp.text, html.parser) rating soup.select_one(.rating_nums) return rating.text.strip() if rating else None except Exception: return None finally: time.sleep(1.5) # 限速防止被封 # 对上一步的CSV逐行补分 df pd.read_csv(movies_raw.csv, encodingutf-8-sig) df[douban_score] df[movie_name].apply(fetch_douban_score) df.to_csv(movies_with_score.csv, indexFalse, encodingutf-8-sig)这段代码展示的是“按名匹配”的合并逻辑一个小坑是猫眼的电影名和豆瓣搜索结果的标题可能不完全一致比如猫眼写“满江红”豆瓣结果里可能带着“(2023)”匹配时要用前面几个关键词而不是整串全等于。合并完成后你的表里就有了“电影名、日期、票房、评分”四个核心字段接下来才能做真正的分析。3. 清洗是重头戏把“1.2亿”和“暂无评分”变成能计算的数字3.1 票房单位归一化字符串转float的三种写法爬下来的票房字段基本是字符串形如“1.2亿”“3456万”“暂无”。如果你直接把这一列画进图表pyecharts会报错或显示不出内容。归一化的含义是把“亿、万、元”统一换算成以“元”为单位的浮点数。import pandas as pd df pd.read_csv(movies_with_score.csv, encodingutf-8-sig) def box_to_number(s): if not isinstance(s, str): return None s s.strip() if s in (暂无, -, ): return None if s.endswith(亿): return float(s.replace(亿, )) * 1e8 if s.endswith(万): return float(s.replace(万, )) * 1e4 try: return float(s) except ValueError: return None df[box_num] df[box_info].apply(box_to_number) df[total_box_num] df[total_box].apply(box_to_number)这里有个细节值得关注我用了isinstance(s, str)来判断类型因为Pandas读CSV时长空值可能变成NaNfloat类型如果不判类型直接执行endswith会抛AttributeError。这是数据清洗里最常见的翻车点处理顺序一定是“先判类型、再判内容、最后转换”。三种写法的区别在于float(s.replace(亿, ))适合固定格式s[:-1] * 1e8也常见但遇到空格就崩最稳的反而是上面这种显式replace加strip的组合。单位换算之后还要处理“票房单位为0”或者“上映首日票房为0”的样本。首日票房为0不代表电影不行可能是爬取时场次未更新此时不要直接删除行而是把这个值记为缺失后续散点图计算相关性时自动跳过。3.2 日期和评分的脏数据处理日期字段同样不是干净的。猫眼返回的上映日期常见格式是“2023-01-22”但偶尔会出现“2023-1-5”这种不补零的情况或者直接在日期后面跟了一个具体时分秒。如果用pd.to_datetime去转前者没问题后者会产生你意想不到的字符串。df[release_time] pd.to_datetime(df[release_time], errorscoerce) df[release_year] df[release_time].dt.year df[release_month] df[release_time].dt.month # 评分字段清理去掉暂无评分、空字符串、括号备注 def clean_score(s): if not isinstance(s, str): return None s s.strip() if s.startswith(暂无) or s : return None # 摘自豆瓣的结果可能长这样7.5(12345人评价) s re.sub(r\(.*?\), , s) try: return float(s) except ValueError: return None df[score] df[douban_score].apply(clean_score)errorscoerce是个关键参数转换失败时不会中断程序而是把非法值变成NaTNot a Time这样后续按年、月分组时无效日期会被自然忽略。评分的清除逻辑更复杂因为搜索页返回的文本可能带着评价人数括号re.sub去掉括号内容后再转float。做完这一步你的DataFrame里应该全是数值型和日期型数据已经具备分析的基础。3.3 导出标准CSV编码、列名、类型约定清洗完成的表要导出成一个新的CSV作为可视化模块的输入。这里有两个约定值得养成一是编码必须用utf-8-sig因为Windows下的Excel和Pandas更兼容带BOM的UTF-8纯utf-8用Excel打开中文会乱码二是列名固定为英文因为pyecharts图表的系列名、提示框里的字段名都依赖这一列列名。df df[[movie_name, release_time, release_year, release_month, box_num, total_box_num, score]] df df.dropna(subset[box_num, score]) df.to_csv(movies_clean.csv, indexFalse, encodingutf-8-sig) print(清洗后有效样本数:, len(df)) print(df.describe())dropna(subset[box_num, score])这行是双刃剑。如果你画的是评分-票房散点图必须同时有这两个值才能画删掉缺失行是合理的。但如果你想画“每月上映电影数量”这样的柱状图评分缺失并不影响数量统计就不该删行而是用fillna填充或者分组时跳过。所以清理策略要跟着图表需求走建议保留一份全量清洗表再在可视化脚本里按图筛选不要一上来就删行。4. 用pyecharts把六类图拼成一个Html报告核心图表配置4.1 折线加柱状组合图日票房趋势与累计票房双轴清洗完成后的第一张图我建议画“当月每天的总票房折线叠加当日冠军电影的累计票房柱状图”。双轴图是pyecharts里最能体现分析能力的图表形式它把一个时间点上的两个量级完全不同的指标叠加在同一张图上观众一眼就能看出大盘走势和头部电影的关系。from pyecharts import options as opts from pyecharts.charts import Line, Bar dates df[df[release_month] 1].sort_values(release_time)[release_time].tolist() daily_box df[df[release_month] 1].groupby(release_time)[box_num].sum().tolist() top_box df[df[release_month] 1].sort_values(release_time)[total_box_num].tolist() bar Bar() bar.add_xaxis(dates) bar.add_yaxis(当日大盘票房(元), daily_box, yaxis_index0) line Line() line.add_xaxis(dates) line.add_yaxis(冠军累计票房(元), top_box, yaxis_index1) bar.overlap(line) bar.set_global_opts( title_optsopts.TitleOpts(title1月每日票房趋势与头部电影累计票房), yaxis_optsopts.AxisOpts(name大盘票房), tooltip_optsopts.TooltipOpts(triggeraxis) ) bar.render(chart_1_daily_trend.html)这段代码里有个容易忽略的地方overlap把Line叠在Bar上之后两个系列分别挂了不同的yaxis_index所以左轴和右轴量纲不同但互不干扰。如果你不设yaxis_index两个系列会共用同一个y轴右边的折线会直接被柱状图压成一条直线这就是常见的“图出来了但没法看”的原因。日期列表一定要先排序再提取否则折线会来回乱串。4.2 散点图看评分和票房是否真的正相关“高评分一定高票房”是很多入门分析里想当然的结论散点图能一眼把这个假设推翻。把每部电影作为点x轴放评分y轴放总票房你大概率会看到右下角一堆高评分低票房的艺术片和左上角一堆低评分高票房的商业片。这才是这个项目最有价值的发现之一。from pyecharts.charts import Scatter x_data df[score].tolist() y_data df[total_box_num].tolist() names df[movie_name].tolist() scatter Scatter() scatter.add_xaxis(x_data) scatter.add_yaxis(票房, y_data, label_optsopts.LabelOpts(is_showFalse)) scatter.set_global_opts( title_optsopts.TitleOpts(title豆瓣评分与总票房关系), xaxis_optsopts.AxisOpts(name豆瓣评分, min_2, max_10), yaxis_optsopts.AxisOpts(name总票房(元), type_log), # 票房跨度大用log轴 tooltip_optsopts.TooltipOpts( formatter{c}, # 这里可以替换成JS回调显示电影名 ) ) scatter.render(chart_2_rating_boxoffice.html)这个散点图的关键参数在yaxis_opts里。票房的量级从几十万到几十亿跨度有四个数量级如果用线性轴低票房电影全部挤在底部变成一条线什么信息都看不出来。改成type_log后分布才能拉开。min和max限制评分轴的显示范围避免0到10全画出来导致众数区域被挤在中间太密。4.3 地图、词云和饼图把项目源码的“展示面”撑起来一个完整的可视化报告不能只有折线和散点还要有地图和词云这类“视觉冲击力强”的图。地图可以展示各省票房贡献词云可以统计类型或片名的高频词。pyecharts的Map和WordCloud都需要额外安装配套资源这是后面避坑章节的重点先看核心代码from pyecharts.charts import Map, WordCloud, Pie # 地图假设你有省份票房数据 prov_data [(广东, 3.2e8), (江苏, 2.8e8), (浙江, 2.4e8)] map_chart Map() map_chart.add(票房, prov_data, china) map_chart.set_global_opts( title_optsopts.TitleOpts(title各省票房分布), visualmap_optsopts.VisualMapOpts(min_1e7, max_5e8) ) map_chart.render(chart_3_province_map.html) # 词云统计电影名中的高频词 word_pairs df[movie_name].value_counts().head(30).items() wc WordCloud() wc.add(, list(word_pairs), word_size_range[20, 100]) wc.render(chart_4_wordcloud.html)地图加visualmap_opts后不同省份会按票房数值映射出深浅颜色这是地图图表的灵魂配置不设的话所有省份只有一种底色等于没画。词云的word_size_range控制词大小的上下限需要根据你的数据量调整如果词太少把size调太大整个图会显得很空。这里的地图数据我没给出省份字段怎么拿常见做法是在爬虫阶段再写一个按城市定位的采集或者用现有开源的中国各省票房统计CSV做静态读取这取决于你想把项目深度放在爬虫还是可视化上。5. 避坑手册从爬虫到渲染的6个常见问题5.1 猫眼返回空数据json藏在script标签里而非HTML里现象是已经拿到HTML源码但用BeautifulSoup去find_all(div)时找不到任何票房节点打印出来是一堆空标签。原因是猫眼票房页面是服务端渲染加前端注水数据不在HTML结构里。解决方法是回到这篇笔记2.2节的思路用正则定位window.__INITIAL_STATE__把整个JSON抽出来体检先dict打印keys再一层层定位字段不要凭感觉去猜结构。5.2 pyecharts地图渲染出来是空白方块现象是图表画出来了但地图区域全部显示为灰色没有省份边界也没有数值颜色。根因是pyecharts自带的map用户需要显式添加地图文件新版pyecharts已经内置了地图数据但如果你用的是1.x旧版本需要执行pip install echarts-countries-pypkg pyecharts-snapshot或者把Map换成Geo试一下。另外注册地图文件后必须重启Python进程频繁在Jupyter里重跑代码块是看不到效果的。5.3 图表中文变成一排方块现象是标题和图例全是“口口口”。原因是HTML模板或浏览器渲染时找不到中文字体pyecharts默认使用theme和font_family两个配置。解决方法是设置全局选项里的textstyle_optsopts.TextStyleOpts(font_familyMicrosoft YaHei)在Linux服务器上要把font_family改成Noto Sans CJK SC或实际安装的中文字体名。这个坑在Windows本机开发时经常不出现一部署到云服务器就现原形。5.4 折线图出现断线跳变现象是时间序列图里某几天的数据点缺失折线会从上周三直接连到上周五中间画一条斜线看起来像数据跳崖。原因是你的原始表里缺少那几天的数据可能是平台当天没更新或者城市范围不完整。解决方法是先把日期列表用pd.date_range补全再把缺失值通过ffill或interpolate填充否则图表会暗示一个不存在的数据变化。5.5 爬虫请求被302重定向到验证页面现象是requests请求返回200但内容是一堆英文提示或者让你滑动验证。原因是请求频率过高服务器识别了你的User-Agent或者IP地址。解决方法是给每次请求之间增加time.sleep(1.5)同时每次启动爬虫时换一个新的User-Agent常见做法是维护一个UA列表随机抽选放弃之前每天请求几千次就能拿来用的想法。还有一种是降低目标把并发改成串行串行虽然慢但稳定单机项目不追求速度。5.6 Windows下csv中文乱码现象是清洗后的csv用Excel打开后中文全乱。原因是Python的to_csv默认utf-8编码而Excel在Windows下默认用GBK打开文件两边编码不匹配。解决方法是导出时统一用encodingutf-8-sig。这个坑在Mac上看不到一到给别人发文件的时候就翻车建议把“utf-8-sig”变成自己代码里的固定习惯。6. 从源码到作品多图联动与增量更新技巧当你能跑通上面所有图表以后最后一个提升作品完成度的步骤是把多个html合并成一个多标签页的报告并且让爬虫支持增量更新。pyecharts提供了Page和Tab两个容器Page按顺序拼长页面Tab生成浏览器顶部的标签切换我一般用Tab来做不同分析角度的切换比如“票房趋势”、“口碑分析”、“区域分布”三个标签页。合并代码很简短创建Tab对象add每张chart最后render(final_report.html)注意各chart实例要分别设置title否则Tab页面里会分不清当前看的是哪张图。增量更新的思路是给电影名加时间戳做成主键爬虫每次运行前先读取旧的CSV数据爬完新数据后按电影名加上映日期去重新电影追加老电影覆盖当日的票房字段。这比每次都全量重爬节省大量时间也能避免频繁请求导致被封。我自己第一次做这个项目时把大量时间花在调pyecharts的炫酷效果上最后发现真正决定报告质量的是数据是否干净每张图的配置反而只用了不到半天。希望这篇笔记能帮你把这个方向跑通做出一份自己敢讲清楚的完整作品。本文还有配套的精品资源点击获取
返回列表