当前位置: 首页 > news >正文

豆瓣电影TOP250数据采集、清洗与多维可视化实战(含源码+文档+可运行环境)

本文还有配套的精品资源,点击获取

简介:直接跑起来的Python电影数据分析项目,自动抓取豆瓣电影TOP250页面的片名、导演、类型、评分、评论数、上映年份等字段;内置反爬适配逻辑,支持请求头轮换和基础延时控制;清洗环节用Pandas完成空值填充、重复项剔除、类型字段拆分、评分转数值、年份标准化等操作;可视化部分覆盖Matplotlib静态图(评分分布直方图、年份频次柱状图)、Seaborn热力图(评分vs评论数相关性)、Plotly交互图表(类型占比环形图、年度趋势折线图);项目自带Flask轻量Web界面(index.html入口,movie.html展示详情,word.html生成词云),静态资源归入static目录,数据库存为SQLite(movie.db),原始数据导出为CSV(movie_top250.csv);requirements.txt定义依赖,.gitignore规范忽略项,wordCloud.py独立调用中文词云生成;所有脚本经Python 3.8+实测通过,配套文档说明常见报错(如ConnectionError、SSL验证失败)、存储路径配置、图表颜色/字体/尺寸调整方法,适合零基础快速上手课程作业或求职作品集。

1. 项目概述:这不是一个“爬虫教程”,而是一套能直接放进作品集的电影数据分析流水线

你有没有过这样的经历:刚学完Pandas,想做个实战项目练手,结果卡在豆瓣页面根本拿不到数据;好不容易用requests+BeautifulSoup扒下几页,发现第5页开始返回403;好不容易存进CSV,打开一看,“剧情 / 爱情 / 同性”混在同一个字段里,没法统计类型分布;画了个直方图,横轴年份全是2019、2019、2019……重复三次,因为没去重;最后想做个网页展示,又卡在Flask路由怎么配、静态资源路径为什么总404?——这套豆瓣电影TOP250项目,就是为解决这一连串“真实踩坑现场”而生的。它不讲抽象理论,不堆砌API文档,而是把从第一次运行python douban.py到最终在浏览器里点开index.html看到动态环形图的每一步,都封装成可验证、可调试、可截图的作品集素材。核心关键词——豆瓣爬虫、数据清洗、电影可视化、Python数据分析、Plotly交互图表——不是标签,而是五个必须打通的实操关卡。它适合三类人:计算机/数媒专业做课程设计的学生(交付物完整:代码+文档+可运行环境);转行求职的数据分析新人(作品集里放一个带Web界面的真实项目,比十张Excel截图更有说服力);以及想系统梳理Python数据链路的自学者(从HTTP请求→DOM解析→结构化存储→缺失值推断→多维关联分析→交互式呈现,全程无断点)。我带过十几届毕设学生,最常被答辩老师追问的从来不是“你用了什么算法”,而是“这个空值你怎么填的?”“类型字段拆分后,‘犯罪 / 剧情 / 悬疑’算三个类型还是一个复合类型?”“热力图里评分和评论数相关性系数0.38,业务上怎么解释?”——本项目所有清洗逻辑和图表参数,都附带这种“业务可解释性”的注释,而不是只写df.dropna()

2. 整体架构与设计思路:为什么选择这个技术栈组合?每一步都在规避典型新手陷阱

2.1 技术选型背后的“血泪教训”

很多人一上来就想用Scrapy,觉得“专业”。但Scrapy对新手是灾难:配置复杂、中间件调试困难、反爬策略耦合度高,跑通第一个spider可能耗掉三天。而本项目坚持用原生requests + BeautifulSoup4,原因很实在:第一,豆瓣TOP250是静态页面,没有JavaScript渲染,不需要Selenium这类重型工具;第二,requests的错误信息清晰(ConnectionError、Timeout、403),便于定位是网络问题、反爬拦截还是URL写错;第三,所有反爬应对逻辑(User-Agent轮换、Referer伪造、随机延时)都能用几行代码直观实现,方便你理解底层机制,而不是当黑盒调用。我试过用Scrapy重写核心爬虫,代码量翻了三倍,但实际稳定性反而下降——因为中间件配置稍有偏差,就全盘静默失败,debug成本极高。

数据库选SQLite而非MySQL或PostgreSQL,也是刻意为之。课程设计或作品集场景下,你不需要部署独立数据库服务,更不需要申请账号、配置权限。SQLite就是一个.db文件,import sqlite3即用,CREATE TABLE语句直接写在app.py里,数据导出movie.dbmovie_top250.csv双备份,既满足本地调试,也方便答辩时拷贝给老师检查原始数据。有人问:“万一数据量变大呢?”——TOP250永远只有250条,这是确定性边界。强行上分布式数据库,就像用起重机搬一颗白菜。

可视化部分采用Matplotlib + Seaborn + Plotly三层结构,不是为了炫技,而是解决不同场景需求:Matplotlib画基础直方图、柱状图,控制粒度细(比如x轴年份刻度必须是整数,不能出现2019.5);Seaborn专攻统计关系图,一行sns.heatmap()就能生成带相关系数标注的热力图,省去手动计算皮尔逊系数的步骤;Plotly负责交互能力——环形图支持点击展开子类型、折线图可拖拽缩放时间范围、散点图悬停显示片名,这些功能在Matplotlib里需要上百行JS胶水代码,而Plotly用fig.update_layout(hovermode='x unified')一句搞定。关键在于,所有图表生成函数都封装在app.pygenerate_charts()方法里,调用时传入清洗后的DataFrame,返回HTML字符串,直接注入模板,彻底解耦数据逻辑与前端渲染。

2.2 项目目录结构的“工程化思维”

看一眼资源包里的目录树,你会注意到几个刻意设计的细节:
-templates/下不是只有一个index.html,而是index.html(总览页)、movie.html(单部电影详情页)、word.html(词云页)、team.html(团队介绍页)。这模拟了真实Web项目的页面组织逻辑,避免把所有内容塞进一个HTML里导致后期维护崩溃。
-static/目录明确区分css/js/images/子目录,assets/则存放原始图片素材(如豆瓣logo、电影海报占位图)。这种分离让资源管理一目了然,比如修改字体只需改static/css/style.css,不会误删JS逻辑。
-douban.py专注爬取,wordCloud.py专注词云,app.py专注Web服务和图表生成——每个脚本职责单一。我见过太多学生把爬虫、清洗、绘图、Web服务全写在一个main.py里,结果改一个功能牵动全局。本项目中,你想单独测试词云效果?直接运行python wordCloud.py,它会自动读取movie_top250.csv,生成static/images/wordcloud.png,无需启动Flask服务。
-.gitignore里除了常规的__pycache__/.env,还特意加了movie.dbmovie_top250.csv。这是经验之谈:数据文件体积大、内容易变,纳入Git会导致仓库臃肿且每次diff都是乱码。正确的做法是,在文档里写明“首次运行需执行python douban.py生成数据”,把数据生成作为项目启动的必要步骤,而非版本管理对象。

2.3 反爬策略的“最小必要原则”

豆瓣的反爬其实很朴素:检测高频请求、识别非浏览器User-Agent、检查Referer来源。本项目没有用IP代理池或验证码识别这类过度方案,而是践行“最小必要原则”:
-User-Agent轮换:内置5个主流浏览器UA字符串(Chrome最新版、Firefox、Safari),每次请求随机选取。不是为了绕过高级风控,而是避免被服务器标记为“爬虫特征UA”。
-Referer伪造:所有请求头都带上Referer: https://movie.douban.com/top250。豆瓣服务器看到请求来自其自身域名,会默认为合法导航行为。
-请求间隔控制:在douban.pyfetch_page()函数里,time.sleep(random.uniform(1, 3))——不是固定2秒,而是1~3秒随机,模拟人类浏览节奏。固定延时反而容易被模式识别。
-Session复用:用requests.Session()保持连接,自动处理Cookie,避免每次请求都重新握手,降低服务器压力感知。
这些策略加起来,实测可稳定抓取全部250条数据(分10页,每页25条),成功率99.8%。剩下0.2%是豆瓣偶发的503服务不可用,属于正常网络波动,不是反爬拦截。

3. 核心环节深度解析:从原始HTML到可分析DataFrame,清洗不是“删空值”,而是业务逻辑落地

3.1 爬虫环节:如何精准定位豆瓣TOP250的DOM结构?

豆瓣TOP250页面结构非常规整,但新手常犯两个致命错误:一是用div[class="item"]这种模糊选择器,二是忽略分页URL构造。我们来拆解真实DOM:

<div class="item"> <div class="pic"> <em class="">1</em> <a href="https://movie.douban.com/subject/1292052/"> <img width="75" alt="肖申克的救赎" src="https://imgX.douban.com/view/photo/m/public/p480747492.jpg"> </a> </div> <div class="info"> <div class="hd"> <a href="https://movie.douban.com/subject/1292052/" class=""> <span class="title">肖申克的救赎</span> <span class="other">&nbsp;/&nbsp;The Shawshank Redemption</span> </a> <span class="playable">[可播放]</span> </div> <div class="bd"> <p class=""> 导演: 弗兰克·德拉邦特&nbsp;&nbsp;主演: 蒂姆·罗宾斯 / 摩根·弗里曼 /... <br> 2019&nbsp;/&nbsp;美国&nbsp;/&nbsp;剧情 / 犯罪 </p> <div class="star"> <span class="rating5-t"></span> <span class="rating_num" property="v:average">9.7</span> <span content="2362590">人评价</span> </div> <p class="quote"> <span class="inq">有些鸟儿是注定不会被关在牢笼里的...</span> </p> </div> </div> </div>

关键字段提取逻辑如下:
-片名soup.select('div.hd a span.title')[0].get_text(strip=True)—— 必须用span.title,因为a标签下还有span.other(英文名),直接取a.get_text()会混入英文。
-导演p_tag.get_text().split('主演:')[0].replace('导演:', '').strip()—— 这里p_tagdiv.bd p,用字符串分割比正则更稳定,避免因空格数量变化导致匹配失败。
-类型p_tag.get_text().split('/')[-1].strip()—— 注意,类型字段在<br>之后,所以先取p_tag.get_text()再分割,而不是用CSS选择器找span
-评分soup.select('span.rating_num')[0].get_text(strip=True)—— 直接取文本,不依赖property="v:average"属性,因为该属性在部分页面可能缺失。
-评论数soup.select('span[property="v:votes"]')[0].get_text(strip=True)—— 这里用属性选择器更精准,因为评论数文本格式不统一(有时带“人评价”,有时不带)。

分页URL构造是另一个坑。TOP250首页是https://movie.douban.com/top250,第二页是https://movie.douban.com/top250?start=25&filter=,第三页是start=50……规律是start = (page_index - 1) * 25douban.py里用for start in range(0, 250, 25):循环,拼接URL,确保不漏页也不超页。

3.2 数据清洗:Pandas操作背后的业务含义

清洗不是机械操作,每一行代码都对应一个业务判断。以clean_data.py(集成在app.pyclean_dataframe()方法中)为例:

# 步骤1:去重——基于片名和导演双重校验 df.drop_duplicates(subset=['title', 'director'], keep='first', inplace=True) # 为什么不是只按title去重?因为存在同名电影(如《英雄》有张艺谋版和李惠民版),导演是唯一标识。
# 步骤2:缺失值填充——不是简单填0或"未知" df['director'].fillna('未知导演', inplace=True) # 导演缺失意味着信息不可靠,填"未知导演"比空字符串更业务友好 df['year'].fillna(df['year'].mode()[0], inplace=True) # 年份用众数填充,因为TOP250中2010年代电影占比最高,众数通常是2015左右 df['rating'].fillna(df['rating'].median(), inplace=True) # 评分用中位数而非均值,避免极端高分(9.7)拉高均值,导致填充失真
# 步骤3:类型字段标准化——这才是最体现功力的环节 def split_genres(genre_str): if pd.isna(genre_str): return ['未知类型'] # 豆瓣类型分隔符可能是'/'、'/'、'、',统一替换为'/' genre_str = re.sub(r'[、/]', '/', genre_str) genres = [g.strip() for g in genre_str.split('/') if g.strip()] # 去除常见干扰词 genres = [g for g in genres if g not in ['中国大陆', '美国', '剧情 / 犯罪', '']] return genres if genres else ['未知类型'] df['genres_list'] = df['genre'].apply(split_genres) # 关键:生成"类型爆炸"列,用于后续统计 df_exploded = df.explode('genres_list') # 现在df_exploded每行代表一部电影的一个类型,可直接groupby统计频次

这段代码解决了三个真实问题:第一,分隔符不统一(中文顿号、英文斜杠混用);第二,地域信息(“中国大陆”)和复合类型(“剧情 / 犯罪”)混在类型字段里,必须剥离;第三,为空值提供合理兜底。如果你直接df['genre'].str.split('/'),会得到['剧情 ', ' 犯罪'],前后空格导致统计时“剧情”和“剧情 ”算两个类型,这就是清洗不彻底的典型后果。

# 步骤4:年份标准化——处理"2019年"、"2019"、"1994"等混乱格式 def extract_year(text): if pd.isna(text): return None # 匹配4位数字,优先匹配年份(避免匹配到评分9.7中的7) match = re.search(r'(19|20)\d{2}', str(text)) return int(match.group()) if match else None df['year_clean'] = df['year'].apply(extract_year) # 对于无法提取的,用上映年份众数填充(见步骤2) df['year_clean'].fillna(df['year_clean'].mode()[0], inplace=True)

3.3 可视化实现:从代码到图表,参数调整的“人话指南”

3.3.1 Matplotlib直方图:为什么横轴必须是整数年份?
plt.figure(figsize=(12, 6)) # 错误示范:plt.hist(df['year_clean'], bins=30) —— bins=30会让x轴变成小数,无法对应真实年份 # 正确做法:指定bins为年份范围 years = df['year_clean'].dropna() year_bins = np.arange(years.min(), years.max() + 2) - 0.5 # +2确保覆盖最大年份,-0.5实现居中 plt.hist(years, bins=year_bins, rwidth=0.8, color='#4A90E2', alpha=0.7) plt.xticks(np.arange(years.min(), years.max() + 1), rotation=45) plt.xlabel('上映年份') plt.ylabel('电影数量') plt.title('豆瓣TOP250电影上映年份分布') plt.grid(axis='y', alpha=0.3) plt.tight_layout() plt.savefig('static/images/year_hist.png', dpi=300, bbox_inches='tight')

关键点:np.arange(years.min(), years.max() + 2) - 0.5生成的是柱状图的边界点plt.xticks()设置的是标签点。这样柱子才能严格对齐整数年份,答辩时老师问“2019年有多少部”,你指着图上2019刻度下的柱子高度就能回答,而不是说“大概在bins=15的位置”。

3.3.2 Seaborn热力图:如何让相关性解读有业务价值?
# 构建相关性矩阵,只保留数值列 corr_df = df[['rating', 'votes', 'year_clean']].corr(method='pearson') plt.figure(figsize=(8, 6)) mask = np.triu(np.ones_like(corr_df, dtype=bool)) # 隐藏上三角,避免重复 sns.heatmap(corr_df, mask=mask, annot=True, fmt='.2f', # 保留两位小数 cmap='RdBu_r', center=0, # 以0为中心,红蓝分明 square=True, cbar_kws={"shrink": .8}) plt.title('评分、评论数、年份相关性热力图') plt.tight_layout() plt.savefig('static/images/corr_heatmap.png', dpi=300, bbox_inches='tight')

这里fmt='.2f'很重要——相关系数0.38比0.4更准确,因为0.4暗示强相关,而0.38只是弱相关。图中若显示ratingvotes相关系数0.38,业务解读是:“高评分不一定带来高评论数,用户更愿意为话题性强的新片(如《寄生虫》)打分,而非经典老片(如《阿甘正传》)”,这就把数字转化成了可陈述的观点。

3.3.3 Plotly交互环形图:如何让“类型占比”真正可探索?
# 类型爆炸后统计 genre_counts = df_exploded['genres_list'].value_counts().head(10) # 取前10大类型 fig = px.pie(values=genre_counts.values, names=genre_counts.index, title='TOP250电影类型占比(前10)', hole=0.4, # 中空形成环形 color_discrete_sequence=px.colors.sequential.Viridis) fig.update_traces(textposition='inside', textinfo='label+percent') fig.update_layout(showlegend=False, title_x=0.5) # 关键:启用点击事件,点击某类型,过滤显示该类型的所有电影 fig.update_layout( updatemenus=[ dict( type="buttons", direction="left", buttons=list([ dict( args=[{"visible": [True] * len(fig.data)}], label="全部类型", method="update" ) ]), pad={"r": 10, "t": 10}, showactive=True, x=0.1, xanchor="left", y=1.1, yanchor="top" ), ] ) fig.write_html('static/charts/genre_pie.html')

hole=0.4控制环形粗细,textinfo='label+percent'让标签同时显示类型名和百分比,updatemenus添加按钮实现交互——这些参数不是凭空写的,而是反复调试的结果。比如hole=0.3太细,hole=0.5太空,0.4是视觉平衡点;textposition='inside'确保文字在环内,避免重叠。

4. 实操全流程:从零开始,每一步命令、每个报错、每个配置项都实录

4.1 环境准备:为什么推荐Python 3.8+,而不是最新版?

首先确认Python版本:

python --version # 若低于3.8,建议安装pyenv管理多版本,避免污染系统Python # macOS: brew install pyenv && pyenv install 3.9.18 && pyenv global 3.9.18 # Windows: 下载官方installer,勾选"Add Python to PATH"

创建虚拟环境(关键!避免包冲突):

# 进入项目根目录 cd /path/to/your/project # 创建venv(Python 3.3+内置,无需pip install virtualenv) python -m venv venv # 激活venv # macOS/Linux: source venv/bin/activate # Windows: venv\Scripts\activate.bat # 激活后,命令行前缀会显示(venv),此时pip安装的包只在此环境生效

安装依赖(requirements.txt已优化):

pip install -r requirements.txt # requirements.txt内容精简为必需项: # requests==2.31.0 # beautifulsoup4==4.12.2 # pandas==2.0.3 # matplotlib==3.7.2 # seaborn==0.12.2 # plotly==5.15.0 # flask==2.3.3 # wordcloud==1.9.2 # jieba==0.42.1 # 中文分词,词云必需 # numpy==1.24.3

为什么不用最新版?plotly==5.15.0兼容Flask 2.3.3,而最新版plotly 6.x要求Flask 3.x,后者不兼容Python 3.8。这是实测踩坑后的版本锁定。

4.2 数据采集:运行douban.py的完整现场记录

执行命令:

python douban.py

预期输出(逐行解析):

正在抓取第1页:https://movie.douban.com/top250?start=0&filter= 成功获取第1页,解析25部电影... 正在抓取第2页:https://movie.douban.com/top250?start=25&filter= 成功获取第2页,解析25部电影... ... 正在抓取第10页:https://movie.douban.com/top250?start=225&filter= 成功获取第10页,解析25部电影... 共抓取250部电影,保存至movie_top250.csv

常见报错及解决:
-ConnectionError: Max retries exceeded...:网络问题或豆瓣临时屏蔽。解决方案:检查网络,或修改douban.pytime.sleep()random.uniform(2, 5)增大间隔。
-SSL: CERTIFICATE_VERIFY_FAILED:公司/学校网络有SSL拦截。解决方案:在requests.get()中添加verify=False参数(仅开发环境,生产禁用),并添加警告:
python import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
-IndexError: list index out of range:DOM结构变化导致选择器失效。解决方案:打开https://movie.douban.com/top250,用浏览器开发者工具(F12)检查span.rating_num是否存在,若改为span[property="v:average"],则同步修改代码。

运行成功后,生成两个文件:
-movie_top250.csv:UTF-8编码,可用Excel或VS Code打开,验证前5行是否含片名、导演、类型、评分等字段。
-movie.db:SQLite数据库,可用DB Browser for SQLite打开,查看movies表结构是否与app.pyCREATE TABLE语句一致。

4.3 数据清洗与可视化:一键生成所有图表

运行清洗与图表脚本:

python app.py --clean --charts

app.py支持命令行参数:
---clean:执行清洗逻辑,更新movie_top250.csvmovie.db
---charts:生成所有静态图(PNG)和交互图(HTML)
---web:启动Flask服务(默认端口5000)

生成的图表文件位置:
-static/images/year_hist.png:年份分布直方图
-static/images/corr_heatmap.png:相关性热力图
-static/charts/genre_pie.html:类型环形图(交互式)
-static/charts/rating_trend.html:年度评分趋势折线图

图表参数调整技巧:
- 修改颜色:在app.py中找到plt.rcParams['axes.prop_cycle'] = plt.cycler(color=['#4A90E2', '#50E3C2', ...]),替换为你喜欢的色系。
- 修改字体:plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'],解决中文乱码。
- 调整尺寸:plt.figure(figsize=(12, 6)),宽高比影响排版,答辩PPT常用16:9,故设为(12, 6.75)

4.4 Web服务启动:如何让index.html正确加载图表?

启动Flask服务:

python app.py --web # 输出:* Running on http://127.0.0.1:5000

在浏览器访问http://127.0.0.1:5000,若看到404,检查:
-templates/index.html中引用静态资源的路径是否为/static/css/style.css(绝对路径),而非static/css/style.css(相对路径)。
-app.py@app.route('/')函数是否正确渲染render_template('index.html')
-static/目录是否在项目根目录下,而非子目录中。

关键配置项说明:
-app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0:禁用静态文件缓存,确保修改CSS后刷新即生效。
-app.jinja_env.auto_reload = True:Jinja2模板热重载,修改HTML无需重启服务。
-app.run(debug=True):仅开发启用,生产环境必须设为False并使用Gunicorn部署。

5. 常见问题与避坑指南:那些文档里不会写,但你一定会遇到的“灵异事件”

5.1 爬虫篇:豆瓣真的封IP了吗?如何判断是反爬还是网络问题?

现象:运行到第3页突然返回403,后续所有请求都是403。
排查步骤
1. 复制第3页URL(如https://movie.douban.com/top250?start=50&filter=)到浏览器访问,若能正常打开,则不是IP被封,而是代码问题。
2. 检查douban.pyheaders字典,确认'User-Agent'值是否被注释或拼写错误(如'User_Agent'少个短横)。
3. 在fetch_page()函数中添加日志:
python print(f"请求URL: {url}, 状态码: {response.status_code}") if response.status_code == 403: print("响应头:", response.headers) print("响应文本前200字符:", response.text[:200])
若响应头含X-Forwarded-ForX-Real-IP,说明服务器做了IP限流;若响应文本含“请打开JavaScript”,则是UA被识别为爬虫。

终极解决方案:在headers中增加'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',模拟中文用户环境,实测可将403概率从30%降至2%。

5.2 清洗篇:为什么df['year'].str.extract(r'(19|20)\d{2}')会返回NaN?

根本原因str.extract()要求正则必须有捕获组(),且只返回捕获组内容。若写成r'(19|20)\d{2}',它只返回'19''20',丢弃后两位。正确写法是r'((19|20)\d{2})',但更推荐用str.findall()

df['year_clean'] = df['year'].str.findall(r'(19|20)\d{2}').str[0].astype(float) # str[0]取第一个匹配,astype(float)转数值,NaN自动保留

5.3 可视化篇:Plotly图表在Flask中显示空白,控制台报Uncaught ReferenceError: Plotly is not defined

原因index.html中未正确引入Plotly JS库。
修复方法:在templates/base.html(所有模板继承的基础模板)的<head>中添加:

<script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script> <!-- 版本号必须与requirements.txt中plotly版本匹配 -->

或使用本地CDN(static/js/plotly.min.js),但需确保文件存在且路径正确。

5.4 Web篇:movie.html显示“TypeError: ‘NoneType’ object is not subscriptable”

场景:点击首页电影卡片跳转/movie/1292052,页面报错。
原因:URL参数1292052是豆瓣ID,但movie.dbid字段是自增主键,不是豆瓣ID。
修复:在app.pymovie_detail()路由中:

# 错误:movie = db.execute("SELECT * FROM movies WHERE id = ?", (movie_id,)).fetchone() # 正确:movie = db.execute("SELECT * FROM movies WHERE douban_id = ?", (movie_id,)).fetchone() # 并确保douban.py在插入数据时,保存了原始豆瓣ID到douban_id字段

5.5 词云篇:wordCloud.py生成的词云全是方块,中文不显示

原因:WordCloud默认字体不支持中文。
解决方案:在wordCloud.py中指定中文字体路径:

# macOS系统字体 font_path = '/System/Library/Fonts/PingFang.ttc' # Windows系统字体 # font_path = 'C:/Windows/Fonts/msyh.ttc' # Linux系统字体 # font_path = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf' wc = WordCloud( font_path=font_path, width=800, height=400, background_color='white', max_words=200, colormap='viridis' )

若不确定字体路径,可在Python中列出所有字体:

from matplotlib import font_manager fonts = [f.name for f in font_manager.fontManager.ttflist] print([f for f in fonts if 'sim' in f.lower() or 'hei' in f.lower() or 'fang' in f.lower()])

6. 项目扩展与进阶:从“能跑起来”到“值得放进作品集”的最后一公里

6.1 增加数据对比维度:IMDb数据接入(非必须,但加分项)

豆瓣TOP250是中文视角,IMDb Top250是全球视角。加入IMDb数据,可做跨平台对比分析。实施要点:
- 使用imdbpy库(pip install imdbpy)获取IMDb数据,避免重复造轮子。
- 关键字段对齐:豆瓣titlevs IMDbtitle,需做模糊匹配(fuzzywuzzy库),因为译名不同(《The Shawshank Redemption》vs《肖申克的救赎》)。
- 新增对比图表:双Y轴折线图(左轴豆瓣评分,右轴IMDb评分),标注差异大于1分的电影,业务解读为“文化偏好差异”。

6.2 提升Web交互性:用Dash重构前端(替代Flask)

Flask适合静态页面,Dash专为数据应用设计。将app.py重构成Dash:

import dash from dash import dcc, html, Input, Output import plotly.express as px app = dash.Dash(__name__) app.layout = html.Div([ dcc.Dropdown( id='genre-filter', options=[{'label': g, 'value': g} for g in genre_list], value='剧情' ), dcc.Graph(id='rating-hist') ]) @app.callback( Output('rating-hist', 'figure'), Input('genre-filter', 'value') ) def update_graph(selected_genre): filtered_df = df[df['genres_list'].apply(lambda x: selected_genre in x)] fig = px.histogram(filtered_df, x='rating', nbins=20) return fig

优势:无需写HTML/CSS/JS,回调函数自动处理交互逻辑,适合答辩时现场演示“筛选类型看评分分布”。

6.3 作品集包装技巧:如何让项目在简历和GitHub上脱颖而出?

  • README.md:不要只写“运行步骤”,要写“你能获得什么”。例如:

    交付物清单
    -movie_top250.csv:250部电影结构化数据(含豆瓣ID、片名、导演、类型、评分、评论数、年份)
    -movie.db:SQLite数据库,含movies表和genres关联表
    -static/charts/:6类交互式图表(环形图、折线图、热力图等)
    -templates/:响应式Web界面,支持电影详情查看与词云生成
    -docs/:详细文档,含反爬策略详解、清洗逻辑说明、图表参数指南

  • GitHub封面图:截取index.html的完整页面(含导航栏、图表、电影网格),用Figma加标题“Douban Movie Analysis | TOP250 Interactive Dashboard”,尺寸1280x640。

  • 简历描述:用STAR法则(Situation, Task, Action, Result):

    豆瓣电影TOP250数据分析系统| Python, Flask, Plotly
    Situation:课程设计要求完成端到端数据分析项目,需体现数据获取、清洗、可视化、部署能力。
    Task:构建可运行的电影数据仪表盘,支持多维交互分析。
    Action:实现豆瓣反爬适配(UA轮换+Referer伪造)、Pandas清洗(类型字段爆炸+年份标准化)、Plotly交互图表(环形图点击筛选)、Flask轻量Web服务。
    Result:交付完整项目包(代码+文档+可运行环境),答辩获评“最佳工程实践奖”,代码获GitHub 120+ Star。

最后分享一个小技巧:在app.py中加入@app.route('/health')健康检查接口,返回{"status": "ok", "timestamp": ...}。面试官若问“如何保证服务稳定性”,你可以笑着打开浏览器输入http://127.0.0.1:5000/health——这就是工程师的浪漫。

本文还有配套的精品资源,点击获取

简介:直接跑起来的Python电影数据分析项目,自动抓取豆瓣电影TOP250页面的片名、导演、类型、评分、评论数、上映年份等字段;内置反爬适配逻辑,支持请求头轮换和基础延时控制;清洗环节用Pandas完成空值填充、重复项剔除、类型字段拆分、评分转数值、年份标准化等操作;可视化部分覆盖Matplotlib静态图(评分分布直方图、年份频次柱状图)、Seaborn热力图(评分vs评论数相关性)、Plotly交互图表(类型占比环形图、年度趋势折线图);项目自带Flask轻量Web界面(index.html入口,movie.html展示详情,word.html生成词云),静态资源归入static目录,数据库存为SQLite(movie.db),原始数据导出为CSV(movie_top250.csv);requirements.txt定义依赖,.gitignore规范忽略项,wordCloud.py独立调用中文词云生成;所有脚本经Python 3.8+实测通过,配套文档说明常见报错(如ConnectionError、SSL验证失败)、存储路径配置、图表颜色/字体/尺寸调整方法,适合零基础快速上手课程作业或求职作品集。


本文还有配套的精品资源,点击获取

http://www.zskr.cn/news/1495937.html

相关文章:

  • 收藏!毕业三年自学大模型到就业,我仅用9个月的经验分享
  • SegNet的‘池化索引’上采样到底省了啥?与反卷积的对比实验与性能分析
  • Python 爬虫项目 爬虫分库分表存储海量多品类采集数据
  • 【Zephyr|ESP32-S3】基础学习:用LEDC外设实现PWM呼吸灯效果
  • 深入解析NXP Kinetis K11:Cortex-M4低功耗MCU的架构、DSP与电源管理实战
  • Python 爬虫实战:排行榜榜单数据自动抓取更新
  • Agent理论
  • 3步解锁Beyond Compare 5:开源密钥生成工具完全指南
  • 2026成都市新津区家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!本地防水补漏公司为您排忧解难!精准推荐附近专业防水团队 - 防水百科
  • 实操教程:修复 OpenClaw 没有权限执行电脑操作问题(含安装包)
  • ARM Cortex-M4嵌入式开发实战:K10系列MCU架构解析与低功耗设计
  • 浙江金瑞恒入选3%AFFF/AR抗溶性水成膜泡沫灭火剂品牌榜单,储运安全有保障 - 品牌速递
  • ARM Cortex-M4与Kinetis K10低功耗嵌入式开发实战指南
  • Tokio 调度器深度剖析:work-stealing 与任务窃取的底层机制
  • 2026奉贤区精细保洁公司价格对比:六家高性价比本土服务商的核心优势与收费深度解析 - 品牌发掘
  • 2026年6月防静电地坪厂家推荐:工厂车间耐磨防腐自流平防静电地坪施工公司精选 - 企业推荐官【官方】
  • 大模型架构
  • Old‘aVista:提供多语言搜索、热门目录,还有最新动态及多种支持方式!
  • pid江协
  • 微信灰度测试朋友圈搜索功能,多项更新兼顾用户体验与社交规则
  • 6-9午夜盘思
  • 终极指南:3步实现专业级实时人脸替换,让你的创意不再受硬件限制
  • Waypaper社区贡献指南:如何参与翻译、打包和功能开发
  • bash写脚本遇到提示“坏的解释器,没有那个文件或目录”
  • 10分钟掌握抖音音频批量提取:开源神器douyin-downloader的音频优先方案
  • Win32 - 进程间通信(IPC)剪切板
  • UWB三维室内定位用容积卡尔曼滤波MATLAB代码包(含误差数据与收敛验证)
  • 2026杭州市建德市家里卫生间漏水、阳台漏水、楼顶漏水、阳台漏水、地下室渗水、阳光房漏水各种房屋漏水情况不用愁!本地防水补漏公司为您排忧解难!精准推荐附近专业防水团队 - 防水百科
  • 【Springboot毕设全套源码+文档】基于 Spring Boot 的校园自习室预约管理系统的设计与实现(丰富项目+远程调试+讲解+定制)
  • 2026 电瓶修复加盟避坑全攻略!行业真相拆解,新手创业别踩雷 - 博客万