☰
Python爬虫+pyecharts:影视弹幕与评分数据可视化实战
2026/9/26 14:02:58 网站建设 项目流程

简介:这份资源面向具备一定Python基础、希望进入影视数据分析与推荐系统领域的学习者与开发者,围绕影视行业数据展开从预处理、深度学习建模到可视化呈现的完整实践。包内共273个文件,以63个py脚本、38个html页面、36个pyc缓存、33张jpg与14张png图片为主,另含csv、xls数据表、bin与pth模型权重、h5与pb模型文件及css、js等前端资源,压缩包约120.38MB,结构覆盖数据、模型、可视化与Web展示多个模块。项目将FCN用于影视数据特征提取,用LSTM完成影评情感分类与票房预测,并借助漏斗图、饼图、柱状图展示策划到上映的流失、类型占比与票房差异,同时涉及协同过滤推荐思路。已有564人学习下载,适合作为课程设计或毕业项目的参考范例,帮助读者理解数据清洗、模型训练与图表输出的衔接方式,并借鉴其目录组织与排错思路。

1. 影视数据分析可视化:从弹幕和评分里挖出真东西

很多人做影视数据分析,第一步就卡在数据源上。豆瓣、猫眼、灯塔这些平台的数据要么有反爬,要么字段残缺,要么时间跨度不够。我见过太多人兴冲冲打开 Jupyter,写了两行pd.read_csv就发现手里只有一份几百条的样本,连一部热播剧的弹幕量都撑不起来。这个方向真正能落地的做法,是先把数据链路跑通——用 Python 爬虫拿到剧集元数据、短评、弹幕、播放量趋势,再用 pandas 做清洗和特征提取,最后用 pyecharts 或 ECharts 出可视化大屏。它适合两类人:一类是想拿影视数据练手 Python 数据分析与可视化的入门者,另一类是需要给内容团队做选题决策、给运营做投放复盘的一线从业者。核心链路就四步:采集、清洗、分析、可视化。每一步都有坑,但每一步也都有成熟方案。

2. 数据采集:用 Python 爬虫把影视数据拿到手

2.1 选目标站点和字段:先想清楚要回答什么问题

影视数据采集最容易犯的错,是先写爬虫再想分析目标。我一般会先列三个问题:这部剧的评分走势和弹幕情绪是否同步?哪个演员出场时弹幕密度最高?短评里的高频词和官方宣传口径差多远?这三个问题决定了你要采的字段。

以一部在播剧为例,需要采集的字段至少包括:剧集基础信息(剧名、导演、主演、开播日期、集数)、每集播放量、短评(用户名、评分、评论内容、时间)、弹幕(时间戳、内容、集数)。如果只做评分可视化,短评表就够了;如果要做弹幕情绪分析,弹幕表必须带时间戳,否则没法对齐剧情节点。

常见做法是用requests加BeautifulSoup抓静态页面,用Selenium或Playwright处理动态渲染。但影视平台的短评和弹幕大多是异步加载的,直接抓 HTML 拿不到。更稳的方式是打开浏览器开发者工具,切到 Network 面板,筛选 XHR 请求,找到返回 JSON 的接口。这个接口通常带分页参数,比如start和limit,翻页就能拿到全量数据。

注意:采集频率控制在每秒 1 到 2 次请求,加随机 User-Agent 和 Referer,不要用多线程猛冲。影视平台的风控对高频访问很敏感,一旦被封 IP,换代理的成本比慢慢采高得多。

2.2 写一个能翻页的采集脚本

下面这个脚本以短评接口为例,演示分页采集和本地落盘。实际接口地址和参数名需要你从开发者工具里自己确认,不同平台差异很大。

import requests import pandas as pd import time import random # 替换成你从 Network 面板里找到的真实接口 BASE_URL = "https://example.com/api/comments" HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://example.com/movie/12345", "Accept": "application/json", } def fetch_comments(movie_id, max_pages=50): all_comments = [] for page in range(max_pages): params = { "movie_id": movie_id, "start": page * 20, "limit": 20, "sort": "new_score", # 按最新评分排序 } try: resp = requests.get(BASE_URL, headers=HEADERS, params=params, timeout=10) resp.raise_for_status() data = resp.json() except Exception as e: print(f"第 {page} 页请求失败: {e}") break comments = data.get("data", {}).get("comments", []) if not comments: print(f"第 {page} 页无数据,采集结束") break for c in comments: all_comments.append({ "user": c.get("user", {}).get("name", ""), "rating": c.get("rating", {}).get("value", None), "content": c.get("content", ""), "time": c.get("create_time", ""), "votes": c.get("votes", 0), }) print(f"已采集第 {page} 页,累计 {len(all_comments)} 条") time.sleep(random.uniform(1.0, 2.0)) # 随机间隔,降低风控概率 return pd.DataFrame(all_comments) if __name__ == "__main__": df = fetch_comments(movie_id="12345", max_pages=30) df.to_csv("comments_raw.csv", index=False, encoding="utf-8-sig") print(f"采集完成,共 {len(df)} 条,已保存到 comments_raw.csv")

这段代码的逻辑很直白:循环翻页,每页请求间隔 1 到 2 秒随机休眠,拿到 JSON 后提取字段,最后统一存 CSV。几个关键参数需要你根据实际情况调整。max_pages控制最大翻页数,防止死循环;limit是每页条数,有些平台固定 20,有些支持 50;sort参数决定排序方式,按时间排和按热度排拿到的数据分布完全不同。timeout=10是防止某个请求卡死整个脚本。如果接口返回的不是 JSON 而是 HTML,说明你找错了请求,回到 Network 面板重新筛选。

采集完成后,先别急着分析。打开 CSV 看一眼,检查三件事:总条数是否合理、评分字段有没有大量空值、评论内容有没有被截断。我踩过一次坑,接口返回的评论内容只给了前 50 个字,完整内容在另一个字段里,结果做词频分析时全是半截句子。

3. 数据清洗与特征工程:把脏数据变成能分析的表

3.1 短评和弹幕的清洗规则

原始数据拿到手,第一件事是去重和去空。短评表里同一个用户可能重复评论,弹幕表里同一秒可能有多条重复内容。去重逻辑要看业务:短评按user + content去重,弹幕按time + content去重。空值处理上,评分字段为空的行直接丢弃,因为后续评分分布分析需要这个字段;评论内容为空但评分存在的行保留,用于统计评分。

时间字段的格式化是另一个高频翻车点。接口返回的时间可能是 Unix 时间戳、"2024-01-15"这种日期字符串,或者"3天前"这种相对时间。统一转成datetime类型,后面做时间序列分析才不会报错。

import pandas as pd import re from datetime import datetime, timedelta df = pd.read_csv("comments_raw.csv") # 去重:同一用户同一内容只保留一条 df = df.drop_duplicates(subset=["user", "content"], keep="first") # 丢弃评分为空的行 df = df.dropna(subset=["rating"]) # 评分转数值 df["rating"] = pd.to_numeric(df["rating"], errors="coerce") df = df.dropna(subset=["rating"]) # 处理相对时间 def parse_time(t): if pd.isna(t): return None t = str(t).strip() if re.match(r"^\d{4}-\d{2}-\d{2}", t): return pd.to_datetime(t) m = re.match(r"(\d+)天前", t) if m: return datetime.now() - timedelta(days=int(m.group(1))) m = re.match(r"(\d+)小时前", t) if m: return datetime.now() - timedelta(hours=int(m.group(1))) return None df["time"] = df["time"].apply(parse_time) df = df.dropna(subset=["time"]) # 评论长度作为特征 df["content_len"] = df["content"].str.len() df.to_csv("comments_clean.csv", index=False, encoding="utf-8-sig") print(df[["rating", "content_len", "time"]].describe())

清洗后的表至少包含这几列:用户、评分、评论内容、时间、点赞数、评论长度。content_len这个特征在后面做“评分与评论长度关系”分析时很有用。describe()输出能帮你快速判断数据分布是否正常,比如评分均值如果在 3 分以下,说明这部剧口碑确实不行,后续可视化会很明显。

3.2 弹幕密度和情绪特征提取

弹幕数据比短评更适合做时间序列可视化,因为每条弹幕都有精确到秒的时间戳。核心思路是按分钟或按剧情节点聚合,计算弹幕密度和情绪得分。

情绪分析可以用SnowNLP或Jieba加自定义词典。影视弹幕里有很多网络用语和缩写,通用情感词典经常误判。我一般会先跑一遍通用模型,再人工抽 200 条检查准确率,如果低于 70%,就手动补充领域词典。

import jieba from snownlp import SnowNLP import pandas as pd danmu = pd.read_csv("danmu_raw.csv") danmu["time"] = pd.to_datetime(danmu["time"]) # 按分钟聚合弹幕密度 danmu["minute"] = danmu["time"].dt.floor("min") density = danmu.groupby("minute").size().reset_index(name="count") # 情绪得分:每条弹幕算一个 0 到 1 的分数 def sentiment_score(text): if not text or len(str(text)) < 2: return None try: return SnowNLP(str(text)).sentiments except Exception: return None danmu["sentiment"] = danmu["content"].apply(sentiment_score) danmu = danmu.dropna(subset=["sentiment"]) # 按分钟聚合平均情绪 sentiment_by_min = danmu.groupby("minute")["sentiment"].mean().reset_index() # 合并密度和情绪 result = pd.merge(density, sentiment_by_min, on="minute", how="left") result.to_csv("danmu_features.csv", index=False, encoding="utf-8-sig") print(result.head(10))

这段代码产出的danmu_features.csv有两列核心指标:每分钟弹幕条数和每分钟平均情绪得分。把这两列画成双轴折线图,就能看出“哪段剧情弹幕爆发但情绪负面”——这通常是编剧翻车的地方。SnowNLP的sentiments返回 0 到 1 的分数,越接近 1 越正面。注意弹幕里大量“哈哈哈”“笑死”会被判为正面,但“刀”“虐”这类词在影视语境下是负面,通用模型可能判错,需要手动加词典修正。

4. 可视化落地:用 pyecharts 做影视数据大屏

4.1 评分分布和短评词云

评分分布用柱状图最直观,词云用WordCloud或pyecharts的WordCloud组件。先看评分分布,把 1 到 5 分各有多少条统计出来,再算一个平均分和标准差。标准差大说明口碑两极分化,这比平均分本身更有信息量。

from pyecharts import options as opts from pyecharts.charts import Bar, WordCloud, Line, Grid import pandas as pd import jieba df = pd.read_csv("comments_clean.csv") # 评分分布 rating_counts = df["rating"].value_counts().sort_index() bar = ( Bar() .add_xaxis([str(int(x)) for x in rating_counts.index]) .add_yaxis("评论数", rating_counts.values.tolist(), color="#5470c6") .set_global_opts( title_opts=opts.TitleOpts(title="评分分布"), xaxis_opts=opts.AxisOpts(name="评分"), yaxis_opts=opts.AxisOpts(name="评论数"), ) ) bar.render("rating_dist.html") # 词云:先分词再统计词频 all_text = " ".join(df["content"].dropna().astype(str).tolist()) words = jieba.lcut(all_text) stopwords = set(["的", "了", "是", "我", "你", "他", "她", "在", "就", "都", "也", "很"]) word_freq = {} for w in words: if len(w) < 2 or w in stopwords: continue word_freq[w] = word_freq.get(w, 0) + 1 top_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:100] wc = ( WordCloud() .add("", top_words, word_size_range=[12, 60]) .set_global_opts(title_opts=opts.TitleOpts(title="短评高频词")) ) wc.render("wordcloud.html")

评分分布图能直接回答“这部剧口碑到底怎么样”。如果 1 分和 5 分都很多,中间 2 到 4 分很少,说明观众态度极端,可能是粉丝和黑粉对冲。词云里如果出现大量演员名字,说明讨论集中在演员而非剧情;如果出现“节奏”“逻辑”“结局”这类词,说明观众在讨论剧本本身。stopwords列表需要根据实际结果不断补充,影视短评里“演技”“剧情”“画面”这些词几乎每部剧都有,做对比分析时可以考虑去掉。

4.2 弹幕密度与情绪双轴折线图

双轴折线图是影视数据分析里最有信息量的可视化之一。左轴是弹幕密度,右轴是情绪得分,横轴是时间。两条线一叠加,高能片段和翻车片段一目了然。

from pyecharts.charts import Line from pyecharts import options as opts import pandas as pd result = pd.read_csv("danmu_features.csv") result["minute"] = pd.to_datetime(result["minute"]) result = result.sort_values("minute") # 取前 120 分钟,避免图太密 result = result.head(120) line = ( Line() .add_xaxis([t.strftime("%H:%M") for t in result["minute"]]) .add_yaxis( "弹幕密度", result["count"].tolist(), yaxis_index=0, is_smooth=True, color="#ee6666", ) .add_yaxis( "情绪得分", [round(x, 3) for x in result["sentiment"].tolist()], yaxis_index=1, is_smooth=True, color="#5470c6", ) .extend_axis( yaxis=opts.AxisOpts( name="情绪得分", min_=0, max_=1, position="right", ) ) .set_global_opts( title_opts=opts.TitleOpts(title="弹幕密度与情绪走势"), xaxis_opts=opts.AxisOpts(name="时间"), yaxis_opts=opts.AxisOpts(name="弹幕条数"), tooltip_opts=opts.TooltipOpts(trigger="axis"), ) ) line.render("danmu_sentiment.html")

这张图怎么读:弹幕密度突然飙升的位置,通常是剧情高潮或争议点;如果同一时间情绪得分骤降,说明观众在骂;如果情绪得分维持高位,说明观众在夸。我做过一部悬疑剧的分析,第 8 集结尾弹幕密度是平均值的 5 倍,但情绪得分从 0.7 掉到 0.3,回去看弹幕内容全是“烂尾”“逻辑崩了”。这种结论直接可以给内容团队做复盘用。

提示:extend_axis必须放在add_yaxis之后,否则右轴不显示。这是 pyecharts 的一个常见坑,很多人第一次用双轴图都会卡在这里。

5. 避坑与排查:影视数据分析里最容易翻车的 5 个地方

5.1 采集阶段:接口返回 200 但数据为空

现象:脚本没报错,resp.status_code是 200,但data["comments"]是空列表。原因通常是请求头缺少关键字段,比如Cookie或X-Requested-With,或者分页参数从 0 开始而不是从 1 开始。解决方法是回到浏览器开发者工具,把请求的完整 headers 复制下来,逐个对比。另一个常见原因是接口有签名参数,比如sign或token,这种需要逆向 JS 或者用 Playwright 直接拦截响应。

5.2 清洗阶段:时间字段格式不统一导致to_datetime报错

现象:pd.to_datetime(df["time"])抛出ValueError: Unknown string format。原因是同一列里混了多种时间格式,比如一部分是"2024-01-15",另一部分是"3天前"。解决办法是用errors="coerce"先强制转换,把无法解析的变成NaT,再单独处理相对时间。不要试图用一个正则匹配所有格式,分情况写函数更稳。

5.3 分析阶段:SnowNLP 对影视弹幕情绪误判严重

现象:明显是骂人的弹幕被判成正面情绪。原因是 SnowNLP 的训练语料是电商评论,和影视弹幕的用词分布差异很大。解决办法是先用 200 条人工标注数据测准确率,如果低于 70%,就换BERT微调或者手动构建领域情感词典。我一般会维护一个影视领域词典,把“刀”“虐”“寄”“烂尾”标为负面,“封神”“绝了”“上头”标为正面。

5.4 可视化阶段:pyecharts 图表在浏览器打开空白

现象:render()生成了 HTML 文件,但浏览器打开是白屏。原因通常是 CDN 资源加载失败,pyecharts 默认从外部 CDN 拉取 JS 库。解决办法是在set_global_opts里指定init_opts=opts.InitOpts(js_host="本地路径"),或者直接用pyecharts的离线模式。另一个原因是数据里有NaN,JSON 序列化失败,检查传给add_yaxis的列表里有没有空值。

5.5 部署阶段:数据量大了之后内存爆掉

现象:本地跑 1 万条数据没问题,跑到 100 万条时MemoryError。原因是 pandas 默认用object类型存字符串,内存占用是实际内容的几倍。解决办法是读取时指定dtype,把低基数列转成category类型,比如df["rating"] = df["rating"].astype("category")。弹幕内容如果不需要全文检索,可以只保留前 100 个字符。分批处理也是常见做法,用chunksize参数分块读取和聚合。

6. 进阶技巧:用 PyQt 做一个本地影视数据看板

如果你不想每次分析都开 Jupyter,可以用 PyQt 加 pyecharts 做一个本地桌面看板。核心思路是用QWebEngineView加载 pyecharts 生成的 HTML,再用QComboBox切换不同剧集的数据。这样运营同学不用装 Python 环境,双击 exe 就能看。

import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QComboBox, QVBoxLayout, QWidget from PyQt5.QtWebEngineWidgets import QWebEngineView import pandas as pd from pyecharts.charts import Bar from pyecharts import options as opts class Dashboard(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("影视数据看板") self.resize(1200, 800) self.combo = QComboBox() self.combo.addItems(["剧集A", "剧集B", "剧集C"]) self.combo.currentTextChanged.connect(self.update_chart) self.view = QWebEngineView() self.view.setHtml(self.build_chart("剧集A")) layout = QVBoxLayout() layout.addWidget(self.combo) layout.addWidget(self.view) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) def build_chart(self, name): # 这里替换成真实数据查询 data = {"1分": 120, "2分": 80, "3分": 200, "4分": 500, "5分": 800} bar = ( Bar() .add_xaxis(list(data.keys())) .add_yaxis(name, list(data.values())) .set_global_opts(title_opts=opts.TitleOpts(title=f"{name} 评分分布")) ) return bar.render_embed() def update_chart(self, name): self.view.setHtml(self.build_chart(name)) if __name__ == "__main__": app = QApplication(sys.argv) window = Dashboard() window.show() sys.exit(app.exec_())

这个看板的骨架很简单:下拉框切换剧集,QWebEngineView渲染图表。build_chart里目前是硬编码数据,实际使用时换成从 SQLite 或 CSV 读取。render_embed()返回完整的 HTML 字符串,直接塞给setHtml就行。打包成 exe 用pyinstaller,注意把 pyecharts 的 JS 资源一起打进去,否则换台电脑打开还是白屏。

我自己的习惯是,每做完一个剧集的分析,就把清洗后的 CSV 和生成的 HTML 图表归档到一个按剧名命名的文件夹里。三个月后回头看,哪些剧的弹幕情绪曲线和评分走势背离,哪些剧的短评高频词集中在演员而非剧情,一目了然。这个习惯帮我省了很多重复采集的时间,也让每次复盘都有据可查。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询