简介豆瓣电影数据集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, encodingutf-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(ascendingFalse) print(null_stats)这一步能直接影响后续清洗策略。以常见的电影记录为例评分和评价人数往往有少量缺失简介字段缺失比例可能更高而片名、导演、年份这类关键字段如果缺失太多说明这份数据集可能本身就不完整需要决定是丢弃还是粗查。类型判断方面最需要警惕的是评分、年份、时长这三列。很多 CSV 从网页导出时会把它们存成文本比如年份写成“1998年”或者“1998(大陆)”评分写成“7.5分”或“暂无评分”。直接用 pd.to_numeric 转就会出现一整列 NaN。我习惯先统一转一遍再把转不出来和正常数值分开统计df[rating] pd.to_numeric(df[rating], errorscoerce) df[year] pd.to_numeric(df[year], errorscoerce) print(df[rating].isna().sum(), df[year].isna().sum())errorscoerce 的含义是“转不出来的统统变成 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], keepfirst ).reset_index(dropTrue) after_len len(df) print(f去重前{before_len} 条去重后{after_len} 条)逻辑说明片名加导演加年份的组合已经能锁定一部电影的绝大多数版本。keepfirst 表示保留第一次出现的记录这样评分、评价人数等字段以先采集到的版本为准。reset_index(dropTrue) 是为了避免去重后索引空洞后面做 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 展开时可以非常方便地做类型维度的聚合。如果数据集里的分隔符是逗号加空格建议先做一次 stripdf[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], binsbins, labelslabels, rightFalse) bin_stats df.groupby(rating_bin, observedFalse).agg( 电影数量(title, count), 评价人数中位数(votes, median) ) print(bin_stats)pd.cut 的 rightFalse 表示区间左闭右开这样 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_indexFalse).agg( 电影数量(title, count), 平均评分(rating, mean), 评价总数(votes, sum) ) genre_stats genre_stats[genre_stats[电影数量] 30] genre_stats genre_stats.sort_values(平均评分, ascendingFalse) print(genre_stats.head(15))explode 的作用是把列表里的每个元素拆成独立行同时保留该行的评分、年份等其他字段。groupby 之后得到的电影数量是“带有这个标签的电影数”评价总数是“这个类型下的总投票量”。这里用电影数量 30 做过滤是为了避免只有一两部的冷门类型因为偶然高分排到顶部。分析时我一般会同时看平均评分和电影数量两张表平均评分高的类型通常是纪录片或小众艺术片电影数量多的类型往往是剧情和喜剧真正有价值的是“评价总数高但平均评分不低”的类型这说明它既有受众又出作品。4.3 导演与主演组合从频率和均分两端找信号导演字段可以直接做分组聚合但直接用原始导演名分组会得到大量只有一部作品的导演统计上没有意义。我常用的门槛是作品数大于等于 3 或 5director_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(平均评分, ascendingFalse) 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 默认的逗号分隔把字段切断了。解决读取时显式指定编码最有兼容性的做法是使用 gb18030with zf.open(csv_name) as f: df pd.read_csv(f, encodinggb18030, errorsreplace)errorsreplace 会把个别无法解码的字节替换成占位符保证整份文件能读进来。读取后再用 sample 抽查乱码比例如果超过 1%再考虑用 utf-8-sig 或其他编码重试。列错位问题靠指定 quotechar 解决df pd.read_csv(f, encodinggb18030, quotechar, doublequoteTrue)quotechar 表示“用什么字符包住含分隔符的字段”doublequoteTrue 允许字段内部出现成对的引号。如果文件的引号格式不规范这一步仍然救不回来就需要退回到用原始文本按行检查定位是哪些行导致解析断开。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], errorscoerce) df.loc[~df[year_clean].between(1888, 2026, inclusiveboth), year_clean] pd.NA正则 (?!\d)(\d{4})(?!\d) 的作用是抽取独立的四位数避免从“20123”这类脏数据里提出 0123。1888 到 2026 的区间来自电影诞生年份到当前年份的合理跨度超出范围的年份多半是乱码或录入错误。评分方面先把“分”字去掉再转数值df[rating] df[rating].astype(str).str.replace(分, , regexFalse) df[rating] pd.to_numeric(df[rating], errorscoerce) 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)如果三列重复量很小但两列重复量很大说明数据集里同一系列的不同年份影片就是靠年份区分的必须保留年份。列表拆分则建议统一清理分隔符后再 splitdf[genre_clean] df[genre].replace(, /, regexTrue).replace(、, /, regexTrue) df[genre_clean] df[genre_clean].str.replace(r\s*/\s*, /, regexTrue)这一条操作把全角标点和空格统一成半角斜杠后再拆分能避免把“剧情 ”和“剧情”视为两个类型。清洗逻辑宁可多花十行代码也比事后发现聚合结果不对劲再回头查字段强。6. 让10万条数据在普通笔记本上跑出结果采样、分块与验证技巧数据集分析最后一步往往不是代码而是性能。10 万行数据对现代笔记本来说不算大但如果字段里有超长简介文本内存会吃紧groupby 也会变慢。我通常用三层手段让流程更顺。第一层是读取时就指定 dtype把明显是低基数的导演、语言字段压缩成 category 类型dtype_dict { year: int16, rating: float32, language: category, director: category, } df pd.read_csv(csv_name, dtypedtype_dict, encodinggb18030)category 类型在内存里只存一次字典映射对 10 万行数据的重复字符串非常有效int16 对年份字段足够float32 对评分字段也不会损失精度。第二层是分块读取适合在数据到底有多大未知时chunk_iter pd.read_csv(csv_name, chunksize20000, encodinggb18030) total 0 for chunk in chunk_iter: total chunk[rating].notna().sum() print(有效评分数, total)chunksize 调到 20000 行每次只处理一个块内存占用始终稳定。第三层是采样验证特别适合排查清洗规则是否合适sample_df df.sample(frac0.2, random_state42) 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_state42 保证采样结果可复现如果 20% 采样算出的年度平均评分与全量结果的最大偏差不超过 0.1说明数据整体分布稳定后续可以放心在采样集上做快速迭代。我会把规则验证到这一层才拿全量跑最终报告。之前做类似电影数据的分析时我吃过一次亏跳过采样直接拿全量做了一遍导演聚合结果因为某些导演名字里有尾随空格排前排后的结论完全不对。后来习惯在每一次清洗后都用 sample 抽查两行原始值确认变换没有误伤正常样本再往后走。这个习惯虽然多花几分钟但能避免返工。数据项目的价值不在代码写了多少而在结论能不能信希望这份从读取到验证的流程能帮到你。数据本身不会替你回答业务问题但当它足够干净你的分析才站得住脚。本文还有配套的精品资源点击获取