高分电影全链路数据分析

TMDb 高分电影数据全链路分析项目报告

作者:黄思博
技术栈:Python (requests/lxml/pandas/sklearn/jieba/wordcloud) | MySQL | DataGrip | Tableau Public | Kaggle
数据来源:TMDb Top-Rated 榜单(1000 条)+ Kaggle TMDB & IMDB Merged Dataset(68 万条)
项目周期:约 8 周


一、项目背景与目标

TMDb(The Movie Database)是全球最具影响力的电影数据库之一。本次项目以“高分电影特征分析”为核心,回答以下问题:

  1. 高分电影有什么共性?(时长、类型、语言、导演等)
  2. 电影产量与质量随时间如何变化?
  3. 英语片与非英语片的表现有何差异?
  4. 能否用机器学习预测一部电影是否属于高分?

项目经历从网页爬虫 → 数据清洗 → SQL 分析 → Tableau 可视化 → 文本挖掘 → 机器学习预测的完整链路,最终形成一份可落地的数据分析作品。


二、第一阶段:网页爬虫获取数据

2.1 技术方案

使用 Python 的 requests + lxml 编写爬虫,抓取 TMDb Top-Rated 榜单前 1000 部电影的详情页数据。

目标字段(11 个):电影名称、年份、上映日期、类型、时长、评分、语言、导演、标语、简介、详情页 URL。

2.2 关键技术点

  1. 列表页与详情页分离抓取:先抓列表页拿到 1000 部电影的详情页 URL,再逐个访问详情页提取字段。
  2. XPath 定位:用 Chrome DevTools 分析 HTML 结构,定位到评分组件 consensus_pill、时长组件、语言组件等。
  3. 随机延迟防封:每个请求间隔 random.uniform(1, 2) 秒。
  4. 多 XPath 兜底:评分人数尝试从 data-count 属性、consensus_pill 文本、全文正则三种方式提取。
  5. Session + 自动重试:使用 requests.Session 复用连接,配置 urllib3.Retry 自动重试 429/5xx 错误。

2.3 遇到的问题

问题 现象 原因 解决方案
评分人数抓取失败 全部为 0 rating_details_window 是 JS 动态弹窗,静态 HTML 中不存在 确认静态请求无法获取后,将该字段标记为缺失,后续改用 Kaggle 数据集中的 vote_count 字段补全
IP 被限制 403 Forbidden、ConnectionResetError 请求频率触发 TMDb 反爬机制 每个请求间隔 random.uniform(1, 2) 秒;使用 requests.Session 复用连接;配置 urllib3.Retry 自动重试 429/5xx 错误;若持续 403 则暂停抓取并降低请求频率
API 访问失败 api.themoviedb.org 超时 该域名在国内被墙 注册 TMDb 官方 API 后仍无法访问;尝试 Cloudflare Workers 代理,但 workers.dev 域名同样被墙;最终放弃 API 方案,改用 Kaggle 公开数据集 TMDB + IMDB Merged Movies Dataset 完成后续分析与建模

2.4 最终产出

成功抓取 1000 条电影数据,字段完整率约 98%(仅评分人数缺失),保存为 csv_data/movie_list.csv。
image


三、第二阶段:Pandas 数据清洗

3.1 清洗任务

字段 原始格式 清洗后格式
电影评分 "79"(0–100) 7.9(0–10)
电影年份 "(1994)" 1994(整数)
电影时长 "2h 22m" / "142 分钟" 142(整数分钟)
类型 "剧情,史诗" 拆分为多行存入 movie_genres 表
语言 "$20,000,000"(异常值) 剔除异常值,保留有效语言

3.2 关键代码

def parse_rating(text):
    return round(float(text) / 10, 1) if text else None


def parse_runtime(text):
    text = text.strip().lower()
    m = re.match(r"(?:(\d+)\s*h)?\s*(?:(\d+)\s*m)?", text)
    if m and (m.group(1) or m.group(2)):
        h = int(m.group(1)) if m.group(1) else 0
        mnt = int(m.group(2)) if m.group(2) else 0
        return h * 60 + mnt
    m = re.search(r"(\d+)", text)
    return int(m.group(1)) if m else None

3.3 数据入库

使用 Datagrip 将清洗后的数据导入 MySQL,建立两张表:

CREATE TABLE movies (
    movie_id INT PRIMARY KEY,
    title VARCHAR(255),
    year INT,
    release_date DATE,
    runtime_min INT,
    rating DECIMAL(3,1),
    language VARCHAR(100),
    director VARCHAR(255),
    tagline TEXT,
    overview TEXT,
    url VARCHAR(255)
);

CREATE TABLE movie_genres (
    movie_id INT,
    genre VARCHAR(50),
    PRIMARY KEY (movie_id, genre),
    FOREIGN KEY (movie_id) REFERENCES movies(movie_id)
);

3.4 清洗后数据展示

image


四、第三阶段:DataGrip SQL 筛选分析

4.1 数据质量检查

SELECT
    COUNT(*) AS total,
    SUM(year IS NULL) AS missing_year,
    SUM(rating IS NULL) AS missing_rating,
    SUM(runtime_min IS NULL) AS missing_runtime
FROM movies;

结果:1000 条数据,缺失时长 17 条(1.7%),其余字段完整。

4.2 核心分析 SQL

(1)评分分层

SELECT
    CASE
        WHEN rating >= 8.5 THEN '超高分(>=8.5)'
        WHEN rating >= 8.0 THEN '高分(8.0-8.5)'
        WHEN rating >= 7.5 THEN '中高(7.5-8.0)'
        ELSE '中分(<7.5)'
    END AS rating_level,
    COUNT(*) AS cnt,
    ROUND(AVG(runtime_min), 0) AS avg_runtime,
    ROUND(AVG(year), 0) AS avg_year
FROM movies
WHERE rating IS NOT NULL
GROUP BY rating_level;

结果:

评分档 数量 平均时长 平均年份
超高分(>=8.5) 30 143 分钟 2002
高分(8.0-8.5) 372 119 分钟 1999
中高(7.5-8.0) 598 112 分钟 1997

(2)年代趋势

SELECT
    FLOOR(year / 10) * 10 AS decade,
    COUNT(*) AS movie_count,
    ROUND(AVG(rating), 2) AS avg_rating,
    ROUND(AVG(runtime_min), 0) AS avg_runtime
FROM movies
WHERE year BETWEEN 1920 AND 2025
GROUP BY decade
ORDER BY decade;

(3)语言差异

SELECT
    language,
    COUNT(*) AS cnt,
    ROUND(AVG(rating), 2) AS avg_rating
FROM movies
WHERE language IS NOT NULL AND language != ''
GROUP BY language
HAVING cnt >= 5
ORDER BY avg_rating DESC;

结果:日语(7.99)、西班牙语(7.98)、朝鲜语(7.98)、意大利语(7.98)均高于英语(7.94)。

4.3 核心发现

  1. 时长与评分正相关:超高分电影平均 143 分钟,比中高片多 31 分钟。
  2. 英语片“量大不优”:占 56% 数量,但平均分低于意日韩西语。
  3. 2010 年代产量爆发:270 部上榜,但各年代平均分稳定在 7.89–8.00。

五、第四阶段:Tableau 可视化 + 词云分析

5.1 Tableau 看板设计

建立 2 个仪表板,共 6 张核心图表:

图表 类型 展示内容
KPI 卡片 文本 总数 1000、平均分 7.94、平均时长 115、年份跨度
评分分布 直方图 集中于 7.5–8.5
时长 vs 评分 散点图 正相关趋势
年代趋势 双轴图 柱状产量 + 折线平均分
语言差异 条形图 意日韩西语高于英语
导演四象限 散点图 大师/劳模/偏科生

image
image

5.2 词云分析(Python)

使用 jieba + wordcloud 对 1000 条电影简介进行分词与词频统计,生成 5 张词云图:
image

分类 高频词
超高分(>=8.5) 教父、家族、神秘、希望、黑手党、战争
中高分(7.5–8.0) 生活、世界、父亲、妻子、母亲、家庭

结论:超高分电影偏向“宏大叙事与强冲突”,中高分电影偏向“日常题材与家庭关系”。

5.3 核心可视化结论

  • 超高分电影平均时长 143 分钟,被史诗题材拉高;
  • 2020 年代高分片时长回落至 112 分钟,趋向紧凑叙事;
  • 英语片数量占优但平均分低于意日韩西语,存在“幸存者偏差”。

六、深入数据分析:三大主题与三个追问

在完成初步的 SQL 筛选和 Tableau 可视化后,我们对数据进行了更深入的主题式分析,形成三个核心主题,并针对每个主题提出了一个关键追问。

主题一:高分电影特征

结论:

  1. 时长是高分片的硬指标。 超高分电影(≥8.5)平均时长 143 分钟,比中高评分片(7.5–8.0)的 112 分钟整整长了 31 分钟。在 Top-Rated 榜单里,想要冲上 8.5 分,电影几乎都在 2 小时以上(通常是史诗、传记或深度剧情片)。
  2. 年代不是决定性因素。 虽然超高分片平均年份(2002)比中高片(1997)晚,但差距只有 5 年。说明“老片天然评分高”的假设在 Top-Rated 里不成立,榜单对各个年代的优秀电影是公平的。

注意:超高分电影只有 30 部(占比约 3%),样本量偏小,结论需注明“基于 Top-Rated 榜单内的观察”。

主题二:年代趋势(产量与质量的背离)

核心结论:

  1. 产量爆发在 2010 年代。 2010 年代上榜电影 270 部,是 2000 年代(145 部)的近 2 倍。但 2020 年代至今(170 部)趋势依然强劲。
  2. 质量整体非常稳定。 所有年代的平均分都在 7.89–8.00 之间波动,没有出现“越老越经典”或“越新越水”的明显趋势。1990 年代以 8.00 微微领跑,2000 年代略低(7.90)。
  3. 时长呈抛物线。 电影从 1920 年代的 84 分钟,一路增加到 1970 年代的 133 分钟,然后又回落到 2010 年代的 111 分钟。现代电影越来越短,但产量越来越高。

主题三:语言差异

结论:

  1. 英语片“量大不优”。 英语片以 560 部的绝对数量占据榜单半壁江山,但平均分(7.94)和高分比例(40.0%)低于日语、西班牙语、朝鲜语和意大利语。
  2. 意日韩西表现亮眼。 意大利语高分比例最高(52.7%),日语平均分最高(7.99),西班牙语和朝鲜语紧随其后。非英语片在 Top-Rated 里的质量密度更高。
  3. 法语德语意外偏低。 法语片虽然数量多(62 部),但平均分只有 7.88,高分比例只有 24.2%,低于英语片。这打破了“法语片=文艺高分”的刻板印象。

深入追问一:为什么高评分电影的平均时长在 143 分钟?

1. 类型决定论:143 分钟是被“史诗题材”拉高的

从类型分布看:

  • 拉高平均值的类型:剧情(150 分钟)、犯罪(156 分钟)、动作(150 分钟)、奇幻(148 分钟)、冒险(158 分钟)。这些类型往往需要宏大的世界观或复杂的人物弧光(如《指环王》《教父》)。
  • 拉低平均值的类型:动画(111 分钟)、爱情(120 分钟)、家庭(114 分钟)、音乐(116 分钟)、纪录(95 分钟)。这些类型更注重情感浓度或创意密度,不需要长篇幅。

2. 年代决定论:2020 年代的“叙事紧凑化”

从年代分布看:

  • 1990 年代(165 分钟)、2000 年代(180 分钟)的超高分片极长。
  • 但 2020 年代,超高分电影数量最多(10 部),平均时长急剧下降到了 112 分钟。

这意味着:现代电影不再靠“拉长时长”来拿高分,而是靠高密度的信息量和紧凑的叙事。143 分钟是被 70–90 年代的历史/史诗片拉高的,现代高分片其实更短。

3. 个案印证:Top 10 超长高分片全是“史诗”

看看前 10 名:《指环王3》(263 分钟)、《七武士》(207 分钟)、《教父2》(200 分钟)、《辛德勒的名单》(195 分钟)。这些无一例外都是史诗、历史、黑帮题材。它们用超长时长讲透了复杂的时代背景。

最终结论:143 分钟是“史诗题材”的典型特征,而非高分的通用公式。

数据揭示了一个有趣的分化:剧情、犯罪、奇幻等史诗题材的平均时长高达 148–158 分钟,它们拉高了整体均值。但值得注意的是,2020 年代超高分电影的平均时长已大幅回落至 112 分钟,且数量创下新高。这说明现代高分电影不再依赖《指环王》式的超长叙事,而是通过高密度的紧凑叙事取胜。因此,真正的规律是:史诗片需要时长来铺陈,而当代佳作倾向于用更短的篇幅实现高情感密度。


深入追问二:导演的高产与高质能否兼得?

结论:高产与高质并非天然矛盾,但“持续稳定”比“偶尔爆发”更难。

在 TMDb Top 1000 榜单中,诺兰、宫崎骏等导演稳居“大师象限”(作品数 ≥3,平均分 ≥8.0),且他们的评分标准差极小(通常 <0.3),说明他们不仅高产,而且质量极其稳定。

与之相对的是“劳模象限”(作品数多,平均分低于榜单均值),这类导演靠数量上榜,但缺乏顶尖代表作。

值得注意的是,大量非英语导演集中在“偏科生象限”(上榜 1–2 部,平均分极高),这再次印证了非英语片进入 Top 1000 的“幸存者偏差”——只有极其优秀的作品才能突破语言壁垒进入榜单。

此外,从作品平均时长来看,“大师象限”的导演往往偏好长片叙事(如诺兰的 160+ 分钟),这解释了为什么高分电影的平均时长会被拉高至 143 分钟。


深入追问三:高分电影的关键词与时代关联

使用 jieba 对 1000 条电影简介进行分词,并通过 wordcloud 生成词云图,我们得到以下发现:

1. 超高分 vs 中高分的词云差异

  • 超高分电影(≥8.5):高频词为 教父、家族、神秘、希望、战斗、黑手党、战争、史诗。题材偏向强戏剧冲突与宏大叙事。
  • 中高分电影(7.5–8.0):高频词为 生活、世界、父亲、妻子、母亲、孩子、家庭、意外。题材更贴近日常化与个人命运。

结论:在 TMDb 高分区,探讨家族羁绊、生存希望与正义抗争的宏大题材更容易获得极高评价;而日常题材虽能进入榜单,但很难冲破 8.5 分。

2. 时代演变:从宏大史诗到微观人性

按年代切片后,我们发现:

  • 1990 年前:高频词多为 战争、历史、英雄、国家,高分片偏向宏大史诗。
  • 1990–2009 年:高频词开始出现 家庭、梦想、自由、爱情,叙事转向个人命运。
  • 2010 年后:高频词进一步微观化,出现 现实、成长、孤独、记忆,现代高分片更关注个体的内心世界与精神困境。

这一演变与“电影时长变短”(2010 年代平均 111 分钟)的趋势相呼应。现代高分电影不再依赖超长篇幅去铺陈战争与历史的宏大背景,而是用更短的时长、更紧凑的叙事,去聚焦个体在现实中的情感挣扎。这就是 TMDb 榜单背后的审美变迁。


七、第五阶段:爬虫进阶与数据源切换

7.1 尝试添加评分人数和票房字段

为了让机器学习模型拥有更强的特征,我尝试在爬虫中添加 vote_count(评分人数)和 revenue(票房)字段。

遇到的问题:

  1. 评分人数抓不到:rating_details_window 是 Kendo UI 动态弹窗,requests 拿到的静态 HTML 中不存在该元素,多方案兜底(data-count 属性、文本正则、全文搜索)均返回 0。
  2. API 访问失败:注册 TMDb 官方 API 后,api.themoviedb.org 在国内被墙,连接超时。
  3. Cloudflare Workers 代理失败:workers.dev 域名同样被墙,WinError 10061 目标计算机积极拒绝。
  4. IP 被限流:频繁请求触发 TMDb 反爬,持续 403 Forbidden。

7.2 解决方案:使用 Kaggle 公开数据集

经过评估,决定放弃继续攻克爬虫,转而使用 Kaggle 公开数据集 TMDB + IMDB Merged Movies Dataset。

数据集信息:


八、第六阶段:机器学习预测

8.1 数据准备

  • 原始数据:683,475 条
  • 清洗后:257,643 条(剔除缺失年份/时长/评分)
  • 过滤 vote_count >= 50(保证评分稳定性):27,768 条

8.2 目标变量构造

df['is_high_rated'] = (df['rating'] >= 8.0).astype(int)

结果:高分 602 条,非高分 27,166 条,比例 1:45(严重不平衡)。

8.3 特征工程

  • 数值特征:runtime_min、year、revenue、vote_count
  • 类别特征:language(原始语言)、genres(类型)

使用 ColumnTransformer + OneHotEncoder 处理类别变量,RandomForestClassifier 训练模型。

8.4 模型评估结果

========== 模型评估报告 ==========
              precision    recall  f1-score   support
         非高分       0.99      0.92      0.95      5434
          高分       0.11      0.48      0.19       120
    accuracy                           0.91      5554
   macro avg       0.55      0.70      0.57      5554
weighted avg       0.97      0.91      0.93      5554

ROC-AUC 分数:0.8715

核心指标解读:

指标 值 含义
ROC-AUC 0.8715 排序能力优秀,87% 概率把高分排前面
macro avg F1 0.57 高分小类的综合表现
高分 recall 0.48 抓到约一半的高分电影
accuracy 0.91 整体准确率(但会骗人,因数据不平衡)

8.5 特征重要性分析

排名 特征 重要性 业务含义
1 runtime_min 23.6% 时长是最强预测因子
2 vote_count 21.2% 评分人数次之,高热度=高口碑
3 year 14.0% 年份有中等影响
4 revenue 7.5% 票房影响有限,高票房≠高口碑
5–15 language_* / genres_* 各 <5% 语言和类型影响分散

8.6 核心结论

  1. 时长和评分人数是决定电影高分的最强信号,合计贡献 45% 的重要性,完美验证了 SQL 分析阶段“超高分电影平均 143 分钟”的结论。
  2. 票房影响有限,说明“商业成功”和“艺术评价”是两回事。
  3. 语言和类型的影响分散,说明高分电影不存在单一的“类型偏好”。

8.7 模型局限性

  • 数据严重不平衡(高分仅占 2.2%),导致高分类别 precision 偏低;
  • 未使用 SMOTE 过采样,可通过 imbalanced-learn 提升召回率;
  • 未纳入导演、演员、制作公司等特征。

九、项目成果与交付物

9.1 交付清单

交付物 说明
Tableau Public 看板 6 张图表 + 2 个仪表板([链接])
GitHub 代码仓库 爬虫、清洗、SQL、词云、机器学习全流程
PDF 报告 本报告,15–20 页
原始数据集 1000 条 TMDb 数据 + Kaggle 68 万条数据
词云图片 5 张(超高分、中高分、1990前、1990–2009、2010后)
特征重要性图 1 张

9.2 核心结论汇总

  1. 高分电影特征:超高分电影平均时长 143 分钟,被史诗题材拉高;2020 年代回落至 112 分钟,趋向紧凑叙事。
  2. 年代趋势:2010 年代产量爆发(270 部),但各年代平均分稳定在 7.89–8.00。
  3. 语言差异:英语片数量占优(56%),但平均分(7.94)低于意日韩西语,存在“幸存者偏差”。
  4. 文本挖掘:超高分电影高频词为“家族、希望、战斗”,中高分电影为“生活、父亲、妻子”。
  5. 机器学习:ROC-AUC 0.8715,“时长”和“评分人数”是最强预测因子。

十、技术难点与解决过程

难点 解决方案
评分人数在静态 HTML 中缺失 多 XPath 兜底 + 转向 Kaggle 数据集
api.themoviedb.org 被墙 尝试 Cloudflare Workers 代理,最终改用公开数据集
IP 被 TMDb 限流 随机延迟、Session 复用、自动重试
数据严重不平衡 使用 class_weight='balanced' 缓解
中文在 matplotlib 中乱码 设置 plt.rcParams['font.sans-serif']

项目最大收获:数据获取不是目的,分析结论才是。当爬虫遇到不可逾越的障碍时,及时切换数据源(Kaggle 公开数据集)是专业数据分析师的选择,而非在技术细节上死磕。


十一、局限性与未来改进

  1. 幸存者偏差:TMDb Top-Rated 榜单只有高分电影,缺少低分对照组,无法得出“电影越长评分越高”的普适结论。
  2. 特征不足:未纳入导演、演员、制作公司、预算等特征,模型可以进一步优化。
  3. 数据不平衡:高分样本仅 2.2%,可通过 SMOTE 过采样或调整阈值提升 recall。
  4. 文本挖掘深度:仅做词频统计,未来可尝试 TF-IDF、LDA 主题模型或 BERT 情感分析。
posted @ 2026-08-18 15:31  Hsibo  阅读(22)  评论(0)    收藏  举报