在影视行业数字化浪潮中,豆瓣电影作为国内最具影响力的评分与评论平台之一,沉淀了海量电影数据。本文基于Python构建了一套从数据爬取、清洗到分析与可视化的完整系统,帮助读者深入挖掘电影评分分布、类型趋势、导演/演员影响力等核心信息。无论你是电影爱好者还是行业从业者,都能从中获得数据参考与趋势洞察。

一、项目背景与核心目标

本项目旨在通过Python技术栈实现豆瓣电影数据的自动化采集与多维度分析,最终以交互式可视化图表呈现洞察结果。核心能力包括:

  • 自动化采集:抓取豆瓣电影的基础信息、评分、评论及影片详情等字段。
  • 结构化存储:将数据清洗后存储至CSV与MySQL数据库。
  • 统计分析与可视化:针对导演/演员、地区、类型、评分等维度进行深度统计,并基于Echarts/Matplotlib/Seaborn生成交互式图表。

值得注意的是,虽然本系统以Python为主力语言,但类似的数据处理流程在Java、JavaScript、C++、Go等语言中同样可以复现(例如用Java的Jsoup库做爬虫,或用Go的Colly框架)。掌握一种语言的数据工程思维,便能举一反三。

二、核心数据维度说明

爬取的豆瓣电影核心字段涵盖基础信息、详情与评论三大类,具体如下:

字段分类具体字段说明
基础信息电影名、评分、封面图、详情 URL、上映时间核心标识与基础属性
制作信息导演、主演、类型、制片国家 / 地区、语言、片长影片制作维度特征
评价信息星星评分比例、评价人数、前五条热评、评论时间观众反馈维度
补充信息电影简介、预告片、5 张详情图片内容补充维度

这些字段为后续分析提供了丰富素材。例如,通过“导演”和“主演”字段可以统计个人作品数量;借助“制片地区”和“语言”字段能分析地域分布趋势。

三、核心技术实现流程

3.1 数据采集:豆瓣电影数据爬取

数据采集是整个流程的起点。我们使用Requests库请求豆瓣电影列表页与详情页,结合BeautifulSoup解析HTML结构,提取目标字段。针对数组类型字段(如导演、主演、类型),需进行格式化处理,最终标准化为可存储的结构。

以下是核心爬取代码的优化版:

import requests
import re
import json
import random
from bs4 import BeautifulSoup
# 请求头(模拟浏览器,避免反爬)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
}
def crawl_movie_data(detailUrls, moveisInfomation):
"""
爬取豆瓣电影详情数据
:param detailUrls: 电影详情页URL列表
:param moveisInfomation: 列表页提取的基础信息列表
:return: 结构化的电影数据列表
"""
result_list = []
for i, move_info in enumerate(moveisInfomation):
try:
result_data = {}
# 列表页基础信息提取
result_data['detailLink'] = detailUrls[i]  # 详情页URL
result_data['directors'] = ','.join(move_info['directors'])  # 导演(转字符串存储)
result_data['rate'] = move_info['rate']  # 评分
result_data['title'] = move_info['title']  # 影片名
result_data['casts'] = ','.join(move_info['casts'])  # 主演(转字符串存储)
result_data['cover'] = move_info['cover']  # 封面图URL
# 详情页深度信息提取
detail_res = requests.get(detailUrls[i], headers=headers, timeout=10)
detail_res.encoding = 'utf-8'
soup = BeautifulSoup(detail_res.text, 'lxml')
# 上映年份(正则提取)
year_elem = soup.find('span', class_='year')
result_data['year'] = re.findall(r'[(](.*?)[)]', year_elem.get_text())[0] if year_elem else ''
# 影片类型
type_elems = soup.find_all('span', property='v:genre')
types = [elem.get_text() for elem in type_elems]
result_data['types'] = ','.join(types)
# 制片国家/地区
country_elem = soup.find_all('span', class_='pl')[4].next_sibling.strip()
country = [c.strip() for c in country_elem.split('/')]
result_data['country'] = ','.join(country)
# 影片语言
lang_elem = soup.find_all('span', class_='pl')[5].next_sibling.strip()
lang = [l.strip() for l in lang_elem.split('/')]
result_data['lang'] = ','.join(lang)
# 上映时间(正则提取日期)
uptime_elems = soup.find_all('span', property='v:initialReleaseDate')
uptime_str = ''.join([elem.get_text() for elem in uptime_elems])
uptime = re.findall(r'\d*-\d*-\d*', uptime_str)[0] if uptime_str else ''
result_data['time'] = uptime
# 影片时长(无数据则随机填充合理范围)
runtime_elem = soup.find('span', property='v:runtime')
if runtime_elem:
result_data['movieTime'] = re.findall(r'\d+', runtime_elem.get_text())[0]
else:
result_data['movieTime'] = str(random.randint(80, 180))  # 修正原代码moveiTime拼写错误
# 评价人数
comment_len_elem = soup.find('span', property='v:votes')
result_data['comment_len'] = comment_len_elem.get_text() if comment_len_elem else '0'
# 星星评分比例
star_elems = soup.find_all('span', class_='rating_per')
stars = [elem.get_text() for elem in star_elems]
result_data['starts'] = ','.join(stars)
# 影片简介
summary_elem = soup.find('span', property='v:summary')
result_data['summary'] = summary_elem.get_text().strip() if summary_elem else ''
# 前五条热评(结构化存储)
comments = []
comment_info_elems = soup.find_all('span', class_='comment-info')[:5]
comment_content_elems = soup.find_all('span', class_='short')[:5]
for idx, (info_elem, content_elem) in enumerate(zip(comment_info_elems, comment_content_elems)):
comment = {}
comment['user'] = info_elem.contents[1].get_text() if len(info_elem.contents) > 1 else ''
comment['star'] = re.findall(r'(\d*)', info_elem.contents[5].attrs['class'][0])[7] if len(info_elem.contents) > 5 else ''
comment['time'] = info_elem.contents[7].attrs['title'] if len(info_elem.contents) > 7 else ''
comment['content'] = content_elem.get_text()
comments.append(comment)
result_data['comments'] = json.dumps(comments, ensure_ascii=False)
# 5张详情图片
img_elems = soup.select('.related-pic-bd img')
img_list = [elem['src'] for elem in img_elems[:5]]
result_data['imgList'] = ','.join(img_list)
result_list.append(result_data)
except Exception as e:
print(f"爬取第{i}条数据失败:{str(e)}")
continue
return result_list

⚠️ 注意事项:豆瓣有较强的反爬机制,建议在正式环境中加入IP代理池、随机User-Agent和请求延迟,以降低被封风险。如果使用Java或Go实现爬虫,可借助OkHttp或Colly框架处理类似的请求与解析逻辑。

3.2 数据存储与结构化

爬取完成后,将结构化数据同步存储至CSV文件与MySQL数据库,并记录爬取页数以避免重复。核心逻辑如下:

  • CSV存储:利用Pandas的to_csv方法,指定编码为utf-8-sig(解决中文乱码)。
  • MySQL存储:通过pymysql库建立连接,批量插入数据以提升效率。 如果数据量极大(如百万级),可考虑改用Go的并发写入或Java的JDBC批处理。

接着,将清洗后的数据转换为Pandas DataFrame,方便后续分析。修正原代码中Pandas别名不规范(ps改为通用的pd)及字段拼写错误等问题:

from . import homeData
import pandas as pd
# 构建DataFrame(标准化字段名与顺序)
df = pd.DataFrame(
homeData.getAllData(),
columns=[
'id', 'directors', 'rate', 'title', 'casts', 'cover', 'year', 'types',
'country', 'lang', 'time', 'movieTime', 'comment_len', 'starts',
'summary', 'comments', 'imgList', 'movieUrl', 'detailLink'
]
)
# 数据清洗:空值填充、类型转换
df['rate'] = pd.to_numeric(df['rate'], errors='coerce').fillna(0)  # 评分转数值,空值填0
df['comment_len'] = pd.to_numeric(df['comment_len'], errors='coerce').fillna(0)  # 评价人数转数值
df['movieTime'] = pd.to_numeric(df['movieTime'], errors='coerce').fillna(0)  # 时长转数值

3.3 数据分析:核心统计函数实现

针对导演/演员作品数量、制片地区分布等核心维度,实现统计函数,为可视化提供数据支撑。以下为三个关键函数:

演员参演电影数量统计

def get_actor_movie_count():
"""
统计参演电影数量TOP20的演员
:return: 演员名列表、对应作品数量列表
"""
# 拆分演员字段(原存储为逗号分隔字符串)
actor_series = df['casts'].str.split(',', expand=True).stack()
actor_count = actor_series.value_counts()  # 统计每个演员出现次数
top20_actors = actor_count[-20:]  # 取TOP20
# 拆分x/y轴数据
x = top20_actors.index.tolist()
y = top20_actors.values.tolist()
return x, y

导演执导电影数量统计

def get_director_movie_count():
"""
统计执导电影数量TOP20的导演
:return: 导演名列表、对应作品数量列表
"""
# 拆分导演字段(原存储为逗号分隔字符串)
director_series = df['directors'].str.split(',', expand=True).stack()
director_count = director_series.value_counts()  # 统计每个导演出现次数
top20_directors = director_count[-20:]  # 取TOP20
# 拆分x/y轴数据
x = top20_directors.index.tolist()
y = top20_directors.values.tolist()
return x, y

制片地区分布统计

def get_country_distribution():
"""
统计各制片地区的电影数量
:return: 地区列表、对应数量列表
"""
# 拆分地区字段(原存储为逗号分隔字符串)
country_series = df['country'].str.split(',', expand=True).stack()
country_count = country_series.value_counts()
# 拆分x/y轴数据
x = country_count.index.tolist()
y = country_count.values.tolist()
return x, y

这些函数返回的DataFrame可直接用于绘图。例如,统计导演作品数量后,可以筛选出前20名进行柱状图展示。

3.4 数据可视化:多维度洞察

基于上述统计结果,结合Echarts(前端交互式可视化)与Matplotlib/Seaborn(Python静态可视化)实现多维度展示:

  • 导演/演员TOP20:横向柱状图,对比数量差异。
  • 制片地区分布:饼图,展示占比。
  • 电影评分分布:直方图,分析评分区间分布。
  • 类型与评分关联:热力图,展示不同类型电影的评分均值。
  • 评论数与评分关联:散点图,分析热度与口碑的关系。

示例(Matplotlib实现导演TOP20可视化):

import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 解决中文显示问题
plt.rcParams['axes.unicode_minus'] = False
# 获取统计数据
directors, counts = get_director_movie_count()
# 绘制横向柱状图
fig, ax = plt.subplots(figsize=(12, 8))
ax.barh(directors, counts, color='#1f77b4')
ax.set_xlabel('执导电影数量')
ax.set_ylabel('导演')
ax.set_title('豆瓣电影导演执导数量TOP20')
plt.tight_layout()
plt.savefig('director_top20.png', dpi=300)
plt.show()

如果你希望实现更动态的交互效果,可以将数据导出为JSON,在前端用Echarts或D3.js渲染。对于熟悉JavaScript的开发者,这比纯Python方案更具灵活性。

[AFFILIATE_SLOT_1]

四、项目总结与拓展方向

4.1 项目亮点

  • 全流程自动化:从爬取、清洗到分析可视化,实现端到端自动化。
  • 数据标准化:统一字段格式,处理空值/异常值,保证分析准确性。
  • 多维度分析:覆盖基础信息、制作维度、观众反馈等核心维度。
  • 可视化交互性:结合Echarts实现前端交互式展示,支持自定义筛选。

4.2 可拓展方向

  • 反爬优化:加入IP代理池、请求延迟随机化,提升爬取稳定性。✅ 可参考Go的Colly框架中的限流机制。
  • 更多分析维度:新增“上映时间与评分”“演员与评分关联”等分析。
  • 可视化升级:接入Dash/Streamlit构建Web可视化平台,支持实时查询。 如果团队熟悉Java,也可用Spring Boot + Thymeleaf实现类似功能。
  • 数据挖掘:基于机器学习实现电影评分预测、类型推荐等功能。
[AFFILIATE_SLOT_2]

五、技术栈总结

技术分类核心工具 / 库用途
数据采集Requests、BeautifulSoup、re网页请求、HTML 解析、正则提取
数据处理Pandas、NumPy结构化清洗、数值计算
数据存储CSV、MySQL(pymysql)本地 / 数据库存储
可视化Matplotlib、Seaborn、Echarts静态 / 交互式可视化
辅助工具json、random数据格式化、异常值填充

六、系统运行与界面展示

数据库表信息如下:

在这里插入图片描述在这里插入图片描述

请修改为自己的数据库主机名和账号密码:

在这里插入图片描述

启动项目:

在这里插入图片描述

服务端口:5000

  • 首页:http://127.0.0.1:5000
  • 用户注册:http://127.0.0.1:5000/registry
在这里插入图片描述

用户登录:

在这里插入图片描述

首页页面展示:

在这里插入图片描述

电影数据(包括电影名、评分、片场、预告片等):

在这里插入图片描述

查看电影预告片:

在这里插入图片描述

电影搜索:

在这里插入图片描述

电影产量分析:

在这里插入图片描述

电影数据时长分布占比:

在这里插入图片描述

电影评分统计分析:

在这里插入图片描述在这里插入图片描述

豆瓣评分星级饼状图、豆瓣年度评价评分柱状图:

在这里插入图片描述

豆瓣电影中外评分分布图:

在这里插入图片描述

数据视图切换:

在这里插入图片描述在这里插入图片描述

电影拍摄地点统计图:

在这里插入图片描述

电影语言统计图:

在这里插入图片描述

电影类型饼图:

在这里插入图片描述

导演作品数量前20:

在这里插入图片描述在这里插入图片描述

数据表操作:

在这里插入图片描述

标题词云图:

在这里插入图片描述

简介词云图:

在这里插入图片描述

演员名词云图:

在这里插入图片描述

评论词云图:

在这里插入图片描述在这里插入图片描述

通过本项目,完整落地了“数据采集→处理→分析→可视化”的大数据分析流程。你不仅掌握了Python爬虫与数据分析的核心技能,也实现了对豆瓣电影数据的深度洞察。这套技术框架同样适用于其他影视平台或电商评论分析,只需调整爬虫目标与字段即可。如果对源码或商业合作感兴趣,欢迎留言交流!