首页 / 资讯中心 / 文章详情

Python豆瓣电影数据分析与可视化:从爬虫到课程报告全流程实战

Python豆瓣电影数据分析与可视化:从爬虫到课程报告全流程实战 ★ FEATURED ARTICLE
直接开工。这个项目我当年也是踩了不少坑才跑通的如果你正准备拿“基于Python的豆瓣电影数据分析与可视化系统”当课程大作业或者毕设那这篇正好能帮你省下至少两周的摸索时间。项目本身不算复杂从豆瓣抓取电影数据用pandas做清洗和统计再用可视化把规律呈现出来最后整理成一份能讲清楚“为什么这么做”的课程报告。难点不在于代码而在于整个流程怎么串联得完整、自洽以及遇到反爬、编码、图表中文乱码这些问题时能不能快速反应过来。我下面会把整个项目从设计思路、数据集构建、分析指标、可视化实现到报告撰写全部拆开讲代码直接给可用版本很多细节是我实际跑过的经验不是网上抄来的理论。1. 项目整体设计与思路拆解1.1 为什么选豆瓣电影作为分析对象课程大作业最怕什么怕选题太虚分析完了自己都不知道在说什么也怕数据太难搞时间全耗在采集上。豆瓣电影是很好的折中选择数据量适中结构规整字段丰富而且电影评分本身就是一个大众熟悉、能讲出故事的题材。选豆瓣而不是其它平台还有几个实际考虑。第一豆瓣的评分、评价人数、导演、演员、类型、地区、年份这些字段天然适合做分组统计和相关性分析第二豆瓣电影Top250是固定榜单爬取逻辑简单、便于复现评委一看就知道你的数据来源可靠第三评分类数据能挖出“高分电影有什么共性”“哪个年代好片最多”“哪些类型容易出高分”这类有洞察力的结论比单纯统计一个网页的访问量有意思得多。我建议你的数据集不要只爬Top250至少再把某个类型分类下的电影扩大一下比如按“豆瓣高分”“热门”列表爬个几百部数据样本到500条以上分析结论才不会被极端值带偏。1.2 技术选型背后的理由整个技术栈很常规但每个环节都值得说明为什么这么选数据采集requests BeautifulSoup两个库足够解决99%的静态页面抓取。Selenium虽然也能用但速度慢、资源占用高大作业里没必要杀鸡用牛刀。数据处理pandasPython数据分析的事实标准DataFrame处理表格数据比纯列表操作不知道舒服到哪里去。基础可视化Matplotlib用于静态图表适合放到报告里Pyecharts生成交互式HTML图表适合做系统展示和可视化大屏。词云wordcloud分析电影类型或关键词时很出效果。存储既可以用CSV也可以用SQLite。大作业我更推荐CSV评审可以直接打开看但如果你想展示“完整系统”SQLite更专业两种我都写了方案。有人问要不要上Scrapy、Spark、Hadoop课程大作业没必要。用最朴素的工具把流程跑通把分析思路讲清楚比炫技得分高。评委看的是你的数据分析思维不是框架堆砌。1.3 系统模块划分一个完整系统应该分成四个模块数据采集模块抓取豆瓣电影页面的原始数据保存为结构化文件。数据清洗模块处理缺失值、去重、格式统一输出干净的数据集。数据分析模块计算评分分布、年份趋势、类型偏好等指标并输出统计结果。可视化模块把分析结果用合适的图表形式呈现可以打包成一个交互式报告页面。模块化设计不只是为了代码好看更重要的是方便自己调试。哪一步出了问题直接定位到对应模块就能排查不用从头跑一遍。2. 数据集构建与清洗细节2.1 字段设计与爬取策略爬取之前先把表结构定好不然爬完发现缺字段再补就麻烦了。我的数据集包含以下字段字段名含义示例title电影名称肖申克的救赎rating豆瓣评分9.7rating_count评价人数280万director导演弗兰克·德拉邦特year上映年份1994country制片国家/地区美国genre类型剧情/犯罪duration片长142分钟抓取策略上先请求豆瓣Top250页面每页25部共10页。URL构造很简单url https://movie.douban.com/top250?start{}filter.format(page * 25)这里start参数就是本页第一部的序号0、25、50以此类推。很多新手在这里写错爬了半天都是同一页数据检查URL参数是第一优先级。为了更充裕的样本我建议再爬一个“豆瓣电影分类排行榜”比如按“高分”分类这样可以再多拿几百部电影Dataset大小控制在500到800条比较舒服。2.2 爬虫关键代码与反爬应对豆瓣的反爬不算严但直接疯狂请求肯定会被封。我的策略是设置合理的User-Agent、单页请求间隔2到3秒、加一个简单的重试机制。完整爬虫核心代码import time import requests from bs4 import BeautifulSoup import pandas as pd 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 } def fetch_page(url, retries3): for i in range(retries): try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: return resp.text elif resp.status_code 403: print(被拦截等待10秒后重试, url) time.sleep(10) except requests.RequestException as e: print(请求异常, e) time.sleep(3) return None def parse_movie_list(html): soup BeautifulSoup(html, html.parser) items soup.select(.grid_view .item) movies [] for item in items: title item.select_one(.title).text.strip() info item.select_one(.bd p).text.strip().split(\n) info [i.strip() for i in info if i.strip()] rating item.select_one(.rating_num).text.strip() rating_count item.select_one(.star .people_num).text if item.select_one(.star .people_num) else # info[0]一般是导演/主演info[1]一般是年份/地区/类型 movies.append({ title: title, rating: float(rating), rating_count: rating_count, detail: info, }) return movies实际爬的时候犯过一个低级错误rating_count选择器写错导致评价人数全是空值。解决方式是打开浏览器开发者工具直接复制元素路径然后跟.star下面的span标签匹配。另一个重点豆瓣页面返回的是UTF-8编码但requests有时候自动判断会出错。稳妥做法是在拿到response后手动指定编码resp.encoding utf-8不指定编码中文在后续处理时很可能出现乱码尤其是存进CSV后Excel打开一片乱码。2.3 数据清洗与存储原始数据直接拿去分析是不行的第一处就是rating_count、duration这类字段原始文本是“280.3万人评价”“142分钟”这种带单位的字符串没法参与计算。清洗逻辑如下评分人数剥掉非数字字符把“万”字换算成数值。片长提取数字转为int。年份从详情字段中用正则提取4位数字。地区、类型按“/”拆分需要展开的用explode()需要保留合集的不展开。去重用title year作为唯一键防止多页重复爬取。清洗代码示例import re import pandas as pd def clean_rating_count(text): if not text: return None text text.replace(人评价, ).strip() if 万 in text: return float(text.replace(万, )) * 10000 return float(text) def extract_year(text): m re.search(r(\d{4}), text) return int(m.group(1)) if m else None def extract_duration(text): m re.search(r(\d)\s*分钟, text) return int(m.group(1)) if m else None df[rating_count] df[rating_count].apply(clean_rating_count) df[year] df[detail].apply(extract_year) df[duration] df[detail].apply(extract_duration) # 去重 df df.drop_duplicates(subset[title, year]) # 缺失值处理评分缺失的直接删掉不影响整体分析 df df.dropna(subset[rating, title]) # 保存 df.to_csv(douban_movies.csv, indexFalse, encodingutf-8-sig)注意保存CSV时编码一定用utf-8-sig而不是utf-8否则用Excel打开CSV文件时中文会变成乱码。这个是很多人都会踩的坑报告里截图也要避免出现乱码不然印象分直接扣。2.4 用SQLite存储的进阶方案如果你想体现“系统”的完整度可以进一步把数据存入SQLiteimport sqlite3 conn sqlite3.connect(douban_movies.db) df.to_sql(movies, conn, if_existsreplace, indexFalse) conn.close()之后分析的时候直接查库df pd.read_sql_query(SELECT * FROM movies WHERE rating 8.0, conn)SQLite方案的优势在于数据量更大时不至于一个CSV文件几十MB打不开而且报告里能写“系统支持数据的增量更新与入库管理”听起来专业不少。3. 核心分析维度与统计方法3.1 从数据中能挖出什么结论数据清洗干净之后就要回答几个具体的业务问题了。我当时把分析目标收敛成四个方向评分分布特征高分电影集中在哪个分数段有没有明显的“长尾”年份维度不同年代电影评分的均值和中位数如何变化高品质电影是否在特定年代扎堆地区维度美国、中国、日本、欧洲等主要地区的高分电影占比有什么差异类型维度剧情、犯罪、动画、爱情这些类型谁的平均分更高冷门类型是不是更容易出高分每个分析维度都要有对应的统计指标不能只画一张图就说结论。比如平均值之外还要看中位数、标准差、Top10名单这样结论才扎实。3.2 关键统计与代码实现评分分布的代码import pandas as pd import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 解决中文乱码 plt.rcParams[axes.unicode_minus] False # 解决负号显示 df pd.read_csv(douban_movies.csv) rating_desc df[rating].describe() print(rating_desc) # 输出评分最高的10部电影 top10 df.nlargest(10, rating)[[title, rating, director, year]] print(top10)describe()输出的mean、std、min、max直接放到报告表格里这就是你数据分析的“硬成果”。分类型算均值# 用一个类型一行数据展开后统计 df_expanded df.assign(genredf[genre].str.split(/)).explode(genre) df_expanded df_expanded.dropna(subset[genre]) genre_stats df_expanded.groupby(genre)[rating].agg([mean, count, median]) genre_stats genre_stats.sort_values(mean, ascendingFalse) print(genre_stats.head(15))这里explode会把“剧情/犯罪”这样的复合类型拆成两行做“一部电影可能属于多个类型”的分析时这是标准做法。年份趋势分析year_stats df.groupby(year)[rating].agg([mean, count]) year_stats year_stats[year_stats[count] 5] # 样本太少不参与趋势分析加count 5这个过滤条件很重要否则上世纪80年代只有一两部片子均值忽高忽低画出来的折线图完全没有趋势意义。3.3 相关性分析评价人数与评分还有一个值得做、报告里也很出彩的分析评价人数和评分之间是否存在相关性。corr df[[rating, rating_count]].corr() print(corr)当时我跑出来相关系数大约在-0.4到0之间说明两者没有明显的正向线性关系。这个结论可以用来讨论口碑好的电影不一定评价人数多评价人数受电影宣传、大众关注度的影响更大。这种“反常识”的结论在答辩时很加分因为说明你真的在做分析思考而不是机械地套模板。4. 可视化设计与前端展示4.1 图表选型原则可视化不是随便选个图表就完事。不同问题用不同图评分分布连续数值直方图 核密度曲线。年份趋势时间序列折线图。地区/类型占比类别数据横向条形图或饼图。电影名称高频词词云特别是把经典电影名做词频视觉效果拉满。综合展示用Pyecharts做交互式仪表盘鼠标悬停能看详情比静态图有档次。我当时做了一个HTML报告页面左侧是导航右侧分块展示图表基本就是一个小型可视化大屏的简化版。效果比一次性跑出二十张静态图好很多复制到Word里也能当“系统界面截图”用。4.2 Matplotlib静态图代码先给出报告里最常用的三张图。评分分布直方图import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False plt.figure(figsize(10, 6)) plt.hist(df[rating], bins20, edgecolorblack, alpha0.7) plt.xlabel(评分) plt.ylabel(电影数量) plt.title(豆瓣电影评分分布) plt.grid(alpha0.3) plt.tight_layout() plt.savefig(rating_dist.png, dpi150) plt.show()年份平均分趋势图plt.figure(figsize(12, 6)) plt.plot(year_stats.index, year_stats[mean], markero, linestyle-, linewidth1.5) plt.xlabel(年份) plt.ylabel(平均评分) plt.title(各年份电影平均分趋势) plt.grid(alpha0.3) plt.tight_layout() plt.savefig(year_trend.png, dpi150) plt.show()如果你用的Matplotlib版本比较新保存图片时注意设置dpi150以上在Word里才不会模糊。4.3 Pyecharts交互式图表Pyecharts要先确认版本老版本和新版本接口差别很大。我在项目里用的Pyecharts 2.x写法from pyecharts.charts import Bar, Line, Pie, WordCloud from pyecharts import options as opts # 地区分布条形图 country_counts df[country].str.split(/).explode().value_counts().head(12) bar ( Bar() .add_xaxis(country_counts.index.tolist()) .add_yaxis(电影数量, country_counts.values.tolist()) .set_global_opts( title_optsopts.TitleOpts(title制片国家/地区分布), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30)), ) ) bar.render(country_bar.html)一个容易踩的坑Pyecharts渲染出来的HTML如果直接双击打开部分图表组件在Chrome下一切正常但在老旧浏览器或Word内置浏览器里会白屏。答辩播展示尽量用Chrome/Edge提前试好演示环境。词云图是全场效果最好的图表但需要另外处理文本。我当时把电影名列表用空格拼成字符串再用wordcloud生成from wordcloud import WordCloud text .join(df[title].tolist()) wc WordCloud( width1200, height800, background_colorwhite, font_pathC:/Windows/Fonts/simhei.ttf, # 必须要指定中文字体否则中文全是方块 max_words200, ).generate(text) wc.to_file(title_wordcloud.png)font_path一定要指向系统里的中文字体Windows下通常是simhei.ttf或msyh.ttc不然生成出来的词云全是豆腐块。Mac系统则是/System/Library/Fonts/PingFang.ttc。这个细节十个人里有八个人会踩坑。4.4 可视化大屏思路如果你想做得更亮眼可以做一个“数据看板”式页面。思路是这样的先用Pyecharts分别生成图表HTML再用一个主HTML文件通过iframe嵌入或者更简单一点直接在Jupyter Notebook里跑完所有代码块后导出成HTML。后者省事很多而且教程资源也多。我做的是用Flask把图表放到了一个简单网页里from flask import Flask, render_template from pyecharts import options as opts from pyecharts.charts import Bar import pandas as pd app Flask(__name__) app.route(/) def index(): df pd.read_csv(douban_movies.csv) country_counts df[country].str.split(/).explode().value_counts().head(10) bar ( Bar() .add_xaxis(country_counts.index.tolist()) .add_yaxis(电影数量, country_counts.values.tolist()) .set_global_opts(title_optsopts.TitleOpts(title热门制片地区)) ) return bar.render_embed() # 直接在页面内渲染省去跨域问题 if __name__ __main__: app.run(debugTrue)这里注意render_embed()比render()更适合嵌入Flask页面后者生成的是完整HTML文件再套一层Flask模板会出现多余的html嵌套。5. 常见问题与排查技巧实录5.1 高频问题速查表我把这个项目里大部分同学都会遇到的问题整理如下问题现象原因解决方案爬虫被拦截返回418或403请求头不全/频率过高换User-Agent加等待时间必要时用session保持cookies中文乱码标题出现“鍝堝”编码未指定resp.encodingutf-8CSV中文乱码Excel打开全乱文件保存为utf-8保存为utf-8-sig图表中文方块标题轴标签不显示中文Matplotlib默认英文字体设置rcParams[font.sans-serif][SimHei]词云中文方块词云全是矩形块wordcloud没有指定中文字体设置font_path为中文字体pyecharts页面空白浏览器打开HTML无内容低版本浏览器不支持 / js文件未加载用Chrome打开或用render_embed()嵌入groupby后均值NaN统计结果全NaN评分列是字符串确认pd.to_numeric()已转换评价人数列全是NaN爬取字段失败选择器不对或元素分析错误用浏览器devtools重新核对选择器程序运行很久爬取500条要几分钟sleep时间过长2秒即可别设10秒5.2 我实际踩过的坑第一个坑是爬虫爬到一半被限流。当时为了演示快速把间隔设成了0.5秒结果爬到第三页就被返回418页面。排查半天发现不是代码问题是请求频率太高触发了风控。最后把间隔提到3秒稳定跑完整个数据集。这里有个经验爬虫外加一个“完成进度打印”每页爬完打印当前数量不然卡住都不知道卡在哪。第二个坑是字段清洗时年份正则提取出的“1994”没问题但有一部分电影详情里年份和地区写的是“1994(中国大陆)”如果直接用int()会报错。后来改为先正则提取四位数再转int问题解决。这就是为什么清洗阶段一定要把逻辑写健壮一点不要假设数据永远是干净的。第三个坑比较隐蔽df[detail]本身是列表直接从列表里取年份和导演时索引越界。所以我只保留了detail原样再“提取”而不是“按位置取”这样即使某行信息不完整也不会直接崩溃。5.3 答辩时容易被问到的问题课程大作业通常需要答辩或者写报告有几个问题你最好提前准备你的数据量够吗500条只能说勉强够做描述性分析做统计推断远远不够别把话说满。为什么不用豆瓣官方API豆瓣官方API早就关闭了新用户申请而且政策限制多。爬虫虽然麻烦但数据可控。评分能反映电影质量吗这是个送分题你可以说评分是大众审美倾向的反映本身存在幸存者偏差因此分析维度才需要结合年份、类型一起看。如果数据量到了5万条你的系统还跑得动吗这个问题可以展示你的认识pandas处理5万条毫无压力但可视化渲染需要分批或聚合真有海量数据就得上数据库和分布式但那不是本项目的范畴。6. 课程大作业报告与源码交付6.1 报告结构怎么搭报告是大作业的核心交付物之一。评审老师看报告比看代码细得多结构不完整直接扣分。我推荐的结构项目背景与目标分析豆瓣电影评分数据发现高分电影的分布规律。数据采集与清洗说明数据来源、字段、清洗规则配爬虫和清洗代码截图。数据分析方法与结果按评分分布、年份趋势、地区、类型四个维度逐项分析每个维度配图表和结论。可视化系统实现展示系统页面截图说明技术栈和交互逻辑。项目总结与反思写清楚遇到的关键问题和解决方案再谦虚地提一下未来改进方向。报告的重点是“分析结果与结论”不是“代码展示”。每个图表下面都要写两到三句话的结论比如“评分主要集中于7.5到9.2之间说明榜单内电影整体品质较高并且高分区域存在明显的长尾。”这种图文结合的方式老师一眼就能看到你的分析能力。6.2 源码和数据集如何整理交作业前一定要整理目录别直接把一堆py文件丢上去。我最后的目录结构是这样douban-analysis/ ├── data/ │ ├── raw_movies.csv # 爬取原始数据 │ ├── clean_movies.csv # 清洗后数据 │ └── douban_movies.db # SQLite数据库 ├── code/ │ ├── 01_spider.py # 数据采集 │ ├── 02_clean.py # 数据清洗 │ ├── 03_analysis.py # 统计分析 │ └── 04_visualization.py # 可视化生成 ├── output/ │ ├── *.png # 静态图表 │ ├── *.html # 交互式图表 │ └── 可视化大屏.html └── report/ └── 课程大作业报告.docx这样整理的好处是评验收卷时一目了然也方便你自己在答辩时现场演示“一跑到底”。最后说一个实用的小建议代码里把主要函数都加上注释但不要啰嗦。基础注释会让老师觉得文件规范而关键参数和清洗逻辑旁边的一两句“为什么这么写”比大段注释更能体现思考深度。我自己参加课程答辩时老师对评分人数单位换算和类型拆分的两处注释印象很深提问都少了不少。这个项目看着简单但完整做下来你对数据采集、清洗、分析、可视化的全流程理解会一次性补齐后面再做别的数据分析项目流程基本都能复用。
阅读完成 · 觉得有帮助?
咨询建站