Python电影数据可视化系统拆解:数据清洗到图表实战

📅 发布时间:2026/10/10 19:59:36
Python电影数据可视化系统拆解:数据清洗到图表实战
简介一份面向Python课程设计与期末大作业的电影数据可视化分析系统源码包适合计算机相关专业学生、数据分析入门者可作为课程实践、答辩参考和二次开发蓝本。资源共8个文件含4个Python脚本分别实现豆瓣电影数据爬取、词云生成、Flask Web可视化与辅助功能另有2个zip打包模板、1个SQLite数据库douban.db和1个Markdown说明文档压缩包总大小3.5MB短小轻量便于快速部署。包内数据与代码分层明确crawler.py负责采集WordCloud.py生成词云app.py驱动Web展示形成从数据获取、存储到可视化呈现的完整链路适合对照学习项目结构设计与数据处理流程。已有2565人浏览学习内容覆盖爬虫、数据库、Flask与词云等热点技术能帮助读者快速理解电影数据可视化项目的实现思路尤其适合期末答辩演示与个人作品积累。1. 电影数据可视化分析系统这个 Python 源码包拆开看是什么「基于Python的电影数据可视化分析系统源码.zip」——这类标题在课设、毕设和作品集里出镜率极高本质是一条非常标准的数据分析落地链路把电影数据抓下来清洗成结构化表格再用图表把评分、年份、类型分布讲清楚。我见过不少人拿到这类源码包后第一件事就是冲进 README然后卡在环境上Python 版本混用、pyecharts 与 matplotlib 的 API 记混、读 CSV 报编码错、爬虫被对方站点拒绝。这篇笔记不按“介绍项目”的套路写直接按取数、清洗、可视化的顺序把每个环节的命令、参数和坑拆开讲。适合正在做课设、想练 Python 数据分析、或者想快速搭一个电影数据看板的读者。2. 先跑通最小链路数据装载、pandas 清洗与环境依赖拿到任何源码包第一步不是看代码逻辑而是先把一条最小链路跑通数据能读进来、清洗不报错、能输出一张图。这一步过了后面所有功能都有地方挂靠。2.1 数据从哪来源码包里的 CSV、公开数据集与 API 三条路先解压压缩包看看有没有data/、dataset/目录或者.csv文件。常见的课设源码包一般会带一份电影数据文件名可能是movie_data.csv或films.csv里面通常已经包含标题、年份、评分、评价人数、类型这些字段。如果你手上的包没带数据那就只能另找来源常见做法有三条第一条是直接抓豆瓣 Top250 这类公开列表页简单直接但反爬压力越来越大适合演示用。第二条是去公开数据集平台找电影元数据字段全、数据量大但列名常常和你源码里的代码对不上需要花时间做字段映射。第三条是用 TMDB 之类的开放 API数据结构规整但要申请密钥而且有调用频率限制。对刚入门的人来说我最推荐先用 CSV 兜底。原因很现实爬虫和 API 都是不确定因素接口失败或不稳定时你没法判断到底是自己的代码问题还是对方站点的问题。而 CSV 是确定性的你只需要关心读入和清洗。等 CSV 链路跑通了再回头把爬虫接进来替换数据源整个系统的鲁棒性会好很多。另外注意 CSV 的编码很多下载的数据是 GBK 或 UTF-8后面避坑章节会专门讲。2.2 最小可运行脚本CSV 装载与清洗的 pandas 骨架先看清楚数据长什么样再动手清洗。下面这个脚本可以当作整个系统的入口骨架我会在项目里把它命名为load_clean.py后续所有图表都从它产出的df开始。import pandas as pd # 1. 读入原始 CSV。encoding 先按 utf-8 试失败再换 gbk # enginepython 能规避部分分隔符/引号导致的解析问题 df pd.read_csv(movie_data.csv, encodingutf-8, enginepython) # 2. 打印形状和列名确认数据真实存在 print(df.shape) print(df.columns.tolist()) # 3. 丢掉整行全为空的数据这类“空行”最常见 df df.dropna(howall) # 4. 数值列从字符串转成数值。errorscoerce 表示转不了就置为 NaN for col in [rating, votes]: df[col] pd.to_numeric(df[col], errorscoerce) # 5. 只保留后面要用的列减少内存占用和列名噪音 keep_cols [title, year, genre, rating, votes] df df[keep_cols].dropna(subset[title, year]) # 6. 年份列处理成整数明显异常的值直接过滤掉 df[year] pd.to_numeric(df[year], errorscoerce) df df[df[year].between(1900, 2026)] df[year] df[year].astype(int) print(df.head()) print(df.dtypes)这段代码看着短但每个参数都是踩过坑换来的。encodingutf-8是默认选择但很多电影数据是从 Excel 导出的实际编码可能是 GBK如果读入报UnicodeDecodeError就把这里换成encodinggbk再试。enginepython不是必须的但它能在文件里有不规则引号或分隔符时减少解析错误。to_numeric配合errorscoerce是做数据清洗时最常用的组合它会把“7.5”这种字符串转成浮点数把“暂无评分”这种脏数据变成NaN而不是直接让程序崩溃。dropna(subset[title, year])只检查关键列这样不会把“有标题但没类型”的行误删。between(1900, 2026)是个简单粗暴但有效的年份过滤能直接把明显异常的脏数据清掉。提示如果你在 Windows 上双击运行脚本建议在文件末尾加一行input(按回车退出)否则窗口一闪而过你根本看不清报错信息。2.3 环境依赖锁定Python 3.8 与 pandas、pyecharts、matplotlib 的版本匹配这个系统的依赖不少常见的有 pandas、matplotlib、pyecharts、requests、flask。网上很多源码包 README 里只写了pip install -r requirements.txt但 requirements 里的版本号往往已经过期。我一般建议用虚拟环境把 Python 3.8 作为基线版本因为 3.8 对 pandas 1.x、pyecharts 2.x 的兼容性最稳而且不会遇到 3.12 下某些旧版本扩展库装不上的问题。python -m venv venv # Windows 下激活虚拟环境 venv\Scripts\activate # macOS / Linux 下用下面这行 # source venv/bin/activate pip install pandas1.5.3 matplotlib3.7.1 pyecharts2.0.3 flask2.3.2 requests2.31.0这里锁定版本是有原因的。pandas 2.x 改了一些索引和类型推断行为老代码在新版本上偶尔会出现诡异警告pyecharts 1.x 和 2.x 的 API 差异非常大1.x 用add()方法2.x 改成add_xaxis()和add_yaxis()如果你照着网上的老教程写代码会在 2.x 下直接报错。matplotlib 3.7 和 3.8 的字体管理也有细微差别锁 3.7.1 能减少中文字体问题。如果你需要 numpy直接pip install numpy即可它会自动匹配当前 Python 版本。等依赖装完跑一遍 2.2 节的清洗脚本如果能正常打印出df.head()说明最小链路已经通了后面可以放心地往上加爬虫和图表。3. 接上爬虫取数JSON 接口抓取、字段映射与存储CSV 链路跑通后如果不想永远依赖别人给的数据就得把爬虫接进来。电影站点的数据获取我的经验是优先找 JSON 接口而不是去解析 HTML 页面这里面的效率差距和踩坑概率差一个数量级。3.1 为什么优先找 JSON 接口而不是解析 HTML很多电影网站的前端页面背后都有对应的 JSON 接口比如搜索接口、榜单接口、详情接口。用requests直接请求这些接口返回的是结构化数据字段清清楚楚转成 DataFrame 非常快。而解析 HTML 需要用正则或 XPath 去抠元素页面结构只要一改版你的解析代码就立刻失效而且电影页面里的广告、推荐位、动态渲染内容都会干扰解析结果。判断一个接口是不是 JSON最简单的方法是在浏览器里打开开发者工具的网络面板刷新页面看 XHR 类型的请求凡是返回application/json的都可以直接请求。找到接口后先看它的请求参数和返回结构再动手写爬虫脚本。对电影类站点来说常见的分页参数是start和count对应偏移量和每页条数常见返回结构是subjects数组里面每一项包含标题、年份、评分等字段。这个特征在多个电影站点的接口里都很一致。3.2 带重试与限速的抓取脚本headers、timeout、退避参数爬虫部分不能只写一个requests.get()就完事否则跑不了几个请求就会被对方站点识别。我一般会封装一个带重试和退避的请求函数核心是让程序在失败时“慢下来”而不是疯狂重试。下面是一个可以直接改来用的脚本骨架import time import random import requests import pandas as pd # 用 Session 复用 TCP 连接避免每次请求都重新握手 session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9, }) # API_BASE 是示例地址换成你实际抓到的 JSON 接口地址 API_BASE https://api.example.com/movie/top250 def fetch_json(url, retries3, timeout10): for attempt in range(retries): try: resp session.get(url, timeouttimeout) if resp.status_code 200: return resp.json() # 418 / 403 说明被识别为爬虫退避后重试 print(fHTTP {resp.status_code}, 第 {attempt 1} 次重试) time.sleep(random.uniform(2, 5) * (attempt 1)) except requests.RequestException as e: print(f请求异常: {e}) time.sleep(3) return None rows [] for page in range(0, 100, 20): url f{API_BASE}?start{page}count20 data fetch_json(url) if not data: break for item in data.get(subjects, []): rating item.get(rating) or {} rows.append({ title: item.get(title), year: item.get(year), rating: rating.get(value), votes: rating.get(count), }) # 每页之间随机停顿别让对方一眼看出是脚本 time.sleep(random.uniform(1, 2)) df pd.DataFrame(rows) df.to_csv(movie_raw.csv, indexFalse, encodingutf-8-sig) print(df.head())这里几个参数值得解释。timeout10是必须的没有超时限制的请求可能一直挂在那整个脚本就卡死了。retries3只重试 3 次并且每次重试的等待时间按random.uniform(2, 5) * (attempt 1)递增这叫指数退避目的是让封禁风险随着重试次数增加而降低。time.sleep(random.uniform(1, 2))是页面级限速别小看这一秒很多反爬封禁都是因为请求频率太高。Session对象的作用是复用底层的 TCP 连接同时统一维护 headers不用在每个请求里重复写。最后输出 CSV 时我用encodingutf-8-sig这个编码格式会在文件头部加一个 BOM 标记Windows 上的 Excel 打开后不会乱码。如果你不需要用 Excel 看数据用普通的utf-8也行。3.3 嵌套 JSON 转 DataFrame字段路径映射与类型修正从接口拿回来的 JSON 通常是嵌套结构比如评分是一个对象里面还挂着value和count。直接把整个 JSON 塞进 DataFrame 只会得到一堆字典对象后续统计根本没法做。我一般会先把嵌套字段拍平手动建立字段映射关系再交给 pandas。接口 JSON 路径DataFrame 列处理后类型说明subjects[].titletitlestr电影名保留原样subjects[].yearyearint用pd.to_numeric转整型subjects[].rating.valueratingfloat平均评分可能缺失subjects[].rating.countvotesint评价人数排序时会用到字段映射看起来简单但有一个高频报错点item.get(rating)可能返回None如果你直接.get(value)就会报AttributeError。所以我在代码里先做了rating item.get(rating) or {}的兜底把空值替换成空字典这样后续访问就不会崩。这是一个很小的细节但能避免爬虫跑到一半突然中断。字段拍平后必须做类型修正。接口里year可能是字符串votes可能是字符串如果不转后面df.sort_values(votes)会按字典序排结果完全错误。处理方式和第 2 章一样用pd.to_numeric(..., errorscoerce)把脏数据置为NaN分析时再用dropna或fillna处理。存储时我建议保留一份原始 JSON 或 CSV方便后续重新清洗不用再爬一次。4. 图表层做出演示效果pyecharts 与 matplotlib 的选型与必调参数数据洗好之后可视化这步直接决定你整个系统的观感。很多人交上去的作品图表一看就是 “默认样式全家桶”配色丑、坐标轴不解释、鼠标悬停没有反馈。这章我按实际项目里的分工来讲什么时候用 matplotlib什么时候用 pyecharts以及几个必调的参数。4.1 pyecharts 和 matplotlib 怎么分工交互看板 vs 静态论文图matplotlib 和 pyecharts 不冲突它们解决的问题不一样。matplotlib 适合出静态图保存成 PNG 放进论文、实验报告里样式稳定不依赖网络pyecharts 包装的是 ECharts输出 HTML 文件图表可以缩放、悬停提示、联动做演示和看板非常出效果。如果你是交课设建议两个都用静态图放文档交互图放进 Flask Web 页面。对比点matplotlibpyecharts输出形式PNG / SVG 静态图片HTML 网页可交互适合场景论文插图、报告截图演示、数据看板、大屏中文支持需手动配置字体浏览器渲染依赖 echarts.js学习成本低自由度大中配置项多但套路固定如果你的目标是“企业级数据可视化”真正该用的是 BI 工具或者直接上 ECharts 大屏而不是在这套系统里死磕图表特效。这套 Python 系统的价值在数据链路本身而不是视觉效果。所以我在实际做的时候只保留三种图表类型分布直方图、趋势折线图、占比饼图够用且不容易出错。4.2 评分分布直方图与年份趋势折线图的落地代码评分分布用 matplotlib年份趋势用 pyecharts这正好能把两种工具的用法都覆盖到。先看评分分布import matplotlib import matplotlib.pyplot as plt # 中文字体配置具体说明见 4.3 matplotlib.rcParams[font.sans-serif] [ Microsoft YaHei, SimHei, PingFang SC, Noto Sans CJK SC ] matplotlib.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(10, 5)) ax.hist(df[rating].dropna(), bins20, edgecolorblack, alpha0.75) ax.set_title(电影评分分布直方图) ax.set_xlabel(评分) ax.set_ylabel(影片数量) plt.tight_layout() plt.savefig(rating_dist.png, dpi150)bins20控制直方图的分箱数数据量为几百条时 20 箱能看出分布形状数据量更大可以试着加 to 30 或 40。alpha0.75是透明度柱状图重叠时能看出层次。dpi150决定输出图片清晰度论文插图 150 够用如果要投印刷级再调到 300。tight_layout()是为了避免标题和坐标轴文字被裁掉。再看年份趋势折线图用 pyecharts 输出交互式 HTMLfrom pyecharts.charts import Line import pyecharts.options as opts # 按年份聚合平均评分 影片数量 year_stats df.groupby(year).agg( avg_rating(rating, mean), movie_count(title, count), ).reset_index().sort_values(year) line ( Line() .add_xaxis(year_stats[year].astype(str).tolist()) .add_yaxis( 平均评分, year_stats[avg_rating].round(2).tolist(), is_smoothTrue, ) .set_global_opts( title_optsopts.TitleOpts(title电影年份趋势平均评分与产量变化), xaxis_optsopts.AxisOpts(type_category, name年份), yaxis_optsopts.AxisOpts( type_value, name平均评分, min_0, max_10, splitline_optsopts.SplitLineOpts(is_showTrue), ), tooltip_optsopts.TooltipOpts(triggeraxis), ) ) line.render(templates/charts/year_trend_chart.html)pyecharts 2.x 的写法就是链式调用先建Line()然后add_xaxis、add_yaxis、set_global_opts一步步拼。is_smoothTrue把折线变成平滑曲线视觉上更舒服。yaxis_opts里的min_0, max_10是强制评分轴范围否则 pyecharts 默认会根据数据自动缩放评分范围 7 到 9 就会把细微波动放大看起来像过山车。tooltip_opts里triggeraxis表示鼠标移动时按整条轴显示所有数据点比默认的item触发更好用。提示pyecharts 渲染时会生成完整的 HTML 文件包含引用的 echarts.js 资源。如果你把 HTML 文件发给别人对方离线打开可能白屏解决办法见 5.3 节避坑说明。4.3 中文字体配置Windows、macOS、Linux 三平台的处理方式matplotlib 默认字体没有中文字形不配置的话所有中文标签都会变成方块。网上常见的方案是只会告诉你rcParams[font.sans-serif] [SimHei]但换到 macOS 或 Linux 服务器上又失效。我自己的做法是列一个字体回退列表import matplotlib from matplotlib import font_manager # 如果项目里有 fonts/ 目录直接加载本地字体文件 # 这样即使系统没装对应字体也不会乱码 font_manager.fontManager.addfont(fonts/NotoSansCJK-Regular.ttc) matplotlib.rcParams[font.sans-serif] [ Noto Sans CJK SC, Microsoft YaHei, SimHei, PingFang SC ] matplotlib.rcParams[axes.unicode_minus] FalseWindows 上通常有SimHei或Microsoft YaHeimacOS 上常见PingFang SCLinux 服务器上一般需要安装Noto Sans CJK SC。把字体文件名按顺序列在列表里matplotlib 会从左到右找第一个可用的。axes.unicode_minus False必须设置否则坐标轴负号会显示成方框。如果你的代码要部署到没装字体的服务器上最稳妥的做法是下载一个开源中文字体文件放进项目目录的fonts/文件夹用font_manager.addfont()加载。这样你的图在任何机器上生成都是同样的效果不会因为换机器就变 “方块图”。5. 跑这套系统的五个高频坑编码、反爬、类型与渲染排查这套系统看起来功能不多但会把 Python 数据处理里最常见的几类问题全踩一遍。我按现象到原因到解决的顺序整理五个高频翻车点基本覆盖你跑源码包里前几个小时的报错。5.1 请求返回 418/403被反爬识别后的重试与降速现象爬虫脚本运行时requests.get()返回 418 或 403有时候返回 200但拿到的内容里有“访问验证”这类字样。原因请求头里缺User-Agent或是请求频率太高被站点识别成脚本。418 是很多站点专门用来拒绝爬虫的状态码。解决用requests.Session统一维护 headers把User-Agent、Accept-Language、Referer都补上。每页请求间隔至少 1 秒遇到 418 采用指数退避重试。如果换了很多 headers 还是被拒就别硬爬了直接改用 CSV 或开放的公开数据集省下的时间够你多调两张图。5.2 数值列全是 object无法做数值统计与排序现象df.dtypes显示rating、votes、year全是object用df.sort_values(votes)排序后发现顺序完全不对比如 “99” 排在 “1000” 后面。原因pandas 读取 CSV 时如果某一列存在空字符串、“暂无”这类脏值整列会被识别成字符串类型。解决用pd.to_numeric(series, errorscoerce)强制转换转换失败的值自动变成NaN。转换后检查一下df[rating].isna().sum()确认有多少脏数据如果比例超过 10%说明数据源本身有问题回源头清洗更靠谱。5.3 pyecharts 渲染出的 HTML 是空白页现象line.render(output.html)执行完浏览器打开 output.html 却是一片空白控制台报错说某个.js文件加载失败。原因pyecharts 生成 HTML 时默认引用远程 ECharts 资源。如果电脑没联网、开了广告拦截或者资源 CDN 被污染图表就加载不出来页面自然空白。解决先打开浏览器开发者工具看网络请求确认是不是 JS 资源加载失败。如果是就把项目所需的 echarts.js 资源下载到本地把 HTML 里的引用路径改成相对路径另一个绕过方式是换用 matplotlib 输出静态图虽然没交互效果但至少不白屏。如果 HTML 在某些电脑上正常、另一些上白屏基本就是网络环境问题。5.4 pandas 读取 CSV 报 UnicodeDecodeError现象pd.read_csv(movie_data.csv, encodingutf-8)直接抛UnicodeDecodeError: utf-8 codec cant decode byte...文件读不进来。原因文件的实际编码不是 UTF-8而是 GBK 或 GB18030。很多国内网站导出的 CSV 都是 GBK 编码用 UTF-8 解码当然报错。解决先改成encodinggbk或gb18030再读。如果你拿到的数据是无 BOM 的 UTF-8也可以先尝试utf-8-sig。如果不想每次手动试可以用一个简单兜底逻辑先按 UTF-8 读抛异常后自动换 GBK 重试。同时以后写 CSV 时统一用encodingutf-8-sig这样 Excel 能正常打开其他 Python 程序读起来也方便。5.5 Flask 启动时端口被占用现象启动 Flask 项目时终端报OSError: [Errno 98] Address already in use或者 Windows 上提示端口被占用。原因上一次的 Flask 进程没退出或者 5000/8000 端口被其他程序占用。调试模式开启时Flask 会自动重载代码有时旧的子进程没有完全释放。解决先换一个端口跑比如app.run(port8001)这是最快的处理方式。如果想要根治Windows 下用netstat -ano | findstr :8000找到对应 PID再用taskkill /PID xxxx /F结束进程macOS/Linux 下用lsof -i :8000定位进程。另外开发时记得把debugTrue关掉再部署调试模式下监听的机制在部分系统上会放大这个问题。6. 把静态图表升级成可用系统Flask 动态渲染与数据更新图表单独渲染成 HTML 文件只能算“半成品”。让别人通过浏览器访问一个地址、看到最新数据才是这套系统真正变成“系统”的那一步。这一步不需要做多复杂用 Flask 把两张图托管起来再配一个数据更新脚本就够了。import os from flask import Flask, send_from_directory app Flask(__name__) # 约定所有图表 HTML 都输出到 templates/charts/ 目录 CHART_DIR os.path.join(os.path.dirname(__file__), templates, charts) app.route(/chart/chart_name) def show_chart(chart_name): # 只允许访问 _chart.html 结尾的文件防止路径穿越 if not chart_name.endswith(_chart.html): return not found, 404 return send_from_directory(CHART_DIR, chart_name) app.route(/) def index(): return a href/chart/year_trend_chart.html查看年份趋势/a if __name__ __main__: app.run(host0.0.0.0, port8000, debugTrue)这个路由设计里有两个关键点一是把图表文件统一放在templates/charts/目录让数据文件和页面文件分离二是用endswith(_chart.html)做了文件名校验避免别人通过路径穿越读取服务器上的任意文件。send_from_directory本身也会做路径安全检查双保险更稳。数据更新方面我习惯把“抓取数据”和“生成图表”拆成两个脚本。每次更新时先跑爬虫生成新的movie_raw.csv再跑清洗和图表脚本覆盖旧的 HTML 文件最后刷新浏览器。这样数据更新和页面展示完全解耦哪天爬虫挂了你至少还能用旧数据生成图表。验证方法很简单浏览器打开http://localhost:8000/chart/year_trend_chart.html能看到图表且鼠标悬停有数据提示整条链路就是通的。我做这类系统有个改不掉的习惯所有输出文件的路径都用相对路径并且以脚本所在目录为基准来拼很少直接用 “/root” 这种绝对路径。因为代码今天在自己电脑上跑明天就可能丢到服务器或别人机器上路径一写死换个环境就是一片红色报错。这个习惯帮我省了不知道多少排错时间。希望帮到你。本文还有配套的精品资源点击获取