news 2026/9/6 9:04:01

虎扑赛后舆情分析实战:Python爬虫与情感分析可视化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
虎扑赛后舆情分析实战:Python爬虫与情感分析可视化

那几天朋友圈和群里讨论最多的话题,就是 AL 和 JDG 那场打满三局的比赛。比分定格在 1:1 时,所有人都知道真正的决胜局还没开始,但虎扑上已经炸开了锅。有人发帖分析阵容,有人争论 MVP 归属,还有人直接开喷 BP。作为一名经常写爬虫和数据可视化的开发者,我第一反应不是去评论区对线,而是想能不能把虎扑上这几千条帖子爬下来,看看赛后社区情绪到底是怎么变化的。

这个需求听起来很“电竞”,但本质上是一个标准的舆情分析小项目:抓取网页数据、清洗文本、做情感判断、最后可视化。不管你是想分析虎扑、贴吧、微博还是知乎,技术路径完全一致。本文就以“AL 1:1 JDG 后的虎扑现状”为案例,完整拆解从爬虫到情感分析再到可视化的全过程。文章会给到可以直接复制的代码、需要避开的坑、以及对“赛后社区情绪”这一模糊概念的技术化解读。


1. 背景与核心概念

1.1 什么是虎扑赛后社区现状

虎扑是国内活跃度很高的体育论坛,尤其是电竞板块,比赛结束后几十秒内就会出现大量新帖。所谓“赛后虎扑现状”,指的不是某一句话,而是这个社区在比赛某个阶段爆发出的整体讨论倾向:是夸选手操作,还是骂教练 BP,又或者是站队吵架。

这种现状无法靠人工一条条翻帖子去“感受”,因为信息量太大。更科学的方式是用程序把帖子标题、正文、回复数、时间等字段批量抓下来,然后对文本做情感倾向判断,再统计正面和负面内容的比例分布。这其实就是文本挖掘和舆情分析在体育社区场景下的落地。

1.2 技术方案选型

整个项目涉及三个核心环节:

  1. 数据采集:通过 HTTP 请求获取虎扑帖子列表页和详情页内容。
  2. 文本清洗与情感分析:去除无关字符,使用情感分析模型判断每句话的情绪极性。
  3. 数据可视化:把情感分布、高频词、时间趋势等数据绘制成图表。

技术栈我选择 Python,它在这三个领域都有非常成熟的库。爬虫用requests+BeautifulSoup,如果遇到动态渲染的页面,可以升级为Selenium;情感分析用SnowNLP,虽然是针对电商评论训练的,但在社区文本上也有一定参考价值;可视化使用pyechartsmatplotlib,可以快速生成交互式图表。

1.3 为什么用情感分析而不是直接读帖子

很多同学会问:帖子内容我能看懂,为什么非要跑模型?原因有三个。

第一,数据量大。一场比赛的赛后讨论可能有上千条帖子,人工阅读成本高,且容易疲劳导致误判。第二,标准一致。每个人对“正面”“负面”的判定标准不同,而情感分析模型对所有文本采用同一套打分逻辑,结果可横向对比。第三,可以量化。情感分析输出的分数是连续的,比如 0.1 到 0.9,我们可以求均值、画分布,甚至可以和比赛时间线做关联,这是人工阅读做不到的。


2. 环境准备与版本说明

本文的示例代码在以下环境中验证通过:

项目环境
操作系统Windows 11 / Ubuntu 22.04
Python 版本3.9+
开发工具PyCharm 或 VS Code
核心库requests、beautifulsoup4、pandas、snownlp、pyecharts

版本不需要严格一致,思路是通用的。如果你使用 Python 3.12,只要库能正常安装即可。

安装命令如下:

pip install requests beautifulsoup4 pandas snownlp pyecharts

如果你的网络环境比较特殊,可以加上国内镜像源:

pip install requests beautifulsoup4 pandas snownlp pyecharts -i https://pypi.tuna.tsinghua.edu.cn/simple

需要注意,SnowNLP自带的情感模型是基于电商评论语料训练的,对电竞黑话和网络梗的识别能力有限。后面我们会通过自定义积极词和消极词来微调判断逻辑。


3. 核心思路拆解:爬虫与情感分析的关键点

3.1 虎扑帖子列表的 URL 结构

虎扑电竞讨论区有专门的板块,不同比赛会有集中讨论帖。正常情况下,帖子列表页的 URL 是有规律的,例如:

https://bbs.hupu.com/xxxx

由于虎扑页面结构会不定期调整,不建议把具体链接写死。更好的做法是打开虎扑电竞赛事页面,找到你想要分析的讨论区,手动复制列表页 URL。本文示例中,我以“AL vs JDG”讨论串为例,模拟一个帖子列表接口,重点讲解解析逻辑。

注意:本节代码仅用于学习技术原理,请遵守目标网站的robots.txt协议,控制爬取频率,不要对服务器造成压力。

3.2 列表页解析:提取帖子标题与链接

假设我们已经拿到了列表页的 HTML 内容,接下来要提取所有帖子的标题、链接、作者和回复数。

虎扑帖子列表通常以<div class="list-item"><li>标签为单位,标题在<a>标签内。不同时间段的页面结构不同,但核心思路一致:找到包含帖子信息的容器,再逐层提取字段。

下面是一个兼容性较好的解析函数:

import requests from bs4 import BeautifulSoup def parse_post_list(html): """ 从帖子列表页 HTML 中解析出帖子信息 """ soup = BeautifulSoup(html, 'html.parser') posts = [] for item in soup.find_all('div', class_='list-item'): # 标题和链接 a_tag = item.find('a', class_='post-title') if not a_tag: continue title = a_tag.get_text(strip=True) href = a_tag.get('href') # 作者 author_tag = item.find('span', class_='author') author = author_tag.get_text(strip=True) if author_tag else '未知' # 回复数 reply_tag = item.find('span', class_='reply-count') reply_count = reply_tag.get_text(strip=True) if reply_tag else '0' posts.append({ 'title': title, 'href': href, 'author': author, 'reply_count': reply_count }) return posts

这段代码的核心是BeautifulSoupfind_allfind方法。find_all负责定位所有帖子容器,find从每个容器中再提取具体字段。class_参数用于查找指定 class 的元素,是 BeautifulSoup 的固定写法。

如果目标页面没有匹配到数据,可能是因为 class 名变化了。这时建议使用浏览器开发者工具,选中一个帖子元素,查看其真实 HTML 结构,再调整上面的 class 名。

3.3 分词与停用词过滤

获得帖子文本后,不能直接做情感分析。中文句子需要先切分成词,同时过滤掉“的”“了”“是”等没有实际意义的停用词。

SnowNLP内部自带中文分词功能,我们直接用就行。但为了提升准确率,我们要额外处理两类词:

  • 电竞黑话:比如“GG”“操作拉满”“下饭”“白给”,这些词在通用分词里可能被切碎。
  • 自定义积极/消极词:SnowNLP 默认词表不包含这些词汇。

做法是在情感分析前,把文本中的电竞黑话替换成普通表达。例如:

slang_map = { '下饭': '菜', '白给': '失误', '封神': '优秀', 'GG': '结束', '天秀': '优秀', } def clean_text(text): for k, v in slang_map.items(): text = text.replace(k, v) return text

这个替换逻辑简单但有效。替换后再调用 SnowNLP,情感判断的稳定性会更好。

3.4 情感分数计算与正负判定

SnowNLP 的情感得分范围是 0 到 1,越接近 1 表示越积极,越接近 0 表示越消极。常见的划分阈值是:

  • 分数 >= 0.6:正面
  • 分数 <= 0.4:负面
  • 分数在 0.4 到 0.6 之间:中性

但这个阈值不是绝对的。如果你是针对特定领域的情感分析,建议先人工标注 50 条数据,看看模型分数分布,再选择合适的阈值。

计算单条文本情感分数的函数:

from snownlp import SnowNLP def get_sentiment_score(text): # 先清洗再分析 cleaned = clean_text(text) if not cleaned: return 0.5 s = SnowNLP(cleaned) return s.sentiments # 0~1

注意,SnowNLP在遇到过长文本时速度会变慢,效率是一个问题。如果只是几千条帖子,完全没问题;如果数据量到了几十万条,建议换成向量化方案(如 TF-IDF + 逻辑回归),不过这属于进阶内容了。


4. 完整实战:AL 1:1 JDG 赛后虎扑舆情分析

下面我们完成一个从采集到可视化的小型系统。为了便于理解和复现,我把整个流程拆成五个步骤。代码中所有 URL 均为模拟地址,你需要替换成实际可访问的虎扑页面 URL。

4.1 创建项目结构

在本地新建一个项目文件夹,目录结构如下:

hupu_sentiment/ ├── data/ │ ├── raw_posts.csv │ └── posts_with_sentiment.csv ├── crawler.py ├── sentiment.py ├── visualize.py └── requirements.txt
  • crawler.py:负责请求网页和解析帖子列表。
  • sentiment.py:负责文本清洗和情感分析。
  • visualize.py:负责绘制图表。
  • data/目录用于存放中间数据和最终结果。

4.2 编写爬虫代码

创建crawler.py文件,内容如下:

import requests import time import csv from bs4 import BeautifulSoup HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9', } def fetch_html(url): """请求网页,返回 HTML 字符串""" try: resp = requests.get(url, headers=HEADERS, timeout=5) resp.raise_for_status() resp.encoding = resp.apparent_encoding return resp.text except requests.RequestException as e: print(f'请求失败: {e}') return '' def parse_post_list(html): """解析帖子列表,返回帖子列表""" soup = BeautifulSoup(html, 'html.parser') posts = [] # 注意:这里的选择器需要根据实际页面结构调整 for item in soup.find_all('div', class_='list-item'): a_tag = item.find('a', class_='post-title') if not a_tag: continue title = a_tag.get_text(strip=True) href = a_tag.get('href') author_tag = item.find('span', class_='author') author = author_tag.get_text(strip=True) if author_tag else '未知' posts.append({ 'title': title, 'href': href, 'author': author }) return posts def save_posts_to_csv(posts, filename): """保存帖子列表到 CSV""" with open(filename, mode='w', encoding='utf-8-sig', newline='') as f: writer = csv.DictWriter(f, fieldnames=['title', 'href', 'author']) writer.writeheader() writer.writerows(posts) print(f'已保存 {len(posts)} 条帖子到 {filename}') if __name__ == '__main__': # 示例 URL,实际使用时请替换为目标讨论区 URL target_url = 'https://bbs.hupu.com/example-al-vs-jdg' page_html = fetch_html(target_url) if page_html: post_list = parse_post_list(page_html) save_posts_to_csv(post_list, 'data/raw_posts.csv') else: print('未获取到页面内容,请检查网络和 URL')

在爬虫代码中,有两点必须注意:

  • 每次都带上一个标准User-Agent,否则一些反爬机制可能直接拒绝请求。
  • 保存 CSV 时使用utf-8-sig编码,这样用 Excel 打开不会出现乱码。

实际运行时,如果页面做了 JS 渲染,requests只能拿到空的数据。此时可以用Selenium驱动浏览器模拟滚动加载,但会更加消耗资源。建议优先看网页源码里是否直接包含数据,再决定要不要上浏览器模拟。

4.3 编写情感分析代码

接下来创建sentiment.py,读取上一步保存的raw_posts.csv,逐条做情感分析。

import pandas as pd from snownlp import SnowNLP import re # 电竞常用词映射,用于提升情感分析准确率 slang_map = { '下饭': '菜', '白给': '失误', '封神': '优秀', 'GG': '结束', '天秀': '优秀', '拉满': '非常好', '梦游': '差', } def clean_text(text): """清洗文本:去空、去特殊符号、替换电竞黑话""" if not isinstance(text, str): return '' text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s]', '', text) for k, v in slang_map.items(): text = text.replace(k, v) return text.strip() def get_sentiment_score(text): """返回情感分数,范围 0~1,越接近 0 越消极,越接近 1 越积极""" cleaned = clean_text(text) if not cleaned: return 0.5 s = SnowNLP(cleaned) return s.sentiments def label_sentiment(score): """根据分数打标签""" if score >= 0.6: return '正面' elif score <= 0.4: return '负面' else: return '中性' def main(): # 读取帖子列表 df = pd.read_csv('data/raw_posts.csv') print(f'共读取 {len(df)} 条帖子') # 逐条计算情感分数 df['clean_title'] = df['title'].apply(clean_text) df['score'] = df['clean_title'].apply(get_sentiment_score) df['sentiment'] = df['score'].apply(label_sentiment) # 保存结果 df.to_csv('data/posts_with_sentiment.csv', index=False, encoding='utf-8-sig') # 打印简单统计 print(df['sentiment'].value_counts()) if __name__ == '__main__': main()

这里我们只对标题做了情感分析。如果你希望分析帖子正文,就需要额外写一个函数抓取帖子详情页的正文内容,然后把正文拼接到 DataFrame 中再分析。本文为了控制篇幅,以帖子标题作为情感判断的样本,但完整项目应该是“标题 + 正文”一起分析。

4.4 运行与验证

打开终端,依次执行以下命令:

# 第一步:爬虫 python crawler.py # 第二步:情感分析 python sentiment.py

正常情况下,第一步会在data目录生成raw_posts.csv,第二步会生成posts_with_sentiment.csv,并在终端输出类似下面的统计结果:

正面 45 负面 30 中性 25

如果你的输出全部集中在“中性”,大概率是情感分析的输入文本太少或者被清洗得只剩下空字符串。可以检查clean_title列,看看有没有内容。

4.5 可视化呈现虎扑现状

最后我们使用pyecharts绘制三个图表:情感分布饼图、高频词柱状图、帖子时间趋势折线图。这里重点展示情感分布饼图和高频词统计。

创建visualize.py

import pandas as pd from pyecharts.charts import Pie, Bar from pyecharts import options as opts from pyecharts.commons.utils import JsCode # 读取情感分析结果 df = pd.read_csv('data/posts_with_sentiment.csv') # 1. 情感分布饼图 sentiment_counts = df['sentiment'].value_counts() pie = ( Pie() .add('', [list(z) for z in zip(sentiment_counts.index, sentiment_counts.values)]) .set_global_opts(title_opts=opts.TitleOpts(title='AL 1:1 JDG 后虎扑帖子情感分布')) .set_series_opts(label_opts=opts.LabelOpts(formatter='{b}: {c} ({d}%)')) ) pie.render('情感分布饼图.html') # 2. 高频词统计(简易版) from collections import Counter def tokenize(text): # 简单按空格切分,实际项目中建议用 jieba 分词 return text.split() word_list = [] for content in df['clean_title'].dropna(): word_list.extend(tokenize(content)) counter = Counter(word_list) top_words = counter.most_common(20) bar = ( Bar() .add_xaxis([w for w, c in top_words]) .add_yaxis('出现次数', [c for w, c in top_words]) .set_global_opts( title_opts=opts.TitleOpts(title='赛后帖子高频词 TOP20'), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=30)) ) ) bar.render('高频词柱状图.html') print('图表已生成,请打开 HTML 文件查看')

这里的高频词统计是简化版,直接按空格切分。实际中文文本需要先使用jieba分词,然后过滤停用词,否则统计出来的词会很不连贯。

# 如果要使用 jieba,先安装:pip install jieba import jieba def tokenize_by_jieba(text): return [word for word in jieba.cut(text) if len(word) > 1]

visualize.py中的tokenize函数替换为tokenize_by_jieba,再过滤掉停用词表,就能得到更有意义的高频词。


5. 常见问题与排查思路

这个项目虽然看起来简单,但在实际开发中很容易遇到各种问题。下面是一个高频问题汇总。

问题现象常见原因解决思路
requests请求返回 403缺少User-Agent或被反爬识别设置完整的浏览器请求头,降低请求频率
页面内容为空网站用了 JS 动态渲染使用 Selenium 或 Playwright 模拟浏览器
解析不到帖子数据页面 class 名发生变化用浏览器开发者工具重新定位 HTML 节点
CSV 中文乱码编码不是 UTF-8 或缺少 BOM保存时使用encoding='utf-8-sig'
情感分析结果全是 0.5输入文本为空或清洗过度检查清洗函数,保留有效中文
高频词全是“的”“了”“是”没有过滤停用词引入停用词表,去掉单字符和虚词
图表无法打开浏览器安全策略或未找到 HTML 文件使用现代浏览器直接打开本地 HTML

其中情感分析结果全是 0.5 是最容易被忽视的问题。SnowNLP对空白字符串会返回默认的情感值,所以如果大量帖子标题是英文缩写(如“AL”“JDG”),清洗时可能把字母全删掉,变成空字符串,最后全归为中性。解决办法是在清洗时保留英文字母。

还有一个常见坑是虎扑的评论区数据是流式加载的,列表页可能只返回前几页。此时需要手动分析翻页参数,循环请求多个页面。

def fetch_all_pages(base_url, total_pages=5): all_posts = [] for page in range(1, total_pages + 1): if page == 1: url = base_url else: url = f'{base_url}?page={page}' html = fetch_html(url) posts = parse_post_list(html) all_posts.extend(posts) print(f'第 {page} 页,已获取 {len(posts)} 条帖子') time.sleep(1) # 礼貌间隔 return all_posts

这里的total_pages可以根据实际帖子数量调整。注意,每页请求之间最好加time.sleep(1),避免被网站封禁。


6. 最佳实践与工程建议

6.1 爬虫合规性

虎扑的抓取行为应当在法律允许的范围内进行。建议只抓取公开可见的数据,不涉及用户隐私;控制并发和请求频率,避免影响正常访问;数据分析结果不要用于商业用途。如果只是学习爬虫和文本分析,建议把目标页面的数据量控制在较小范围。

6.2 情感分析模型的局限性

SnowNLP是一个基于朴素贝叶斯的轻量级模型,它在电商评论上有不错的表现,但在电竞社区场景下并不完美。比如“这波操作真的秀”和“这波操作真是秀儿”在人类看来是不同态度,但模型可能都判为正面。要提升专业场景下的准确率,有两条路:

  1. 建立领域专属的积极/消极词表,对情感分数进行规则修正。
  2. 使用大型预训练模型(如 BERT),但需要更多标注数据和计算资源。

对个人学习项目来说,SnowNLP已经足够用来理解情感分析的整体流程。

6.3 代码模块化

不要把爬虫、清洗、分析、可视化全部写在一个文件里。每个步骤独立成模块,后续维护和替换算法都会方便很多。比如今天你用的是SnowNLP,明天想换成BERT,只需要修改sentiment.py的核心函数,不影响其他模块。

6.4 数据备份与可复现

每次爬取的数据都加上时间戳,例如raw_posts_20250101.csv,这样你就可以回溯不同时间节点的社区情绪变化。分析脚本也要保持输入输出可控,这样以后有新的比赛,直接把 URL 替换掉就能重新跑一遍全流程。

6.5 可视化交互的意义

静态图表只能告诉我们结果,交互式图表才能让读者自己筛选、缩放。pyecharts生成的 HTML 文件自带交互功能,可以嵌入到网页中,也可以本地打开。如果你有前端基础,还可以通过Flask搭建一个小型服务,把爬虫和分析工具做成一个在线的赛后舆情分析页面。


7. 总结与后续扩展方向

通过这个AL 1:1 JDG 后虎扑现状的项目,完整走通了“网页请求 -> HTML 解析 -> 文本清洗 -> 情感分析 -> 可视化”这条技术链路。哪怕你不是电竞粉丝,只要理解这个流程,就可以把同样的方法套用到任何论坛或社交媒体的话题分析上。

如果你打算继续深入,可以考虑下面几个方向:

  • 接入实时数据:用定时任务每五分钟抓一次新帖,绘制情绪随时间变化的实时折线图。
  • 加入评论分析:帖子标题的情感分数只是片面信息,把热门评论也纳入分析,能更真实地反映社区态度。
  • 优化情感模型:标注一批电竞领域数据,训练一个专用分类器,取代默认的SnowNLP
  • 搭建完整报告页面:用 Flask 或 FastAPI 把爬虫、分析、图表包装成一个 Web 应用,以后每次比赛结束后自动生成一份可视化报告。

技术工具的最终价值,是把模糊的感受变成可量化、可视化、可验证的数据。这场比赛是 1:1,但虎扑社区的情绪峰值到底出现在哪个时间点、哪些关键词在推动讨论走向,这些问题的答案,就藏在数据里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 9:00:59

从零实现AI Agent:任务规划、工具调用与Manus架构拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 9:00:08

OpenHarmony硬件调试三板斧:串口日志、HDC与设备树排查实战

干了这几年OpenHarmony开发&#xff0c;我越来越觉得硬件调试这东西&#xff0c;真不是看多少文档就能会的。文档写得再全&#xff0c;到了真机上跑不起来&#xff0c;板子一片黑&#xff0c;串口一个字都不吐&#xff0c;那感觉&#xff0c;干过的人都懂。我自己从RK3399一路折…

作者头像 李华
网站建设 2026/9/6 9:00:02

构建内容安全审核系统:文本分类与机器学习实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 8:59:26

小智聊天机器人接入W55MH32:离线语音播报与在线对话协同方案

W55MH32这个名字&#xff0c;玩过语音播报DIY的朋友应该不陌生&#xff0c;一块几块钱的MP3解码模块&#xff0c;带串口、带功放、插上喇叭就能响。最近我在折腾开源的小智聊天机器人&#xff0c;发现这两样东西放一起非常搭&#xff1a;一个负责“动脑”对话&#xff0c;一个负…

作者头像 李华
网站建设 2026/9/6 8:59:23

ESP32-S3模组W55MH32实战:从零搭建小智AI语音助手

W55MH32这块板子我前后折腾了将近两个星期&#xff0c;中间吃了不少亏&#xff0c;但最后总算把小智聊天机器人完整跑了起来。如果你正准备做类似的AI语音助手硬件项目&#xff0c;这篇文章应该能帮你少走很多弯路。 先说结论&#xff1a;W55MH32本质上是一块基于乐鑫ESP32-S3…

作者头像 李华