那几天朋友圈和群里讨论最多的话题就是 AL 和 JDG 那场打满三局的比赛。比分定格在 1:1 时所有人都知道真正的决胜局还没开始但虎扑上已经炸开了锅。有人发帖分析阵容有人争论 MVP 归属还有人直接开喷 BP。作为一名经常写爬虫和数据可视化的开发者我第一反应不是去评论区对线而是想能不能把虎扑上这几千条帖子爬下来看看赛后社区情绪到底是怎么变化的。这个需求听起来很“电竞”但本质上是一个标准的舆情分析小项目抓取网页数据、清洗文本、做情感判断、最后可视化。不管你是想分析虎扑、贴吧、微博还是知乎技术路径完全一致。本文就以“AL 1:1 JDG 后的虎扑现状”为案例完整拆解从爬虫到情感分析再到可视化的全过程。文章会给到可以直接复制的代码、需要避开的坑、以及对“赛后社区情绪”这一模糊概念的技术化解读。1. 背景与核心概念1.1 什么是虎扑赛后社区现状虎扑是国内活跃度很高的体育论坛尤其是电竞板块比赛结束后几十秒内就会出现大量新帖。所谓“赛后虎扑现状”指的不是某一句话而是这个社区在比赛某个阶段爆发出的整体讨论倾向是夸选手操作还是骂教练 BP又或者是站队吵架。这种现状无法靠人工一条条翻帖子去“感受”因为信息量太大。更科学的方式是用程序把帖子标题、正文、回复数、时间等字段批量抓下来然后对文本做情感倾向判断再统计正面和负面内容的比例分布。这其实就是文本挖掘和舆情分析在体育社区场景下的落地。1.2 技术方案选型整个项目涉及三个核心环节数据采集通过 HTTP 请求获取虎扑帖子列表页和详情页内容。文本清洗与情感分析去除无关字符使用情感分析模型判断每句话的情绪极性。数据可视化把情感分布、高频词、时间趋势等数据绘制成图表。技术栈我选择 Python它在这三个领域都有非常成熟的库。爬虫用requestsBeautifulSoup如果遇到动态渲染的页面可以升级为Selenium情感分析用SnowNLP虽然是针对电商评论训练的但在社区文本上也有一定参考价值可视化使用pyecharts或matplotlib可以快速生成交互式图表。1.3 为什么用情感分析而不是直接读帖子很多同学会问帖子内容我能看懂为什么非要跑模型原因有三个。第一数据量大。一场比赛的赛后讨论可能有上千条帖子人工阅读成本高且容易疲劳导致误判。第二标准一致。每个人对“正面”“负面”的判定标准不同而情感分析模型对所有文本采用同一套打分逻辑结果可横向对比。第三可以量化。情感分析输出的分数是连续的比如 0.1 到 0.9我们可以求均值、画分布甚至可以和比赛时间线做关联这是人工阅读做不到的。2. 环境准备与版本说明本文的示例代码在以下环境中验证通过项目环境操作系统Windows 11 / Ubuntu 22.04Python 版本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 classlist-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(stripTrue) href a_tag.get(href) # 作者 author_tag item.find(span, class_author) author author_tag.get_text(stripTrue) if author_tag else 未知 # 回复数 reply_tag item.find(span, class_reply-count) reply_count reply_tag.get_text(stripTrue) if reply_tag else 0 posts.append({ title: title, href: href, author: author, reply_count: reply_count }) return posts这段代码的核心是BeautifulSoup的find_all和find方法。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.txtcrawler.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/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, } def fetch_html(url): 请求网页返回 HTML 字符串 try: resp requests.get(url, headersHEADERS, timeout5) 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(stripTrue) href a_tag.get(href) author_tag item.find(span, class_author) author author_tag.get_text(stripTrue) 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, modew, encodingutf-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, indexFalse, encodingutf-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.pyimport 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_optsopts.TitleOpts(titleAL 1:1 JDG 后虎扑帖子情感分布)) .set_series_opts(label_optsopts.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_optsopts.TitleOpts(title赛后帖子高频词 TOP20), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30)) ) ) 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保存时使用encodingutf-8-sig情感分析结果全是 0.5输入文本为空或清洗过度检查清洗函数保留有效中文高频词全是“的”“了”“是”没有过滤停用词引入停用词表去掉单字符和虚词图表无法打开浏览器安全策略或未找到 HTML 文件使用现代浏览器直接打开本地 HTML其中情感分析结果全是 0.5 是最容易被忽视的问题。SnowNLP对空白字符串会返回默认的情感值所以如果大量帖子标题是英文缩写如“AL”“JDG”清洗时可能把字母全删掉变成空字符串最后全归为中性。解决办法是在清洗时保留英文字母。还有一个常见坑是虎扑的评论区数据是流式加载的列表页可能只返回前几页。此时需要手动分析翻页参数循环请求多个页面。def fetch_all_pages(base_url, total_pages5): 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是一个基于朴素贝叶斯的轻量级模型它在电商评论上有不错的表现但在电竞社区场景下并不完美。比如“这波操作真的秀”和“这波操作真是秀儿”在人类看来是不同态度但模型可能都判为正面。要提升专业场景下的准确率有两条路建立领域专属的积极/消极词表对情感分数进行规则修正。使用大型预训练模型如 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但虎扑社区的情绪峰值到底出现在哪个时间点、哪些关键词在推动讨论走向这些问题的答案就藏在数据里。