欢迎光临
我们一直在努力

Python电影数据可视化分析系统:从爬虫到交互看板的完整实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

简介:这是一套面向高校学生与Python初学者的电影数据可视化分析系统完整源码包,适合用作期末大作业、课程设计或数据分析入门实战。项目以豆瓣电影Top250为数据源,串联网络爬虫、数据清洗、Echarts图表展示、词频统计与词云绘制等环节,并借助Flask搭建可视化网站,帮助读者理解从数据采集到前端呈现的完整链路。压缩包共8个文件,约3.5MB,包含4个py脚本、2个zip前端资源包、1个md说明文档和1个db数据库文件,其中py脚本承担爬虫、词云与Web入口逻辑,zip内为静态资源与页面模板,db存放已抓取的电影数据,md提供项目说明。运行前需先解压doubanMovie_flask目录下的static.zip与templates.zip。目前已有1684人学习下载,读者可据此掌握爬虫编写、数据处理、图表配置与Flask部署的排错思路,并直接复用目录结构完成二次开发。

1. 电影数据可视化分析系统:从一份期末大作业到能跑通的数据产品

期末周临近,很多同学手里都攥着一个「基于 Python 的电影数据可视化分析系统」的题目,需求很朴素:能爬数据、能清洗、能出图、能有个像样的界面,最后打包成一份带文档说明的源码交上去。但真正动手时才发现,坑不在写代码,而在数据从哪来、字段怎么对齐、图表怎么不糊、文档怎么写得像回事。这套系统本质是一条小型数据流水线:采集 → 清洗 → 存储 → 分析 → 可视化 → 交互展示,任何一环偷懒,最后答辩演示时都会翻车。

这篇文章面向两类人:一类是第一次做课程设计、想照着步骤把系统跑起来的新手;另一类是做过爬虫和 Pandas、但没把可视化与文档串成完整交付物的熟手。我会按真实落地顺序拆开讲,包括数据源选型、字段设计、可视化参数、文档结构,以及那些只有踩过才知道的坑。读完你应该能独立复现一套可演示、可讲解、可交付的电影数据分析系统,而不是交一份跑不起来的压缩包。

2. 数据从哪来:电影数据采集与字段设计

2.1 数据源选型:静态数据集还是自己爬

做电影数据可视化,第一步永远是数据。常见做法有三条路:一是用公开的静态数据集(比如 MovieLens、TMDB 的公开导出),二是自己写爬虫抓取,三是两者混合。选哪条取决于你的时间预算和答辩要求。

如果老师只看分析能力和可视化效果,静态数据集是最稳的。MovieLens 的 ratings.csv 和 movies.csv 结构干净,字段明确,几万条数据足够画出漂亮的分布图和热力图。缺点是字段偏少,缺少导演、票房、海报这类能撑场面的信息。

如果要求「有采集过程」,那就得自己爬。目标站点通常是 TMDB、豆瓣电影这类公开页面。这里要提醒一句:爬取前先看目标站点的 robots.txt 和使用条款,控制请求频率,只取公开的、非敏感的展示信息,不要碰用户隐私数据。我一般会把爬虫写成可断点续爬的形式,避免中途失败重头再来。

混合方案最实用:用静态数据集做主体分析,用爬虫补充少量字段(比如导演、类型标签),既保证数据量,又能体现采集能力。

2.2 用 requests + BeautifulSoup 抓取电影列表的最小实现

下面是一个抓取电影列表页的最小示例,目标是拿到片名、评分、年份、类型四个字段。实际站点结构会变,选择器要按真实页面调整。

import requests
from bs4 import BeautifulSoup
import csv
import time

HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
}

def fetch_page(url):
# 控制频率,避免给目标站点造成压力
time.sleep(1.5)
resp = requests.get(url, headers=HEADERS, timeout=10)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding
return resp.text

def parse_movies(html):
soup = BeautifulSoup(html, "html.parser")
rows = []
for item in soup.select(".movie-list .item"):
title = item.select_one(".title").get_text(strip=True)
rating = item.select_one(".rating").get_text(strip=True)
year = item.select_one(".year").get_text(strip=True)
genres = "|".join(g.get_text(strip=True) for g in item.select(".genre"))
rows.append([title, rating, year, genres])
return rows

def save_csv(rows, path="movies_raw.csv"):
with open(path, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["title", "rating", "year", "genres"])
writer.writerows(rows)

if __name__ == "__main__":
html = fetch_page("https://example.com/movies?page=1")
data = parse_movies(html)
save_csv(data)
print(f"抓取 {len(data)} 条")

逻辑说明: fetch_page 负责发请求并统一编码, time.sleep(1.5) 是礼貌间隔,别省。 parse_movies 用 CSS 选择器定位列表项,字段名和选择器要跟真实页面一致。 save_csv 用 utf-8-sig 编码,这样 Excel 打开不会乱码,这是血泪经验。

参数说明: timeout=10 防止请求卡死; apparent_encoding 自动识别编码,比硬写 utf-8 稳;分页抓取时把 URL 的 page 参数做成循环变量即可,但记得每页之间都要 sleep。

2.3 字段设计:哪些列必须有,哪些是加分项

字段设计决定了后面能画什么图。核心字段建议保留这几类:

字段 类型 用途
title 字符串 标识、词云
year 整数 年份趋势折线图
rating 浮点 评分分布直方图
genres 字符串(分隔) 类型占比饼图、类型-评分热力图
votes 整数 热度排序、散点图
director 字符串 导演作品数排行

加分项包括票房、时长、国家、语言。注意 genres 这种多值字段,存的时候用 | 或 , 分隔,分析时再拆开,别一开始就拆成多行,否则去重和统计会乱。年份字段要统一成整数,遇到「2023-05-01」这种日期格式,用 pd.to_datetime 转完再取 .dt.year 。

3. 清洗与存储:把脏数据变成能画图的表

3.1 用 Pandas 做清洗的五个固定动作

原始数据几乎没有直接能用的,清洗是绕不过去的一步。我一般固定做五件事:去重、缺失值处理、类型转换、异常值过滤、字段拆分。

import pandas as pd
import numpy as np

df = pd.read_csv("movies_raw.csv")

# 1. 去重:按片名+年份判断重复
df = df.drop_duplicates(subset=["title", "year"])

# 2. 缺失值:评分缺失的直接丢,类型缺失的填"未知"
df = df.dropna(subset=["rating"])
df["genres"] = df["genres"].fillna("未知")

# 3. 类型转换
df["year"] = pd.to_numeric(df["year"], errors="coerce")
df["rating"] = pd.to_numeric(df["rating"], errors="coerce")
df = df.dropna(subset=["year", "rating"])
df["year"] = df["year"].astype(int)

# 4. 异常值:评分只保留 0-10,年份只保留 1900 以后
df = df[(df["rating"] >= 0) & (df["rating"] <= 10)]
df = df[df["year"] >= 1900]

# 5. 拆分多值字段,生成一份展开表供类型分析用
df_exploded = df.assign(genre=df["genres"].str.split("|")).explode("genre")

df.to_csv("movies_clean.csv", index=False, encoding="utf-8-sig")
df_exploded.to_csv("movies_exploded.csv", index=False, encoding="utf-8-sig")
print(df.shape, df_exploded.shape)

逻辑说明: drop_duplicates 的 subset 很关键,只按 title 去重会误删翻拍片。 errors="coerce" 把无法转换的值变成 NaN,再统一 drop,比 try/except 循环干净。 explode 是处理多值字段的标准操作,展开后每个类型一行,画饼图和热力图都方便。

参数说明: encoding="utf-8-sig" 还是为了 Excel 兼容。 df.shape 打印出来对比清洗前后行数,能直观看到丢了多少数据,答辩时也是个可讲的点。

3.2 存储选型:CSV、SQLite 还是 MySQL

课程设计级别,CSV + SQLite 就够了。CSV 方便调试和交付,SQLite 单文件、免安装、支持 SQL 查询,适合做「数据管理」模块的演示。MySQL 除非老师明确要求,否则没必要,装环境的时间够你多画三张图。

import sqlite3

conn = sqlite3.connect("movies.db")
df.to_sql("movies", conn, if_exists="replace", index=False)
df_exploded.to_sql("movies_exploded", conn, if_exists="replace", index=False)

# 验证:查评分最高的十部
top10 = pd.read_sql(
"SELECT title, year, rating FROM movies ORDER BY rating DESC LIMIT 10",
conn
)
print(top10)
conn.close()

逻辑说明: to_sql 直接把 DataFrame 写进表, if_exists="replace" 每次重建,避免重复插入。 read_sql 返回的还是 DataFrame,后续画图无缝衔接。

参数说明: index=False 不把行号写进去。如果数据量大,可以加 chunksize=1000 分批写入。SQLite 的并发能力弱,但单机演示完全够用。

提示:清洗后的数据一定要单独存一份,别在原始数据上反复操作。原始数据是后悔药,丢了就得重爬。

4. 可视化实现:从静态图到可交互看板

4.1 Matplotlib 与 Seaborn:出图快但要注意中文字体

静态图是基础,Matplotlib 配 Seaborn 能覆盖大部分需求。新手最容易翻车的地方是中文字体,默认字体渲染出来全是方框。

import matplotlib.pyplot as plt
import seaborn as sns

# 中文字体设置,Windows 用 SimHei,Mac 用 Arial Unicode MS
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False # 负号正常显示

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 评分分布直方图
sns.histplot(df["rating"], bins=20, kde=True, ax=axes[0], color="#4C72B0")
axes[0].set_title("电影评分分布")
axes[0].set_xlabel("评分")
axes[0].set_ylabel("数量")

# 年份趋势折线图
year_count = df.groupby("year").size().reset_index(name="count")
year_count = year_count[year_count["year"] >= 1980]
sns.lineplot(data=year_count, x="year", y="count", ax=axes[1], color="#DD8452")
axes[1].set_title("历年电影数量趋势")
axes[1].set_xlabel("年份")
axes[1].set_ylabel("数量")

plt.tight_layout()
plt.savefig("overview.png", dpi=150)
plt.show()

逻辑说明: font.sans-serif 设成系统里真实存在的中文字体,Linux 服务器上可能没有 SimHei,要换成 WenQuanYi Micro Hei 之类。 axes.unicode_minus=False 解决负号变方框的问题。 groupby 后过滤年份,避免早期数据太少导致折线抖动。

参数说明: bins=20 是直方图分箱数,数据量大可以调到 30-50。 dpi=150 保证导出图片清晰,答辩投影不糊。 tight_layout 自动调整间距,省得手动调。

4.2 Pyecharts:交互式看板的正确打开方式

如果要求「可交互」,Pyecharts 是性价比最高的选择,生成的 HTML 可以直接在浏览器打开,鼠标悬停有提示,还能缩放。

from pyecharts import options as opts
from pyecharts.charts import Bar, Pie, Page

# 类型占比饼图
genre_count = df_exploded["genre"].value_counts().head(10)
pie = (
Pie()
.add("", [list(z) for z in zip(genre_count.index, genre_count.values)])
.set_global_opts(
title_opts=opts.TitleOpts(title="电影类型占比 Top10"),
legend_opts=opts.LegendOpts(orient="vertical", pos_left="left")
)
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {d}%"))
)

# 导演作品数柱状图
director_count = df["director"].value_counts().head(10)
bar = (
Bar()
.add_xaxis(director_count.index.tolist())
.add_yaxis("作品数", director_count.values.tolist())
.set_global_opts(
title_opts=opts.TitleOpts(title="导演作品数 Top10"),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=30))
)
)

page = Page(layout=Page.SimplePageLayout)
page.add(pie, bar)
page.render("dashboard.html")
print("看板已生成:dashboard.html")

逻辑说明: Page 把多个图拼成一个页面, SimplePageLayout 自动纵向排列。 value_counts().head(10) 取 Top10,避免图太挤。 formatter="{b}: {d}%" 让饼图标签显示名称和百分比。

参数说明: orient="vertical" 图例竖排,适合类型多的场景。 rotate=30 让 x 轴标签倾斜,防止重叠。生成的 HTML 依赖 CDN 加载 echarts.js,离线演示要提前把 js 文件下到本地并改引用路径,这是答辩现场断网的保命操作。

4.3 分析维度:哪些图能撑起一份高分作业

光有图不够,得有分析逻辑。建议覆盖这几个维度:评分分布(看整体质量)、年份趋势(看产量变化)、类型占比(看市场结构)、类型-评分热力图(看哪类片子口碑好)、导演/演员排行(看头部集中度)。每个维度配一段文字解读,说明「这张图说明了什么」,而不是只放图。

热力图用 sns.heatmap 做,行是类型、列是评分区间,值是该区间的电影数量。这种图信息密度高,答辩时容易讲出东西。

5. 避坑与排查:那些让演示当场翻车的细节

5.1 中文乱码:从 CSV 到图表的全链路排查

现象:CSV 用 Excel 打开是乱码,或者图表标题显示方框。 原因:编码不统一,或者字体没设对。 解决:写 CSV 统一用 utf-8-sig ;Matplotlib 显式设置 font.sans-serif ;Pyecharts 默认支持中文,不用额外设。Linux 环境下先 fc-list :lang=zh 看有没有中文字体,没有就装一个。

5.2 爬虫被封:请求频率和请求头没做对

现象:跑几十条后返回 403 或空页面。 原因:请求太快、没带 User-Agent、或者触发了站点的频率限制。 解决:加 time.sleep ,至少 1 秒;带上完整的 User-Agent;如果还不行,降低并发、加随机间隔。别用多线程猛冲,课程设计没必要,稳比快重要。

5.3 图表重叠:布局参数没调

现象:x 轴标签挤成一团,或者子图标题被裁掉。 解决: plt.tight_layout() 或 plt.subplots_adjust() ;Pyecharts 里用 rotate 倾斜标签,或者用 Grid 手动布局。数据类别超过 15 个就考虑只取 Top N,别硬塞。

5.4 文档说明写成流水账

现象:文档只有「第一步装 Python,第二步运行」,没有设计思路和字段说明。 原因:把文档当安装手册写。 解决:文档至少包含系统架构图(文字描述也行)、数据字典(字段含义和来源)、模块说明、运行步骤、依赖清单。老师看的是你的设计能力,不是复制粘贴的安装教程。

5.5 依赖版本冲突:requirements 没锁版本

现象:换台电脑跑不起来,报各种 ImportError。 解决:用 pip freeze > requirements.txt 导出当前环境,交付时附上。Python 版本也写清楚,比如 3.8 和 3.11 在某些库上行为不同。虚拟环境是标配,别在全局环境里装一堆东西。

6. 交付与进阶:让这份作业经得起追问

一份能拿高分的电影数据可视化系统,交付物不只是代码。我一般会整理成这样的结构: data/ 放原始和清洗后数据, src/ 放爬虫、清洗、分析、可视化四个脚本, output/ 放生成的图表和 HTML 看板,根目录放 README.md 和 requirements.txt 。README 里写清楚运行顺序:先跑爬虫(或直接用附带数据),再跑清洗,最后跑可视化。

进阶方向有两个值得做。一是把分析结果做成可筛选的交互看板,用 Streamlit 几行代码就能起一个带下拉框和滑块的页面,比静态 HTML 更有演示效果。二是加一个简单的推荐逻辑,比如「同类型高分电影推荐」,用类型和评分做相似度排序,不需要复杂模型,但能让系统从「展示」升级到「有输出」。

# Streamlit 最小看板示例
import streamlit as st
import pandas as pd

df = pd.read_csv("movies_clean.csv")
st.title("电影数据看板")

genre = st.selectbox("选择类型", ["全部"] + sorted(df["genres"].unique().tolist()))
min_rating = st.slider("最低评分", 0.0, 10.0, 7.0)

view = df[df["rating"] >= min_rating]
if genre != "全部":
view = view[view["genres"].str.contains(genre)]

st.write(f"共 {len(view)} 部")
st.dataframe(view[["title", "year", "rating", "genres"]].head(50))
st.bar_chart(view["year"].value_counts().sort_index())

逻辑说明: selectbox 和 slider 提供筛选交互, st.dataframe 展示表格, st.bar_chart 快速出图。运行 streamlit run app.py 即可在浏览器打开。

参数说明: min_rating 默认 7.0,范围 0-10。 head(50) 限制展示行数,避免页面卡顿。

最后说个习惯:每次改完代码,我都会从头跑一遍完整流程,确认没有依赖上一步的临时变量。课程设计最尴尬的不是功能少,而是演示时第一步就报错。把数据、代码、文档三样对齐,这份作业就稳了。希望帮到你。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

赞(0)
未经允许不得转载:171主机测评 » Python电影数据可视化分析系统:从爬虫到交互看板的完整实现
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址