Python 爬虫实战:豆瓣高分电影可视化分析(四维爬虫 + 11 张图,附完整源码)3部分 一、项目背景与目标豆瓣电影作为国内最具影响力的影评社区其评分数据是分析电影市场趋势、观众偏好和内容质量的宝贵资源。本项目旨在通过 Python 爬虫技术从豆瓣电影网站爬取四个维度的数据Top250、类型、地区、年代并进行清洗、存储和可视化分析最终生成 11 张图表全方位揭示高分电影的特征与规律。项目目标掌握多维度、合规的 Python 爬虫实战技巧。学习数据清洗、合并与存储星型-雪花模型的完整流程。使用 Matplotlib、Seaborn、Plotly 等库进行高质量数据可视化。从数据中挖掘出有价值的洞察为电影内容创作、投资决策提供参考。二、技术栈与工具准备核心库爬虫requests, BeautifulSoup, pandas, time控制请求频率数据处理pandas, numpy可视化matplotlib, seaborn, plotly数据库可选SQLite / MySQL用于星型-雪花模型存储环境配置# 安装依赖 pip install requests beautifulsoup4 pandas numpy matplotlib seaborn plotly中文字体配置Windowsimport matplotlib.pyplot as plt import matplotlib as mpl from matplotlib.font_manager import FontProperties font_path C:/Windows/Fonts/simhei.ttf font_prop FontProperties(fnamefont_path) mpl.rcParams[font.family] [SimHei, Microsoft YaHei] mpl.rcParams[axes.unicode_minus] False plt.style.use(seaborn-v0_8-whitegrid)三、四维爬虫设计与实现为确保爬虫合规、稳定我们设计了四个独立的爬虫脚本分别针对不同维度的数据并严格控制请求间隔。3.1 Top250 爬虫douban_spider250.py爬取豆瓣电影 Top250 榜单包含电影名称、评分、导演、主演、年份、地区、类型等详细信息。3.2 类型维度爬虫douban_type.py按电影类型如剧情、喜剧、动作等爬取高分电影列表。def get_movies_by_type(movie_type, page_limit10): base_url https://movie.douban.com/j/new_search_subjects movies [] for page in range(page_limit): params { sort: R, range: 7.5,10, tags: movie_type, start: page * 20 } resp requests.get(base_url, headersheaders, paramsparams) data resp.json() for item in data[data]: movies.append({ 电影名称: item[title], 评分: float(item[rate]), 类型: movie_type }) time.sleep(1) # 单请求间隔 return pd.DataFrame(movies)3.3 地区维度爬虫douban_region.py按制片地区如中国大陆、美国、日本等爬取高分电影。def get_movies_by_region(region, page_limit8): # 类似类型爬虫替换 tags 参数为地区 pass3.4 年代维度爬虫douban_time.py按上映年代如 90年代、2000年代、2010年代等爬取高分电影。def get_movies_by_decade(decade, page_limit6): # 类似类型爬虫替换 tags 参数为年代 pass四、数据合并与清洗4.1 四维数据合并四组数据字段不同用电影名做外连接合并逻辑来自数据合并处理.ipynbimport pandas as pd df_time pd.read_csv(douban_time.csv) # 列电影名称, 豆瓣ID, 评分, 数据来源 df_region pd.read_csv(douban_region.csv) # 列电影名称, 评分, 地区, 评分段 df_type pd.read_csv(douban_type.csv) # 列电影名称, 评分, 类型, 评分段 保留必要字段 df_time_clean df_time[[电影名称, 数据来源]].copy() df_time_clean.rename(columns{数据来源: 年代标签}, inplaceTrue) df_region_clean df_region[[电影名称, 地区]].copy() df_type_clean df_type[[电影名称, 类型, 评分]].copy() # 评分从类型表带过来 按电影名称外连接合并再按名称去重 df_merged pd.merge(df_time_clean, df_region_clean, on电影名称, howouter) df_merged pd.merge(df_merged, df_type_clean, on电影名称, howouter) df_merged df_merged.drop_duplicates(subset[电影名称], keepfirst) df_merged df_merged[[电影名称, 评分, 年代标签, 地区, 类型]] print(合并后总电影数:, len(df_merged)) # 2448 df_merged.to_csv(douban_total.csv, indexFalse, encodingutf-8-sig)4.2 数据清洗缺失 / 异常 / 重复三步走合并后的数据存在评分缺失等问题清洗函数来自数据处理.ipynbdef clean_movie_data(df, core_col评分, id_col豆瓣ID, name_col电影名称): df_clean df.copy() print(f\n开始清洗原始样本量 {len(df_clean)}) # 1. 缺失值处理评分是核心字段直接删除 if core_col in df_clean.columns: before len(df_clean) df_clean df_clean.dropna(subset[core_col]) print(f 缺失值删除{before - len(df_clean)} 条) 2. 异常值过滤评分必须在 7.5 ~ 10 之间 if core_col in df_clean.columns: before len(df_clean) df_clean df_clean[(df_clean[core_col] 7.5) (df_clean[core_col] 10)] print(f 异常值过滤{before - len(df_clean)} 条) 3. 样本去重有豆瓣ID按ID去重否则按电影名去重 before len(df_clean) if id_col in df_clean.columns: df_clean df_clean.drop_duplicates(subset[id_col]) else: df_clean df_clean.drop_duplicates(subset[name_col]) df_clean df_clean.reset_index(dropTrue) print(f 重复样本删除{before - len(df_clean)} 条) print(f✅ 清洗完成最终样本量 {len(df_clean)}) return df_clean 批量清洗五组数据total 没有豆瓣ID按电影名去重 df_top250_clean clean_movie_data(df_top250) df_type_clean clean_movie_data(df_type, id_colNone) df_region_clean clean_movie_data(df_region, id_colNone) df_time_clean clean_movie_data(df_time) df_total_clean clean_movie_data(df_total, id_colNone)实际运行输出真实结果开始清洗原始样本量 250 → 最终样本量 250 开始清洗原始样本量 1164 → 最终样本量 1164 开始清洗原始样本量 1263 → 最终样本量 1263 开始清洗原始样本量 746 → 最终样本量 746 开始清洗原始样本量 2448 → 缺失值删除 1284 条 → 最终样本量 1164清洗前后对比数据集原始样本量清洗后样本量缺失删除异常过滤重复删除douban_top250.csv250250000douban_type.csv11641164000douban_region.csv12631263000douban_time.csv746746000douban_total.csv24481164128400踩坑提醒合并表里 1284 条评分缺失大多是因为类型表有评分、年代/地区表没有导致外连接后为空所以清洗时以评分为主键列删除。清洗后的douban_total_clean.csv1164 条就是后面可视化的总数据集。五、数据存储星型 雪花模型清洗后的数据按星型-雪花结合模式建库以电影 MOVIE为事实表拆出时间、地区、类型、总览四个维度表再挂一张聚合表存电影数量 平均分。这样设计的好处既能按维度快速汇总星型又能下钻到每部电影的导演、演员细节雪花分支。比如想统计日本动画片各年代平均分一条 SQL 就能搞定。六、可视化实现真实源码 运行输出可视化代码集中在数据可视化处理.ipynb本文只挑核心片段贴出来图片全部是程序实际运行输出的 PNGdpi300。6.1 环境配置中文字体是第一个坑import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl import seaborn as sns from matplotlib.font_manager import FontProperties # 中文字体配置 font_path C:/Windows/Fonts/simhei.ttf font_prop FontProperties(fnamefont_path) mpl.rcParams[font.family] [SimHei, Microsoft YaHei] mpl.rcParams[axes.unicode_minus] False plt.style.use(seaborn-v0_8-whitegrid) 全局配置 SAVE_PATH r...\可视化处理\可视化图片 def save_fig(fig_name): plt.tight_layout() plt.savefig(f{SAVE_PATH}\{fig_name}.png, dpi300, bbox_inchestight) plt.close()踩坑提醒Windows 下 Matplotlib 默认字体不含中文不设置的话标题和图例全是方框axes.unicode_minus False是为了让负号正常显示。6.2 数据加载与预处理df_total pd.read_csv(...\清洗后数据集\douban_total_clean.csv) df_top250 pd.read_csv(...\清洗后数据集\douban_top250_clean.csv) 1. 类型拆分一部电影可能有多个类型用空格分隔 df_type_expand df_total.dropna(subset[类型]).copy() df_type_expand[类型] df_type_expand[类型].str.split( ) type_expand df_type_expand.explode(类型) 2. 年代标签映射为大致年份 year_map { 更早: 1950, 60年代: 1965, 70年代: 1975, 80年代: 1985, 90年代: 1995, 2000年代: 2005, 2010年代: 2015, 2019: 2019, 2020: 2020, 2021: 2021, 2022: 2022 } df_total[上映年份_映射] df_total[年代标签].map(year_map).fillna(2000).astype(int) 3. 四大社会时期划分 def get_period(year): if 1947 year 1991: return 冷战时期 elif 2007 year 2009 or year 2020: return 经济危机时期 elif 2020 year 2022: return 疫情时期 elif (2001 year 2007) or (2010 year 2019): return 世界经济上行时期 else: return 其他时期 df_total[社会时期] df_total[上映年份_映射].apply(get_period) df_period df_total[df_total[社会时期] ! 其他时期].copy()6.3 总数据集五连图高分电影的普遍规律① 年代数量与评分趋势双轴图time_analysis df_total.groupby(年代标签).agg( 电影数量(评分, count), 平均评分(评分, mean) ).reset_index() fig, ax1 plt.subplots(figsize(14, 6)) ax1.bar(time_analysis[年代标签], time_analysis[电影数量], color#4ECDC4, alpha0.8) ax1.set_ylabel(电影数量) ax2 ax1.twinx() ax2.plot(time_analysis[年代标签], time_analysis[平均评分], color#FF6B6B, markero, linewidth3) ax2.set_ylabel(平均评分) plt.title(高分电影年代数量与评分趋势) save_fig(01_总数据集_年代趋势分析)