ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

B站弹幕爬取与词云分析实战:从接口解析到中文分词全攻略

2026/10/1 16:40:34 拓冰建站 浏览量
B站弹幕爬取与词云分析实战:从接口解析到中文分词全攻略 做了几年数据采集我越来越觉得弹幕是最被低估的分析素材。B站视频弹幕数据不像评论区那样有组织但它胜在够真实、够即时用户看到哪一帧就喷哪一句情绪颗粒度是文本分析里很难得的一类数据。前阵子心血来潮把一个热门视频的弹幕全量抓下来做了词云分析整个过程走下来发现坑比想象中多接口地址变了、中文乱码了、分词把哈哈哈算成关键词、词云字体发虚……这篇文章把完整流程复盘一遍从技术选型、弹幕接口解析到数据清洗、词云可视化该给的代码都给该避的坑也一起列出来。适合刚接触数据爬取的朋友也适合想用弹幕做用户反馈分析的运营同学参考。1. 弹幕数据到底能分析出什么先想清楚再动手很多教程上来就教代码结果读者爬完数据不知道下一步该干嘛。所以在动手之前先把这次项目的目标和边界说清楚。1.1 弹幕是用户情绪的实时快照弹幕和评论最大的区别是时间锚点。用户发弹幕时必定附着在视频的某个时间点上这意味着你可以分析出视频哪个片段最让人激动、哪个片段被吐槽最多、哪个片段让人反复前方高能。词云分析只是第一步它把文本频率转化为视觉上最容易感知的关键词分布帮你回答观众到底在聊什么。如果要再深一层还可以结合时间轴做分段词频统计技术路线是通用的。1.2 这次项目的边界公开视频、公开弹幕、学习用途本次采集对象只针对开放平台的公开视频弹幕数据本身属于公开内容采集频率必须控制在对目标站点无压力的水平且只用于个人学习和分析不商用、不做数据倒卖。爬虫技术本身是中性的但用不好容易涉及法律和合规问题这一点后面专门有一节讲。先说技术从B站获取弹幕数据核心并不在爬字上而在于找到正确的接口和解析格式。弹幕数据字段至少有三种发送时间、弹幕内容、发送者UID脱敏后可看。我们在分析阶段主要使用内容是文本所以清洗阶段要重点处理内容字段。2. 技术选型绕过页面解析直取官方弹幕接口如果你一开始想的是打开B站视频页用Selenium控制浏览器滚动从HTML里抠弹幕那方向就偏了。弹幕是动态加载内容页面解析不仅效率低而且Selenium每次启动一个浏览器实例内存开销大触发风控的风险也高。正确做法是直接找B站的弹幕数据接口一条请求拿全整包数据。2.1 为什么选择官方公开接口B站每个视频都有唯一的BV号通过视频详情接口可以拿到一个叫 cid 的数值这个数值对应视频分P的ID。弹幕接口以 cid 为入参返回XML或分段二进制数据。这个链路是官方公开的Web前端自己也在用所以只要按规范请求不需要模拟点击不需要OCR更不需要什么逆向破解。相比页面解析这种方式稳定、高效、代码量少。2.2 核心依赖清单与安装注意事项语言使用Python 3.9建议直接上新一点的版本。依赖列表非常短库名用途备注requests发送HTTP请求替代urllib处理Header更方便jieba中文分词词云分析前必须做分词wordcloud生成词云注意中文字体路径matplotlib绘图与图像显示wordcloud底层依赖pandas数据存储与清洗可选但用了会舒服很多lxml解析弹幕XML也可以只用正则安装命令一行搞定pip install requests jieba wordcloud matplotlib pandas lxml如果安装wordcloud时出现Microsoft Visual C 14.0报错首选方案是直接下载对应的.whl文件安装不要硬编译源码。2.3 从BV号到cid唯一绕不过去的关卡B站视频的播放页地址通常长这样https://www.bilibili.com/video/BV1xx411c7mDBV号就是BV1xx411c7mD这一段。我们要的cid需要通过视频详情接口获取https://api.bilibili.com/x/web-interface/view?bvidBV1xx411c7mD这个接口返回JSON里面包含页面标题、UP主信息、分P列表每个分P都有对应的cid。需要注意的是请求这个接口时最好带上Referer头否则部分请求会返回风险提示。原因很简单B站API在判断场景时会校验来源页面。下面代码演示完整获取过程。3. 手把手实现弹幕爬取关键代码与执行链路这一段从请求头设置、获取cid、请求弹幕、解析数据一直讲到落盘。我不喜欢放一堆零碎片段所以直接给出一段可运行的核心代码然后逐段解释。3.1 第一步设置请求头和会话import requests import json import re HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: https://www.bilibili.com/, Accept: application/json, text/plain, */*, } session requests.Session() session.headers.update(HEADERS)不要小看这个请求头。很多新手爬B站只带User-Agent也能通但遇到反爬加严的账号或IP段没有Referer的请求很容易被识别为异常。最稳妥的做法就是从上到下都保持浏览器常见字段。3.2 第二步获取cid并构造弹幕地址bvid BV1xx411c7mD view_url https://api.bilibili.com/x/web-interface/view resp session.get(view_url, params{bvid: bvid}) data resp.json() if data[code] ! 0: raise RuntimeError(f接口返回错误: {data}) pages data[data][pages] cid pages[0][cid] print(f标题: {data[data][title]}) print(f分P数量: {len(pages)}第一分P cid: {cid})注意data[code]必须判断是否等于0这是B站接口的统一约定。很多教程拿到数据不判断状态直接解析遇到“风险验证”或“参数错误”时只能一脸懵。分P视频必须逐P获取cid因为弹幕是跟着分P走的不同分P的弹幕池互相独立。3.3 第三步两种弹幕接口格式的选择B站弹幕接口常见有两种方案我都测试过方案一是老版XML全文接口https://comment.bilibili.com/{cid}.xml这个地址直接返回XML里面所有弹幕一锅端解析简单适合学习。缺点是弹幕数量比较大时接口实际只返回部分数据且没有分段能力。方案二是新版分段接口https://api.bilibili.com/x/v2/dm/web/seg.so?type1oid{cid}segment_index{index}这个接口把弹幕按时间段切成了多个segment单段返回protobuf二进制。好处是能获取更完整的历史弹幕坏处是需要处理protobuf格式复杂度瞬间上来了。对词云分析来说方案一完全够用绝大多数普通视频的弹幕量级不会大到XML接口装不下。但如果你想做长视频的完整弹幕档案或者想拿趋势数据最好还是用分段接口。为了兼顾可读性下面我用方案一因为它能让读者把注意力集中在数据处理上而不是被protobuf劝退。3.4 第四步解析XML弹幕并转为结构化数据XML弹幕内容长这样d p1.123,1,25,1678,0,0,3b4f2a1b,1234567前方高能/dd标签里的p属性包含逗号分隔的元信息常见字段依次是弹幕出现时间秒、弹幕类型、字体大小、颜色、发送时间戳、弹幕池、用户Hash、弹幕ID。标签内的文本就是弹幕内容。用lxml解析最省事from lxml import etree xml_text session.get(fhttps://comment.bilibili.com/{cid}.xml).content.decode(utf-8, errorsignore) root etree.fromstring(xml_text.encode(utf-8)) danmaku_list [] for d in root.xpath(//d): p_attr d.get(p, ).split(,) content d.text if not content: continue danmaku_list.append({ time: float(p_attr[0]) if p_attr else None, content: content, timestamp: int(p_attr[4]) if len(p_attr) 4 else None, }) print(f共获取 {len(danmaku_list)} 条弹幕)这里有一个特别容易踩的坑etree.fromstring接收的是字节串如果你直接传字符串中文编码不对就会报ValueError: Unicode strings with encoding declaration are not supported。我在第一次写时就是在这里卡了好久。上面的写法先把content转为字节再解析是从根源上避开这个坑。3.5 第五步多P视频合并与数据落盘如果视频有多个分P循环遍历每个分P即可all_danmaku [] for p in pages: cid p[cid] page_text session.get(fhttps://comment.bilibili.com/{cid}.xml).content root etree.fromstring(page_text) for d in root.xpath(//d): p_attr d.get(p, ).split(,) content d.text if content: all_danmaku.append({ page: p[page], part: p[part], time: float(p_attr[0]) if p_attr else None, content: content, }) import pandas as pd df pd.DataFrame(all_danmaku) df.to_csv(danmaku.csv, indexFalse, encodingutf-8-sig)utf-8-sig编码写入很关键。如果直接写utf-8用Excel打开CSV时中文大概率乱码因为Excel默认按ANSI解析。用utf-8-sig加BOM头Excel才能正确识别。这是数据分析落地时最常见的国服特供坑。到这里弹幕数据本身已经拿到手了但是几百上千条哈哈哈哈救命666还不能直接做词云必须进行清洗。4. 词云分析的正确打开方式分词、停用词与可视化调优词云生成的本质是词频统计但中文不像英文天然按空格分词。如果你直接统计字符串频率会出现前方高能被切成前方和高能两个词或者大量无意义的语气词霸占视觉中心。所以词云好看不好看七成取决于分词和过滤三成才是wordcloud参数。4.1 jieba分词在弹幕场景里的特殊处理jieba默认分词词典对网络流行语覆盖不足像yyds绝绝子AWSL这类词很容易被切碎。解决思路分两步先加自定义词典让jieba优先识别这些梗词import jieba custom_words [yyds, 绝绝子, AWSL, 破防了, 泪目, 名场面, 前方高能, 爷青回, 一键三连] for w in custom_words: jieba.add_word(w, freq10000)freq参数就是为了让这个词在切分时优先成词值越大优先级越高但也不宜设得太大否则会影响附近正常词的切分。然后是分词import jieba.analyse def segment(texts): words [] for t in texts: if not isinstance(t, str): continue t t.strip() if len(t) 2: continue words.extend(jieba.lcut(t)) return words seg_list segment(df[content].tolist())4.2 自定义停用词表与弹幕语气词过滤弹幕里出现频率最高的往往不是有效主题词而是哈哈哈哈哈哈啊哦666111等感叹词和刷屏数字。这些词不清掉词云出来全是尖叫声。我整理了一份针对弹幕场景的停用词表放在代码里直接过滤stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: word line.strip() if word: stopwords.add(word) # 额外补充弹幕特有停用词 extra_stopwords {哈哈, 哈哈哈, 哈哈哈哈, hhhh, hhh, 666, 111, 222, 333, 绝了, 救命, 卧槽, 我去, 牛批, 给力, 来了, 来了来了, 啊这, , 。。。} stopwords.update(extra_stopwords)注意这里有个矛盾点像卧槽救命这类词虽然可能反映情绪但作为高频词放进词云会掩盖真正的内容关键词。所以我倾向于把它们过滤掉。如果你的分析目标是情绪识别那另当别论可以保留并做情感极性分类。过滤完的完整流程filtered_words [w for w in seg_list if w not in stopwords and len(w) 1 and not w.isdigit() and w.strip()]还有一个很多人忽略的点弹幕文本里的英文字母大小写。像yyds和YYDS会被jieba看成两个词统计出来都很少。所以分词前统一转小写df[content] df[content].str.lower()4.3 词云参数调优字体、背景、形状与中文乱码中文词云最经典的报错就是OSError: cannot open resource这是因为wordcloud默认找英文渲染字体没法处理中文。解决办法是指定中文字体路径。Windows系统一般用C:/Windows/Fonts/simhei.ttf也可以下载思源黑体放到项目目录里。生成词云的完整代码from wordcloud import WordCloud import matplotlib.pyplot as plt from collections import Counter counter Counter(filtered_words) font_path C:/Windows/Fonts/simhei.ttf wc WordCloud( font_pathfont_path, width1920, height1080, background_colorblack, max_words200, collocationsFalse, ) wc.generate_from_frequencies(counter) plt.figure(figsize(16, 9)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.savefig(danmaku_wordcloud.png, dpi300, bbox_inchestight)这里两个参数很关键。一个是collocationsFalse如果不关掉wordcloud会默认把相邻搭配识别成词组比如前方高能拼成前方高能然后再计数导致重复统计。另一个是interpolationbilinear让图片边缘更平滑尤其在浅色背景上能明显减少锯齿感。形状定制也简单用PIL处理一下蒙版图import numpy as np from PIL import Image mask np.array(Image.open(mask.png).convert(L)) wc WordCloud(font_pathfont_path, maskmask, background_colorwhite, collocationsFalse)使用形状遮罩时注意mask图片必须是黑底白图案白色区域才是词云绘制区。很多人用普通彩色图片直接当mask结果输出一片空白就是因为没转成灰度且没有处理好黑白关系。4.4 词云结果怎么看高频词背后的信息出一张词云图不是终点。比如我给一个游戏区视频做分析时最大的词是联机其次是教程配置卡顿。这说明弹幕讨论重心在实操层面而非剧情或画面。你可以根据词云反推视频剪辑节奏是否过慢比如太慢了倍速高频哪些画面最受欢迎名场面回放用户对UP主的期待是什么下一期更新这些结论如果配合弹幕时间轴做分段统计效果会更进一步。比如把视频前10%时间段的弹幕单独拿出来做词云通常能看出开头的保留率问题把后半段弹幕拿出来能看出内容是否烂尾。词云本身只是工具关键是你想让它替你回答什么问题。5. 实测中的常见坑反爬、编码、时间与合规边界最后这一段是实战中最需要留意的部分。我按踩过的坑排列由技术到合规逐个拆开说。5.1 请求频率与请求头伪装B站接口并不算严格但如果你连续用同一个IP高频请求几百个视频会触发一个叫请求过于频繁的提示接口返回code: -412。这是个风控信号意味着你的IP段被短期观察了。解决办法不是换IP池而是从源头控制频率。比如每请求一个视频time.sleep(1)到2秒并发控制在2至3个以内。毕竟你是做学习分析不是做数据工厂没必要和风控硬刚。另外User-Agent最好保持较新的Chrome版本字符串并且不要每次请求都换一个随机UA那样反而更容易被识别为爬虫。一个稳定UA配上固定Referer表现上更像真实用户。5.2 XML解析时的中文编码问题前面提到用lxml解析时etree.fromstring的编码坑是个高频问题。还有一个常见问题是接口返回的XML内容如果包含、这些特殊字符直接用正则解析容易崩。用etree.XMLParser(resolve_entitiesTrue)可以处理实体符号但更省事的是不要用正则解析XML。记住一条铁律XML就用XML解析器HTML就用HTML解析器不要试图用正则去硬刚结构化文本。5.3 弹幕数量与“字少词高频”的天然偏误弹幕文本普遍短一条弹幕可能就四五个字所以词频统计天然偏向短小的语气词和网络梗。即使做了停用词过滤绝了救命这种词还是容易冒出来。如果某个词你想排除但因为它变体太多排不干净可以考虑用TF-IDF或TextRank来替代纯词频统计。jieba自带jieba.analyse.textrank它能根据词之间的共现关系给词加权效果比单纯Counter适合弹幕场景top_kw jieba.analyse.textrank( .join(filtered_words), topK50, withWeightTrue)TextRank出来的关键词往往更有代表性因为它在多个弹幕中反复以相似语义共现才会获得高权重。对词云来说你也可以只取前50个关键词生成词云避免噪音词干扰。5.4 合规边界学习用途也要守住底线弹幕数据虽然公开但爬取和使用仍需遵循以下底线只采集公开接口返回的公开弹幕不碰私信、隐私、付费内容。严格控制请求频率不制造访问压力不逆向强加密接口。所有代码和结果仅用于个人学习研究不商用不提供批量下载服务不转售数据。不在博客放其他人能直接复制就去批量抓全站数据的一键脚本这既是对平台资源的保护也是对自己的保护。我在这里不会提供绕过验证码、模拟登录获取敏感数据的方法那部分已经超出技术分享的范畴。你能从这篇文章里拿到的是公开接口的规范用法和完整的分析思路。最后说一点个人体会弹幕词云这个项目技术上不复杂但把它做顺了你对HTTP请求、数据解析、文本清洗和可视化的理解会扎实很多。我实际跑下来最大感受是B站弹幕接口最大的门槛不是反爬而是“你以为它很难于是照着错误教程绕远路”。只要找准cid这个钥匙后面就是流水线操作。踩过几次坑之后我现在做这类分析会多备份一步把清洗前的原始弹幕数据单独存一份。因为词云参数可以反复调停用词表也会改如果原始数据被覆盖所有调优都得重新爬一遍。这个习惯帮我省了不少时间。如果你后面想继续深入可以在词云基础上做弹幕情感分析、按时间轴分段热度曲线甚至把多个视频的弹幕横向对比看UP主选题方向。技术都是一通百通的先把这条链路跑通后面自然有更多玩法。