简介:豆瓣电影数据集10万.zip汇集了约10万条豆瓣电影相关记录,适合数据分析爱好者、电影行业研究者以及爬虫技术初学者。压缩包共4个文件,包含2个csv、1个SQLite数据库和1个Python爬虫程序,整体体积40.02MB。csv文件分别用于存放用户信息与电影基本信息:前者涵盖用户ID、评分历史等,可分析观影习惯与偏好;后者包含导演、主演、类型、评分等字段,为构建电影推荐系统或做市场热度研究提供基础;SQLite数据库则保存影评、点赞等交互数据,适合挖掘用户情感反应和社交互动。爬虫程序演示了使用requests、BeautifulSoup抓取并整理网页数据的方法。目前已有3442人学习,整个数据集覆盖从数据采集、清洗、存储到分析的关键环节,既能支持用户行为分析、情感挖掘,也能帮助理解爬虫与SQLite数据管理,是一份应用场景丰富、参考价值较高的电影数据集。
1. 豆猜电影数据集10万.zip:一份10万量级电影数据能承载哪些分析任务
一个压缩包,文件名写着“豆猜电影数据集10万”,解压后是几万到十万量级的电影记录。很多人第一反应是拿来做推荐系统练手,但实际打开后会发现,它能做的事远不止一个协同过滤:评分分布、类型偏好、导演与主演组合规律、年份趋势、文本简介的关键词聚类,都能在几百行代码内跑出可用结论。对刚接触数据分析的人来说,这是一份字段足够丰富、又不需要额外清洗太多内容的练习数据;对已经在做内容产品的人来说,它可以用来快速验证一个特征假设,比如“评价人数多的电影是否真的集中在某几个类型”。本文会从读取文件开始,把字段结构、清洗思路、典型分析落地和常见坑位一次讲完整。
2. 数据集内部结构:从压缩包到 DataFrame 的读取路径与字段预期
2.1 压缩包组织方式与字段约定
拿到 zip 文件,不要立刻解压双击打开。10 万量级的表格用 Excel 打开会卡,而且容易被它自作主张改变字段类型。我一般先用 zipfile 列一下内部目录,确认文件的组织方式:
import zipfile with zipfile.ZipFile('豆猜电影数据集10万.zip') as zf: for name in zf.namelist(): print(name)输出可能是一个 movies.csv,也可能是一个目录下挂着多张表。常见做法是主表放电影基本信息,附带的演员表或短评表单独存放;如果只有一张主表,字段通常会覆盖电影 id、中文名、导演、主演列表、类型、年份、地区、语言、时长、评分、评价人数和简介。列名可能是英文也可能是中文,实际读取时以文件的表头为准。
提示:先看 namelist 里的文件后缀。如果是 csv,直接用 pandas 打开;如果是 json 或 parquet,读取方式和字段类型预期完全不同。
2.2 用 pandas 读出数据:第一步先观察再下结论
读 csv 时我会直接把 zip 里的文件句柄传给 pandas,省一步解压动作,磁盘也干净:
import pandas as pd with zipfile.ZipFile('豆猜电影数据集10万.zip') as zf: csv_name = [n for n in zf.namelist() if n.endswith('.csv')][0] with zf.open(csv_name) as f: df = pd.read_csv(f, encoding='utf-8')读进来之后先做三个动作,不急着画图、不急着建模:
print(df.info()) # 每一列的非空数量与数据类型 print(df.sample(5).T) # 转置后随机看 5 行,直接确认中文是否乱码 print(df.nunique()) # 每列的取值数量,判断哪些是低基数字段df.info() 最有用的一点是能快速看出“列数对不对、行数是不是接近 10 万”。如果 read_csv 解析时因为逗号和引号问题把字段错位,行数往往会比预期少很多;如果编码不对,sample 输出的中文会变成乱码。nunique() 负责帮我们判断哪些字段适合做分组聚合,比如导演字段的取值数量通常在上万级别,而时长字段基本不可能用于枚举。
2.3 字段质量排查:空值分布与类型判断
初读数据后,第二步是精确找出哪些字段有空值、空值占比多少:
null_stats = df.isna().sum() null_stats = null_stats[null_stats > 0].sort_values(ascending=False) print(null_stats)这一步能直接影响后续清洗策略。以常见的电影记录为例,评分和评价人数往往有少量缺失,简介字段缺失比例可能更高,而片名、导演、年份这类关键字段如果缺失太多,说明这份数据集可能本身就不完整,需要决定是丢弃还是粗查。
类型判断方面,最需要警惕的是评分、年份、时长这三列。很多 CSV 从网页导出时会把它们存成文本,比如年份写成“1998年”或者“1998(大陆)”,评分写成“7.5分”或“暂无评分”。直接用 pd.to_numeric 转就会出现一整列 NaN。我习惯先统一转一遍,再把转不出来和正常数值分开统计:
df['rating'] = pd.to_numeric(df['rating'], errors='coerce') df['year'] = pd.to_numeric(df['year'], errors='coerce') print(df['rating'].isna().sum(), df['year'].isna().sum())errors='coerce' 的含义是“转不出来的统统变成 NaN”,好处是不会因为个别脏文本导致整个脚本报错;代价是丢掉了原始文本信息,所以如果后续要修正年份,需要保留原始字段备份。这一步做完,才算真正对这份数据建立了第一版认知。
3. 数据清洗实操:把“看起来干净”变成“真的可用”
3.1 缺失值处理:分辨哪些字段补、哪些字段删
10 万条电影记录的缺失值通常不在少数,但不同字段的处理逻辑完全不一样。评分为空可以有两种成因:一种是影片上映后没人打分,一种是爬虫采集时字段没抓到。前者的解决方式是保留并标记为“未打分”,后者则需要考虑这条样本还有没有其他字段可支撑分析。
df['rating'] = df['rating'].replace(0, pd.NA) print('评分为空的数量:', df['rating'].isna().sum()) print('评分为 0 的数量:', (df['rating'] == 0).sum())注意这里的 replace(0, pd.NA):很多电影平台的用户评分最低是 1 分,所以 0 分大概率代表“无评分”。如果不做这一步,0 会直接拉低平均值,导致后面算出来的平台整体表现失真。评分字段的缺失样本如果占比小于 5%,可以直接在评分相关分析里过滤掉;但如果做的是全量特征聚合,建议保留样本、只把评分列置空。
对导演、主演这类文本字段,情况相反。它们是分类变量的核心来源,缺失了没法合理补全。如果要保留样本,我会用“未知”占位;如果缺失占比太高且不影响主要分析目标,整列丢弃反而更干净。
3.2 重复值判定:片名、导演和年份的组合才可信
电影数据重复是一个隐蔽问题。同一部电影可能因为译名不同、重映年份不同、甚至上映地区不同被记成多行。直接用 duplicated() 查全行重复意义不大,因为简介、评分这类字段稍有差异就会判定为不重复。
我一般用片名、导演、年份三列组合去重:
before_len = len(df) df = df.drop_duplicates( subset=['title', 'director', 'year'], keep='first' ).reset_index(drop=True) after_len = len(df) print(f'去重前:{before_len} 条,去重后:{after_len} 条')逻辑说明:片名加导演加年份的组合已经能锁定一部电影的绝大多数版本。keep='first' 表示保留第一次出现的记录,这样评分、评价人数等字段以先采集到的版本为准。reset_index(drop=True) 是为了避免去重后索引空洞,后面做 explode 和 groupby 时不会踩到索引错位。
3.3 把类型和主演拆成列表:为聚合分析做好准备
电影数据的类型和主演字段经常是“剧情/喜剧/爱情”或“主演A/主演B/主演C”这种斜线分隔格式。直接用字符串去做分组,会把“剧情/喜剧”和“剧情”当成两个完全不同的值。正确做法是先按分隔符拆成列表:
df['genre_list'] = df['genre'].str.split('/') df['actor_list'] = df['actors'].str.split(r'/|,|、')这里使用正则 r'/|,|、' 来切分主演字段,覆盖三种常见分隔风格。需要说明的是,str.split 默认生成的是 Python 列表,后面用 explode 展开时可以非常方便地做类型维度的聚合。如果数据集里的分隔符是逗号加空格,建议先做一次 strip:
df['genre_list'] = df['genre'].str.split(r'\s*/\s*')这一步不要省略。很多人跳过拆分直接聚合,结果会认为数据里存在上千种类型,实际上拆开后往往只有十几个。拆分之后再对数据做规模校验:
print(df['genre_list'].explode().nunique())输出结果是真正的类型数量。这个数字如果和直觉相差太大,说明原始字段里要么有脏标签,要么分隔格式比预期复杂。
4. 基于10万条数据的三类分析落地样例:评分分布、类型偏好与主创影响
4.1 评分分段与评价人数的关系:不要只看平均分
拿到清洗后的数据,第一个值得做的分析是评分分布。评分这个字段的真正价值不在平均值,而在分布形态。我习惯把评分切分成段,再看各分段里的样本量和评价人数中位数:
bins = [0, 3, 5, 6, 7, 8, 10] labels = ['0-3', '3-5', '5-6', '6-7', '7-8', '8-10'] df['rating_bin'] = pd.cut(df['rating'], bins=bins, labels=labels, right=False) bin_stats = df.groupby('rating_bin', observed=False).agg( 电影数量=('title', 'count'), 评价人数中位数=('votes', 'median') ) print(bin_stats)pd.cut 的 right=False 表示区间左闭右开,这样 8 分影片会归入“8-10”而不是“7-8”。评价人数中位数比均值更能抵抗极端值。这一步能迅速回答一个常见问题:10 万条数据里到底是高分片多还是低分片多。实际分析中,数据通常呈偏态分布,6 到 8 分段占据大头;如果出现 8 到 10 分段样本量暴增,要怀疑是有刷分或字段解析问题。
4.2 类型展开与偏好画像:explode 之后重新聚合
类型是电影数据里最值得立刻拆开做聚合的字段。把 genre_list 用 explode 展开后,一行电影会变成多行,每行对应电影的一个类型标签:
df_exploded = df.explode('genre_list') genre_stats = df_exploded.groupby('genre_list', as_index=False).agg( 电影数量=('title', 'count'), 平均评分=('rating', 'mean'), 评价总数=('votes', 'sum') ) genre_stats = genre_stats[genre_stats['电影数量'] >= 30] genre_stats = genre_stats.sort_values('平均评分', ascending=False) print(genre_stats.head(15))explode 的作用是把列表里的每个元素拆成独立行,同时保留该行的评分、年份等其他字段。groupby 之后得到的电影数量是“带有这个标签的电影数”,评价总数是“这个类型下的总投票量”。这里用电影数量 >= 30 做过滤,是为了避免只有一两部的冷门类型因为偶然高分排到顶部。
分析时我一般会同时看平均评分和电影数量两张表:平均评分高的类型通常是纪录片或小众艺术片,电影数量多的类型往往是剧情和喜剧;真正有价值的是“评价总数高但平均评分不低”的类型,这说明它既有受众又出作品。
4.3 导演与主演组合:从频率和均分两端找信号
导演字段可以直接做分组聚合,但直接用原始导演名分组会得到大量只有一部作品的导演,统计上没有意义。我常用的门槛是作品数大于等于 3 或 5:
director_stats = df.groupby('director').agg( 作品数量=('title', 'count'), 平均评分=('rating', 'mean'), 评价总数=('votes', 'sum') ) director_stats = director_stats[director_stats['作品数量'] >= 3] director_stats = director_stats.sort_values(['作品数量', '平均评分'], ascending=[False, False]) print(director_stats.head(20))这个结果能看出两类导演:一类是高产但评分平稳的,一类是作品少但平均分极高的。前者适合做“稳定输出”类特征,后者适合做“品质备份”类特征。
主演字段同样可以展开:
actor_exploded = df.explode('actor_list') actor_stats = actor_exploded.groupby('actor_list').agg( 作品数量=('title', 'count'), 平均评分=('rating', 'mean') ) actor_stats = actor_stats[actor_stats['作品数量'] >= 5] actor_stats = actor_stats.sort_values('平均评分', ascending=False) print(actor_stats.head(10))这里我习惯把 actor_list 里拆出来的空字符串先过滤掉:
actor_exploded = actor_exploded[actor_exploded['actor_list'].str.strip() != '']如果不做这一步,空字符串会成为一个作品数量巨大的“假演员”,直接干扰排序。导演与主演组合的分析结果,可以继续作为推荐系统或内容池筛选的候选特征,也可以只用来做数据报告里的交叉结论。
5. 数据集避坑指南:从乱码到错位的排查路径
5.1 编码错乱与列错位:读取阶段最容易翻车
现象:read_csv 打开后中文变成乱码,或者某一行看起来少了几列、后面的字段整体向左或向右错位。
原因:编码错乱通常是文件实际不是 UTF-8,而是 GBK 或 GB18030;列错位则是因为某些电影简介或片名里含有逗号、换行符、引号,而 read_csv 默认的逗号分隔把字段切断了。
解决:读取时显式指定编码,最有兼容性的做法是使用 gb18030:
with zf.open(csv_name) as f: df = pd.read_csv(f, encoding='gb18030', errors='replace')errors='replace' 会把个别无法解码的字节替换成占位符,保证整份文件能读进来。读取后再用 sample 抽查乱码比例,如果超过 1%,再考虑用 utf-8-sig 或其他编码重试。列错位问题靠指定 quotechar 解决:
df = pd.read_csv(f, encoding='gb18030', quotechar='"', doublequote=True)quotechar 表示“用什么字符包住含分隔符的字段”,doublequote=True 允许字段内部出现成对的引号。如果文件的引号格式不规范,这一步仍然救不回来,就需要退回到用原始文本按行检查,定位是哪些行导致解析断开。
5.2 年份带文字与评分脏值是清洗期第二大坑
现象:year 列转成数值后大量变成 NaN,或者 year 的平均值异常低;rating 列出现 0 分、10 分占比过高、或者“暂无评分”字样。
原因:年份列常见格式是“1998年”“1998(中国大陆)”“1998-11-20”,字符串里混着非数字字符;评分列则可能把“暂无评分”“评分中”“7.5分”混在纯数字里。
解决:年份用正则抽取四位连续数字,再进行区间筛选:
year_text = df['year'].astype(str) year_extract = year_text.str.extract(r'(?<!\d)(\d{4})(?!\d)') df['year_clean'] = pd.to_numeric(year_extract[0], errors='coerce') df.loc[~df['year_clean'].between(1888, 2026, inclusive='both'), 'year_clean'] = pd.NA正则 (?<!\d)(\d{4})(?!\d) 的作用是抽取独立的四位数,避免从“20123”这类脏数据里提出 0123。1888 到 2026 的区间来自电影诞生年份到当前年份的合理跨度;超出范围的年份多半是乱码或录入错误。评分方面,先把“分”字去掉再转数值:
df['rating'] = df['rating'].astype(str).str.replace('分', '', regex=False) df['rating'] = pd.to_numeric(df['rating'], errors='coerce') df.loc[df['rating'] == 0, 'rating'] = pd.NA清洗后必须重新做一次空值统计。如果某一年份的缺失比例特别高,大概率是当年影片的日期被单独存到了另一个字段,需要回到原始字段去看完整内容,而不是直接删除这些样本。
5.3 去重过度与列表拆分遗漏
现象:去重后数据量从 10 万掉到 5 万以内,或者类型聚合得到的类型数远多于常识。
原因:subset 里选了片名加导演,但没加年份,导致不同年份的续集、翻拍被误删;列表拆分时没有处理引号或全角逗号,导致“剧情 / 喜剧”被拆出带空格的前后残留。
解决:去重时先看三列组合的重复量:
dup_count = df.duplicated(subset=['title', 'director', 'year']).sum() print('三列重复量:', dup_count)如果三列重复量很小,但两列重复量很大,说明数据集里同一系列的不同年份影片就是靠年份区分的,必须保留年份。列表拆分则建议统一清理分隔符后再 split:
df['genre_clean'] = df['genre'].replace(',', '/', regex=True).replace('、', '/', regex=True) df['genre_clean'] = df['genre_clean'].str.replace(r'\s*/\s*', '/', regex=True)这一条操作把全角标点和空格统一成半角斜杠后再拆分,能避免把“剧情 ”和“剧情”视为两个类型。清洗逻辑宁可多花十行代码,也比事后发现聚合结果不对劲再回头查字段强。
6. 让10万条数据在普通笔记本上跑出结果:采样、分块与验证技巧
数据集分析最后一步往往不是代码,而是性能。10 万行数据对现代笔记本来说不算大,但如果字段里有超长简介文本,内存会吃紧,groupby 也会变慢。我通常用三层手段让流程更顺。第一层是读取时就指定 dtype,把明显是低基数的导演、语言字段压缩成 category 类型:
dtype_dict = { 'year': 'int16', 'rating': 'float32', 'language': 'category', 'director': 'category', } df = pd.read_csv(csv_name, dtype=dtype_dict, encoding='gb18030')category 类型在内存里只存一次字典映射,对 10 万行数据的重复字符串非常有效;int16 对年份字段足够,float32 对评分字段也不会损失精度。第二层是分块读取,适合在数据到底有多大未知时:
chunk_iter = pd.read_csv(csv_name, chunksize=20000, encoding='gb18030') total = 0 for chunk in chunk_iter: total += chunk['rating'].notna().sum() print('有效评分数:', total)chunksize 调到 20000 行,每次只处理一个块,内存占用始终稳定。第三层是采样验证,特别适合排查清洗规则是否合适:
sample_df = df.sample(frac=0.2, random_state=42) sample_avg = sample_df.groupby('year')['rating'].mean() full_avg = df.groupby('year')['rating'].mean() diff = (sample_avg - full_avg).abs() print(diff.max())random_state=42 保证采样结果可复现;如果 20% 采样算出的年度平均评分与全量结果的最大偏差不超过 0.1,说明数据整体分布稳定,后续可以放心在采样集上做快速迭代。我会把规则验证到这一层,才拿全量跑最终报告。
之前做类似电影数据的分析时,我吃过一次亏:跳过采样,直接拿全量做了一遍导演聚合,结果因为某些导演名字里有尾随空格,排前排后的结论完全不对。后来习惯在每一次清洗后都用 sample 抽查两行原始值,确认变换没有误伤正常样本,再往后走。这个习惯虽然多花几分钟,但能避免返工。数据项目的价值不在代码写了多少,而在结论能不能信;希望这份从读取到验证的流程能帮到你。数据本身不会替你回答业务问题,但当它足够干净,你的分析才站得住脚。
本文还有配套的精品资源,点击获取