
简介这是一套面向高校学生与Python初学者的数据可视化课程设计资料围绕豆瓣电影Top250数据集展开完整分析流程适用于毕业设计、期末大作业与课程设计等高分场景。资源包共221个文件约14.05MB包含27个Python源码文件、32张可视化图表、16个XML配置、5组Shapefile地理数据shp/dbf/shx/prj以及ipynb笔记本、PDF文档、PPT与说明文本覆盖数据采集、清洗、分析与图表呈现各环节。已有566人学习下载说明其参考价值获得一定认可。源码配有详细注释新手也能读懂并附带文档与PPT可直接用于答辩展示目录中同时保留Scrapy爬虫配置与地图数据便于扩展地理维度分析。整体部署简单下载后即可运行能帮助读者快速掌握Pandas、Matplotlib等工具在真实数据集上的应用并形成一份结构完整、导师认可的高分项目方案。1. 豆瓣Top250数据抓下来之后一份能交作业也能讲清楚的Python可视化方案很多人做 Python 数据可视化分析大作业第一步就卡在数据上——豆瓣电影 Top250 页面结构稳定、字段规整是练手数据分析与可视化的理想素材。但真正动手你会发现把数据抓下来只是开始后面清洗、建模、出图、写文档、做 PPT每一步都有坑。这篇笔记面向正在做数据分析项目实践的学生和转行者把豆瓣电影 Top250 数据分析与可视化的完整落地路径拆开讲从数据采集、Pandas 清洗、Matplotlib 与 Pyecharts 出图到最终怎么把源码、文档、PPT 串成一份能讲清楚的作品。读完你能照着复现一套完整流程也能避开我踩过的那些坑。2. 数据采集与清洗从页面到 DataFrame 的四个关键步骤2.1 为什么选 requests BeautifulSoup 而不是 Selenium豆瓣 Top250 的页面是服务端渲染的静态 HTML每页 25 条共 10 页。这种结构用 requests 加 BeautifulSoup 就够了没必要上 Selenium。Selenium 启动浏览器开销大在数据量只有 250 条的场景下属于杀鸡用牛刀。常见做法是用 requests 循环请求 10 个页面每页解析出排名、片名、导演、主演、年份、国家、类型、评分、评价人数、短评这些字段。请求头里必须带 User-Agent否则豆瓣会返回 418。另外请求间隔建议设 1 到 2 秒250 条数据分 10 次请求加延时也就多花十几秒但能避免被限流。我一般会在循环里用 time.sleep 加一个随机延时比固定值更自然。import requests from bs4 import BeautifulSoup import pandas as pd import time import random 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 } movies [] for page in range(10): url fhttps://movie.douban.com/top250?start{page * 25} resp requests.get(url, headersheaders, timeout10) soup BeautifulSoup(resp.text, html.parser) items soup.find_all(div, class_item) for item in items: rank item.find(em).text.strip() title item.find(span, class_title).text.strip() rating item.find(span, class_rating_num).text.strip() # 评价人数在 rating_num 后面的 span 里 people item.find(div, class_star).find_all(span)[-1].text.strip() info item.find(div, class_bd).p.text.strip() movies.append({ rank: rank, title: title, rating: rating, people: people, info: info }) time.sleep(random.uniform(1, 2)) df pd.DataFrame(movies) print(df.shape) print(df.head())这段代码的逻辑很直接循环 10 个页面每页用 BeautifulSoup 解析出电影条目把需要的字段塞进列表最后转成 DataFrame。参数上注意 timeout 设 10 秒避免某个请求卡死整个流程random.uniform(1, 2) 让每次请求间隔在 1 到 2 秒之间浮动。跑完之后 df.shape 应该是 (250, 5)如果不是说明某页解析出了问题需要检查页面结构是否变化。2.2 info 字段拆解年份、国家、类型怎么分列上面抓下来的 info 字段是一长串文本格式类似“1994 / 美国 / 犯罪 剧情”。这串文本里包含了年份、国家和类型需要拆成独立列才能做后续分析。常见做法是用 split 按斜杠切分但要注意有些条目国家不止一个类型也可能有多个。def parse_info(info): parts [p.strip() for p in info.split(/)] year parts[0] if len(parts) 0 else country parts[1] if len(parts) 1 else genre parts[2] if len(parts) 2 else return year, country, genre df[[year, country, genre]] df[info].apply( lambda x: pd.Series(parse_info(x)) ) # 评价人数去掉“人评价”并转成整数 df[people] df[people].str.replace(人评价, ).astype(int) df[rating] df[rating].astype(float) df[year] pd.to_numeric(df[year], errorscoerce) print(df[[title, year, country, genre, rating, people]].head())这里用 apply 加 pd.Series 的方式把一列拆成三列比逐行循环更简洁。参数上 errorscoerce 让无法转换的年份变成 NaN方便后续过滤。评价人数去掉“人评价”后转 int评分转 float。跑完这一步数据就规整了可以开始做分析。2.3 缺失值与重复值的处理边界豆瓣 Top250 的数据质量整体不错但仍有几个边界情况需要注意。第一有些电影年份跨多个年份比如“1994(中国大陆) / 1994(香港)”直接 pd.to_numeric 会变成 NaN需要先用正则提取第一个四位数字。第二国家字段可能包含多个国家用斜杠分隔做国家维度分析时要么取第一个要么拆成多行。第三类型字段同理一部电影可能同时属于剧情和爱情做类型统计时需要 explode 拆开。import re def extract_year(text): match re.search(r\d{4}, str(text)) return int(match.group()) if match else None df[year] df[year].apply(extract_year) df df.dropna(subset[year]) df df.drop_duplicates(subset[title]) print(f清洗后数据量: {len(df)})这段代码用正则从年份字段里提取第一个四位数字处理跨年份的情况。dropna 去掉年份缺失的行drop_duplicates 按片名去重。清洗后数据量可能略少于 250这是正常的不用强行凑数。3. 用 Pandas 做维度分析评分分布、年份趋势、国家与类型3.1 评分分布与评价人数的关系评分和评价人数是两个最核心的数值字段。先看评分分布用 describe 和 value_counts 快速了解数据的集中趋势。Top250 的评分集中在 8.0 到 9.7 之间大部分在 8.5 以上。评价人数则跨度很大从几万到几百万都有。print(df[rating].describe()) print(df[rating].value_counts().sort_index()) # 评分与评价人数的相关性 corr df[rating].corr(df[people]) print(f评分与评价人数相关系数: {corr:.3f})describe 给出均值、标准差、四分位数value_counts 看每个评分值有多少部电影。相关系数用 corr 计算如果结果接近 0说明评分高低和评价人数多少没有明显线性关系——这个结论本身就可以写进分析报告比强行找规律更有说服力。3.2 年份趋势哪个年代的高分电影最多年份字段清洗后可以按年代分组统计。常见做法是把年份映射到年代比如 1994 年归到 1990s然后统计每个年代的电影数量和平均评分。df[decade] (df[year] // 10) * 10 decade_stats df.groupby(decade).agg( count(title, count), avg_rating(rating, mean) ).reset_index() print(decade_stats)groupby 按年代分组agg 同时算数量和平均评分。reset_index 把分组结果转回普通 DataFrame方便后续画图。从结果能看出 1990s 和 2000s 的高分电影数量最多这跟豆瓣用户群体的观影偏好有关可以在文档里展开讨论。3.3 国家与类型拆开统计才看得清国家和类型字段都可能是多值直接 value_counts 会把“美国 / 英国”当成一个整体。正确做法是先按分隔符拆开再 explode 成多行最后统计。# 国家拆开统计 df[country_list] df[country].str.split(/) country_exploded df.explode(country_list) country_exploded[country_list] country_exploded[country_list].str.strip() country_counts country_exploded[country_list].value_counts().head(10) print(country_counts) # 类型拆开统计 df[genre_list] df[genre].str.split( ) genre_exploded df.explode(genre_list) genre_counts genre_exploded[genre_list].value_counts().head(10) print(genre_counts)explode 把一行多值拆成多行strip 去掉空格。国家统计取前 10类型统计取前 10。从结果能看出美国和日本电影占比最高剧情和爱情是最常见的类型。这些结论直接对应可视化图表后面画图时按这个顺序排布。4. 可视化落地Matplotlib 静态图与 Pyecharts 交互图怎么选4.1 Matplotlib 画评分分布直方图和年份趋势折线图Matplotlib 适合做静态图导出 PNG 放进文档和 PPT 里。评分分布用直方图年份趋势用折线图国家占比用条形图。中文字体需要设置否则会显示方块。import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(14, 5)) # 评分分布直方图 axes[0].hist(df[rating], bins15, color#4C72B0, edgecolorwhite) axes[0].set_title(豆瓣Top250评分分布) axes[0].set_xlabel(评分) axes[0].set_ylabel(电影数量) # 年份趋势折线图 year_counts df.groupby(year).size() axes[1].plot(year_counts.index, year_counts.values, color#DD8452, linewidth2) axes[1].set_title(各年份入选电影数量) axes[1].set_xlabel(年份) axes[1].set_ylabel(数量) plt.tight_layout() plt.savefig(rating_year.png, dpi150) plt.show()这段代码创建 1 行 2 列的子图左边画评分直方图右边画年份折线图。font.sans-serif 设成 SimHei 解决中文显示问题axes.unicode_minus 设 False 解决负号显示问题。dpi150 保证导出图片清晰度放进 PPT 不会糊。tight_layout 自动调整子图间距避免标签重叠。4.2 Pyecharts 做交互式图表词云和玫瑰图Pyecharts 生成 HTML 文件可以在浏览器里交互适合放进大作业的演示环节。类型分布用玫瑰图导演或演员用词云。Pyecharts 的配置项比较多但核心就是准备好数据对然后设置标题和尺寸。from pyecharts.charts import Pie, WordCloud from pyecharts import options as opts # 类型分布玫瑰图 genre_data genre_counts.reset_index() genre_data.columns [genre, count] pie ( Pie(init_optsopts.InitOpts(width800px, height500px)) .add( , [list(z) for z in zip(genre_data[genre], genre_data[count])], radius[30%, 70%], rosetyperadius ) .set_global_opts( title_optsopts.TitleOpts(title电影类型分布), legend_optsopts.LegendOpts(is_showFalse) ) ) pie.render(genre_pie.html)Pie 的 rosetype 设成 radius 就是玫瑰图radius 参数控制内外半径。add 方法里数据格式是 [(名称, 数值), ...]用 zip 加 list 转换。render 输出 HTML 文件双击就能在浏览器打开。词云类似把类型或导演名称和出现次数传进去即可。4.3 图表选型对照什么数据用什么图分析目标推荐图表工具理由评分分布直方图Matplotlib看集中趋势和偏态年份趋势折线图Matplotlib看时间变化国家占比条形图Matplotlib类别对比清晰类型分布玫瑰图Pyecharts交互展示占比关键词词云Pyecharts直观展示频率选图的核心原则是看分布用直方图看趋势用折线图看对比用条形图看占比用饼图或玫瑰图看频率用词云。不要为了炫技选不合适的图比如用饼图展示年份趋势就是典型的翻车操作。5. 避坑与排查数据抓取和可视化里的五个血泪教训5.1 请求返回 418 或空数据现象requests.get 返回状态码 418或者返回的 HTML 里找不到 item 节点。原因豆瓣检测到请求头缺失或请求频率过高。解决检查 User-Agent 是否设置请求间隔是否太短。我一般会在 headers 里加 Referer 和 Accept-Language进一步模拟浏览器行为。如果还是不行把延时调到 3 秒以上再试。5.2 中文字体显示方块现象Matplotlib 图表里的中文全部变成方块。原因默认字体不支持中文。解决设置 font.sans-serif 为 SimHei 或 Microsoft YaHei同时把 axes.unicode_minus 设为 False。如果是在 Linux 环境下跑需要先确认系统里装了中文字体没有的话用 font_manager 手动加载字体文件。5.3 年份字段转换后大量 NaN现象pd.to_numeric 转换年份后出现大量 NaN。原因年份字段里混有“1994(中国大陆)”这类带括号的文本。解决先用正则提取四位数字再转数值。正则用 r\d{4} 匹配第一个四位数字match.group() 取出结果。如果一部电影有多个年份取第一个即可不影响整体趋势分析。5.4 Pyecharts 图表在浏览器里空白现象render 生成的 HTML 打开后一片空白。原因Pyecharts 依赖的 JS 资源加载失败通常是网络问题。解决把 Pyecharts 的 CDN 资源换成本地文件或者用 pyecharts 的 snapshot 功能直接生成图片。另一个常见原因是数据格式不对add 方法要求数据是列表不是 DataFrame 列需要先转换。5.5 评价人数转 int 报错现象astype(int) 报 ValueError。原因评价人数里除了“人评价”还可能有多余空格或换行符。解决先用 str.strip() 去空格再用 str.replace(人评价, ) 去掉后缀最后 astype(int)。如果还有异常值用 pd.to_numeric(errorscoerce) 兜底把无法转换的变成 NaN 再过滤。6. 从源码到 PPT怎么把分析结果讲成一个完整故事6.1 项目结构组织与文档撰写要点一份能交出去的大作业源码、文档、PPT 三样东西要能互相印证。源码目录建议按功能分文件夹data 放原始数据和清洗后的数据scripts 放爬虫和清洗脚本charts 放可视化脚本output 放生成的图表和 HTML 文件。文档用 Markdown 写结构跟分析流程一致数据来源、清洗步骤、分析维度、可视化结果、结论。PPT 不要堆代码每页放一张图加一句结论讲的时候展开说。我一般会在文档里附一个 requirements.txt列出用到的库和版本。这样别人拿到源码能直接 pip install -r requirements.txt 跑起来。版本号不用写太死用 就行避免因为小版本差异装不上。6.2 验证分析结果是否靠谱的三个方法第一个方法是对比法把豆瓣 Top250 的评分分布跟豆瓣整体电影评分分布做对比看 Top250 是否明显偏高。第二个方法是交叉验证用不同年份区间分组看结论是否一致。比如 1990s 和 2000s 的高分电影类型分布是否相似。第三个方法是敏感性分析把评价人数阈值调高或调低看国家占比和类型占比是否发生显著变化。如果结论对参数不敏感说明分析结果比较稳健。# 敏感性分析示例不同评价人数阈值下的类型分布 for threshold in [10000, 50000, 100000]: subset df[df[people] threshold] subset_genre subset[genre].str.split( ).explode() top3 subset_genre.value_counts().head(3) print(f评价人数 {threshold}: {list(top3.index)})这段代码用三个不同的评价人数阈值过滤数据分别看类型分布的前三名是否一致。如果三个阈值下前三名都是剧情、爱情、犯罪说明类型分布结论比较稳。如果差异很大就需要在文档里说明原因。6.3 一个让 PPT 加分的技巧用 Pyecharts 生成可交互演示PPT 里放静态图有个问题观众看不清细节。我的习惯是在 PPT 里放一张静态图同时附一个二维码或者本地 HTML 文件路径让观众可以自己打开交互图看细节。Pyecharts 生成的 HTML 文件可以直接用浏览器打开鼠标悬停能看到具体数值缩放拖拽都支持。演示的时候先讲静态图的整体趋势再让观众自己操作交互图看细节比单纯放一张图效果好得多。具体做法是在 Pyecharts 的 render 方法里指定输出路径然后在 PPT 最后一页放上文件路径和操作说明。如果教室电脑没装 Python可以把 HTML 文件和依赖的 JS 文件一起打包用 U 盘拷过去直接打开。这个技巧我用了好几次每次都能让演示环节更顺畅。做这类大作业最深的体会是数据清洗花的时间永远比画图多但清洗干净了后面的分析和可视化就是水到渠成的事。别急着出图先把数据搞扎实。希望帮到你。本文还有配套的精品资源点击获取