B站评论爬取与中文分词可视化全攻略:从API到词云图

发布时间:2026/10/17 5:00:35
B站评论爬取与中文分词可视化全攻略:从API到词云图 做爬虫的人十有八九都干过这么一件事在某个视频底下翻了几百条评论把自己看笑了然后突然冒出一个想法——这些弹幕一样的吐槽到底都在说啥与其一条条翻不如写个脚本直接把评论全拉下来用中文分词拆开再统计一下高频词最后画个词云图挂出来。这个项目标题看起来有点长其实把整条链路拆开就是三件事用request把B站视频评论爬下来用jieba把评论里的中文切成词再用pyecharts把高频词做成图表。这活儿不难工作量也不大特别适合练手。能把爬虫请求、数据清洗、中文分词、可视化展示这几个Python常用技能串起来做完会很有成就感而且可玩性很强——换个视频、换个关键词结果就完全不一样。这篇文章我先讲清楚整体设计思路然后把从API分析、评论抓取、分词统计到可视化出图的每一步都拆开来讲最后再聊聊我实际踩过的几个坑方便你直接照着做。1. 整体设计与方案选型1.1 为什么直接走B站评论区API而不是页面解析先解决一个核心问题评论数据从哪来如果你第一反应是requests.get(视频页面URL)然后用正则或者某种解析库去页面里抠评论那我建议你赶紧停住。B站视频页是典型的动态渲染页面评论内容是在页面加载之后通过异步接口请求灌进来的直接抓HTML根本拿不到几条而且页面结构说改就改今天能用的正则明天就废。更稳的做法是直接找B站评论区背后的接口。B站评论接口是标准的HTTP JSON接口返回字段结构清晰、数据完整评论内容、点赞数、回复时间、用户ID全都有。用request直接请求这个接口设定好分页参数一行一页地翻就能把整个视频下的热门评论全拉下来。这也是最贴近真实工作场景的做法——动态站点优先找接口别死磕静态页面解析。1.2 技术栈分工与数据流向这项目的技术栈很简单四个库搞定所有事模块工具负责的环节数据采集requests请求评论接口、处理分页、模拟浏览器请求头数据清洗Python标准库rejson提取评论文本、去除杂质、去重中文分词jieba将长评按词切分附加载停用词表过滤无意义词汇词频统计Python标准库collections.Counter统计每个词的出现次数取出TopN可视化pyecharts生成动态HTML图表词云、柱状图数据流动方向非常清晰HTTP响应 → JSON解析 → 评论Text字段 → 数据清洗 → jieba分词 → 过滤停用词 → Counter计数 → pyecharts出图。中间每个环节都是标准数据处理流程哪一步出问题都能单独排查。技术上没有难啃的硬骨头。爬虫部分难点主要是接口参数分析和请求头伪装分词部分难点在停用词表质量这直接决定词频结果有没有参考价值可视化部分几乎没有任何难点照着文档填参数就行。2. 环境准备与依赖安装2.1 Python版本与依赖清单我建议直接用Python 3.8以上版本requests、jieba、pyecharts这三个库都跟进最新版就行。安装命令很简单pip install requests jieba pyecharts如果你的环境是Anaconda也可以用conda装但PyPI源装这三个库通常是最省事的。版本有坑的话留意一点pyechartsv1.x之后的API和v0.5.x完全不兼容网上很多老教程是基于0.5.x写的照着复制代码直接报错。安装时直接装最新版1.x写代码时确认导入方式是from pyecharts.charts import WordCloud不是from pyecharts import WordCloud。2.2 代理与请求头准备访问B站接口直接裸奔请求大概率被拦。你需要在代码里先准备一个相对完整的请求头至少包括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, Referer: https://www.bilibili.com/, Origin: https://www.bilibili.com, }如果只是少量爬取一页20条翻三五页这个请求头基本够用。如果计划翻几十页甚至上百页建议加一个time.sleep(1~2)控制频率。另外“能不能爬到”还取决于评论接口本身的限制这个后面单独讲。3. 评论数据采集从API分析到翻页抓取3.1 定位评论接口并构造请求打开任意一个B站视频页面按F12打开开发者工具切到“网络”面板然后往下手动翻几条评论观察多出来的XHR请求。你会发现一个类似https://api.bilibili.com/x/v2/reply/main的接口这就是当前视频的评论主接口。这个接口的请求参数有几个关键的参数名含义备注type评论类型固定1oid视频的av号ID不是BV号是数字IDmode排序方式3按热门排序2按时间排序next分页游标第1页是0之后的从响应JSON的data.cursor.next取其中最容易坑新人的是oid。很多人在地址栏复制的链接是BV1xx411c7mD这种BV号但接口里只认oid数字ID。解决办法有两个一是直接从网页源码或接口请求里找aid二是用一个简单的转换函数分支处理BV号转数字ID。最省事的是先打开视频页在开发者工具里找到任意一个请求搜索aid或者oid字段直接复制。或者按照B站公开规则在页面的window.__INITIAL_STATE__里能直接看到aid。新手不用太纠结只要抓到一个视频的数字ID代码逻辑一样跑。3.2 评论接口的响应结构与翻页规则reply/main接口返回的JSON长这样{ code: 0, data: { cursor: { next: 1, prev: 0 }, replies: [ { content: { message: 这条评论的内容 }, like: 666, ctime: 1700000000, member: { uname: 某用户 } } ] } }注意看第一页请求的next设为0就能拿到第一页数据而响应里的cursor.next告诉你下一页应该传什么值这叫游标分页不是传统的页码。严格来说这叫“用游标翻页”不是“用页码翻页”。很多照搬老教程的人想当然把next从0递增到最后发现翻不了几页就是因为没弄明白这个游标逻辑。另外有个重要限制B站评论接口最多只能翻到第几十页或者固定条数这取决于视频的热度、官方限制实际响应里还会有一个is_end字段。判断要不要继续翻页直接看data.cursor.is_end它为true时就没必要再请求了。3.3 采集代码示例带请求头、带异常处理、带可控翻页这里写一个可直接复用的采集函数。为了演示清晰我把oid直接设为变量你把自己抓到的数字ID填进去就行。import requests import time import json 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, Referer: https://www.bilibili.com/, Origin: https://www.bilibili.com, } def fetch_comments(oid: int, max_pages: int 5, mode: int 3): 获取B站视频评论 :param oid: 视频的数字IDaid :param max_pages: 最多翻页数避免死循环 :param mode: 3热门排序2时间排序 :return: 评论文本列表 comments [] next_cursor 0 for page in range(max_pages): params { type: 1, oid: oid, mode: mode, next: next_cursor, } try: resp requests.get( https://api.bilibili.com/x/v2/reply/main, paramsparams, headersHEADERS, timeout10 ) data resp.json() if data[code] ! 0: print(f接口返回错误{data[code]} {data[message]}) break cursor data[data][cursor] replies data[data][replies] if not replies: break for reply in replies: comments.append(reply[content][message]) next_cursor cursor[next] if cursor.get(is_end, False): print(f已到最后一页共获取 {len(comments)} 条评论) break except Exception as e: print(f第 {page1} 页请求失败{e}) break time.sleep(1.5) # 控制频率别给服务器添压力 return comments if __name__ __main__: oid 12000000 # 替换成你想爬的视频的数字ID all_comments fetch_comments(oid, max_pages10) print(f共获取 {len(all_comments)} 条评论) with open(comments.json, w, encodingutf-8) as f: json.dump(all_comments, f, ensure_asciiFalse, indent2)几个设计细节我说明一下max_pages我设为可以手动控制的上限避免接口异常时陷入无限请求。time.sleep(1.5)是必要的尤其一次性翻几十页时不加歇菜的机率直线上升。把评论存成JSON而不是CSV是因为中文文本在CSV里容易出现编码问题JSON保存后编码统一后续读取也方便。4. 数据清洗别把垃圾词喂给分词器4.1 清洗哪些内容评论文本不是拿过来就能直接分词的里面混着大量干扰信息。我总结了一下清洗工作分这么几类去除HTML标签、URL链接、用户名形如某某人、表情符号B站评论常见[捂脸][脱单doge]。去除纯数字、无意义符号/、#、*等。去除空格、换行符把文本压成单行。去重同一条评论被多次回复引用时文本会出现重复用集合去重掉。4.2 正则清洗示例import re def clean_text(text: str) - str: # 去掉HTML标签 text re.sub(r[^], , text) # 去掉URL text re.sub(rhttps?://\S, , text) # 去掉用户名 text re.sub(r\S, , text) # 去掉表情符号形如 [捂脸] [脱单doge] text re.sub(r\[.*?\], , text) # 去掉纯数字 text re.sub(r\d, , text) # 去掉多余空白和换行 text re.sub(r\s, , text) return text.strip()注意正则清洗时不要把中文标点一并删掉。虽然最后统计词频时标点不影响但分词器需要标点作为句子的自然边界去掉标点可能导致一堆词粘连在一起影响分词效果。我一般会把中文标点句号、逗号、感叹号、问号替换成空格既保留边界又方便后续分词。5. jieba分词与词频统计实战5.1 分词模式怎么选jieba有三种分词模式精确模式、全模式、搜索引擎模式。这里推荐精确模式它是默认模式切分结果最接近真实词边界。全模式会把所有可能的词全部切出来比如“今天天气真好”会被切成“今天/天天/天气/真好”词频统计结果会变得非常碎。搜索引擎模式适合搜索场景这里用不上。最基本的用法import jieba text B站弹幕文化很有意思 words jieba.lcut(text) print(words) # 输出[B站, 弹幕, 文化, 很有, 意思]注意“有意思”被切成了“很有”“意思”这种粒度对词频统计来说还行但不完美。后面可以通过自定义词典优化。5.2 停用词表才是词频质量的关键停用词表决定了结果到底像人话还是一堆废词。所有数据清洗工具里我最看重的就是这一步。jieba本身不带高质量停用词表你需要自己维护一张。常见的停用词分两类无实际意义的虚词的、了、吗、啊、呢、就、都、也、还、在、是、有、我、你、他。梗词、泛化词哈哈、哈哈哈、hhhh、笑死、真的、感觉、觉得。前一类靠网络上有公开的“中文停用词表”搜“中文停用词表”能找到后一类属于你根据具体视频内容自己累积的。我的习惯是第一次跑完词频统计后从头扫一遍Top 50凡是看了不知道在说啥的词全部拉进停用词表再跑第二次。迭代两三轮之后词云的杂质就少多了。5.3 自定义词典处理B站黑话B站评论里高频出现“yyds”“绝绝子”“awsl”“破防”“泪目”这类网络热词。jieba不认识这些词可能把它们拆成几个部分。解决办法是自己维护一个自定义词典# bilibili_dict.txt每行一个词也可以加词频和词性 yyds 100 n 绝绝子 100 n awsl 100 n 破防 100 v 泪目 100 n加载方式jieba.load_userdict(bilibili_dict.txt)自定义词典对词频统计的影响非常大。不加词典时“yyds”会被切成“yyd”和“s”或者“yy”和“ds”统计结果直接废掉。加了词典之后这个词才能作为一个完整的词计入频率可视化结果才有B站特色。5.4 完整分词统计流程代码import jieba from collections import Counter import re STOPWORDS set() # 从你的停用词表文件加载 with open(stopwords.txt, r, encodingutf-8) as f: for line in f: word line.strip() if word: STOPWORDS.add(word) def tokenize(comments): 输入评论列表 输出过滤停用词后的所有词列表 all_words [] for comment in comments: # 清洗 text clean_text(comment) if not text: continue # 分词 words jieba.lcut(text) for w in words: w w.strip() # 单字过滤除非单字有独立意义看情况 if len(w) 2: continue # 字母转小写统一 yyds 和 YYDS w w.lower() if w in STOPWORDS: continue all_words.append(w) return all_words words tokenize(all_comments) word_counter Counter(words) top_words word_counter.most_common(50) # 输出到终端快速查看效果 for word, cnt in top_words: print(f{word}: {cnt})这里有一个细节为什么过滤单字词因为中文里大量单字了、的、我、你频次极高但对词频分析基本没有信息量。当然也有例外比如“泪”“帅”“燃”这种单字就很有味道。我一般先过滤单字跑完看效果再决定要不要把个别单字加回来。还有一个细节大小写归一化。B站评论里“yyds”和“YYDS”都常见如果不统一小写统计时会被当成两个词。程序里加一行w w.lower()成本极低收获巨大。6. pyecharts可视化从词频到可交互图表6.1 词云图词云图是整个项目最有视觉冲击力的产出。pyecharts的WordCloud组件用起来很简单但有几个配置参数值得细说from pyecharts.charts import WordCloud from pyecharts.globals import SymbolType # 准备词频数据格式要求是 [(词, 频次), ...] data [(word, cnt) for word, cnt in top_words] wc ( WordCloud() .add( series_name评论热词, data_pairdata, word_size_range[12, 80], shapeSymbolType.DIAMOND, ) .set_global_opts( title_opts{text: B站视频评论热词, subtext: 基于top50关键词}, ) ) wc.render(comment_wordcloud.html)word_size_range控制词的最小到最大字号频次越高的词字号越大。shape控制词云的整体形状默认是云朵形改成DIAMOND后会变成菱形。对B站视频来说做个圆形或心形词云会更有味道。6.2 柱状图词云看整体分布柱状图看精确数量。两者搭配效果更好。from pyecharts.charts import Bar # 取前20个词 top20 top_words[:20] bar ( Bar() .add_xaxis([word for word, _ in top20]) .add_yaxis(词频, [cnt for _, cnt in top20]) .set_global_opts( title_opts{text: 评论高频词Top20}, xaxis_opts{axislabel_opts: {rotate: 45}}, ) ) bar.render(comment_top20_bar.html)xaxis_opts里的rotate: 45是让X轴标签倾斜45度不然10个字以上的长词会叠在一起没法看。6.3 HTML图表的展示与分享pyecharts渲染出来的是HTML文件直接用浏览器打开即可。因为图表是动态交互的鼠标悬停能看到具体词频数字比静态图信息量大不少。文件还可以直接发给别人只要对方设备上能开浏览器就能看不需要装Python环境。写入文件时注意一点pyecharts默认生成的HTML文件引用了CDN上的JS资源离线打开时可能会因为加载不到JS而白屏。解决方法是下载完整HTML后把依赖的JS一并打包或者直接用在线环境打开。7. 常见问题与排查实录7.1 接口返回-412或-352错误这是风控拦截的典型表现。优先降低请求频率把time.sleep从1.5秒加到3秒甚至5秒然后检查你的User-Agent是不是浏览器标准格式最后可以尝试在请求头中带上Cookie从浏览器复制你的SESSDATA字段注意这是敏感信息别外传。这只是用于提升可用度不是必须的量少时不需要登录。7.2 第一页能拿到第二页拿不到多半是游标参数用错了。回头检查一下是不是把next当页码递增了。正确的做法是第二页的next必须取第一页响应里cursor.next的值而不是自己填1或2。7.3 词云图里全是“哈哈”和“真的”这就是停用词表不够全。加停用词表中加上“哈哈”“哈哈哈”“真的”“感觉”“觉得”“知道”这类高频虚词再跑一次就好了。这不是bug是数据清洗环节的正常迭代。7.4 分词结果里有英文缩写或者日文有些词是jieba识别不了的网络新词加自定义词典即可。也有的词确实没法完美处理比如英文和中文混排的“yyds”。我的做法是先统计一次把Top100里所有识别错的词看一遍该加词典的加词典该加停用词的加停用词迭代两次之后结果基本干净。7.5 爬虫被封怎么办说实话个人学习项目爬个几百条评论不太可能被封。真正出问题的情况是用了多线程、翻了几百页、没有sleep这才值得警惕。如果真遇到了先停下来歇会儿别硬刚。项目本身是学习用假设被限制正好可以观察一下接口在异常情况下的响应结构这本身也是经验积累。8. 项目扩展与后续玩法最基础版本的链路走通之后这个项目可以往多个方向延伸情感分析在词频统计之外用现成的情感词典或预训练模型对评论做正负向分类统计点赞数最高的前10条评论的情感倾向。多视频对比爬同一个UP主多个视频的评论对比不同视频的热词分布找出评论话题的演变轨迹。定时追踪用APScheduler或cron定时跑脚本记录评论热词随时间的变化分析弹幕文化的话题生命周期。评论用户分析从member.uname字段提取用户数据结合评论数、点赞数做一个简单的评论用户活跃度分析。我个人试过把热词结果配上评论点赞数做权重公式很简单词频 × 平均点赞权重。这样出来的词云能更好反映“大家都在意的词”而不是单纯“谁刷得多”。比如“哈哈哈”虽然频次高但点赞少“讲得好”虽然出现次数一般但赞很多。这种加权的玩法比单纯看频次有意思得多。这个项目的价值不在于单个技术点有多高深而在于把“拿到数据—处理数据—展示结果”整条链路走通。爬虫能力、文本处理能力、数据可视化能力这三个技能点的串联是很多实际需求的最小闭环。就算你以后不做爬虫这套“先分析接口、再清洗数据、最后可视化”的思维做任何数据类项目都用得上。最后提醒一句爬取数据是为了学习和分析注意控制请求频率别给别人的服务器添麻烦用数据时也要尊重原平台的内容规则。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询