尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python爬虫实战:B站弹幕与QQ音乐热评抓取及数据分析
1. 从两条数据线说起弹幕和热评到底能挖出什么很多人第一次接触爬虫都是从视频网站和音乐平台开始的。原因很简单这两类平台的数据有强烈的“人味儿”。B站的弹幕是几万几十万人同时看一个视频时留下的即时反应QQ音乐的热评则是无数人在深夜单曲循环时写下的情绪碎片。把这些数据抓下来你能做的事情远比想象中多——做情感分析、做词云、做弹幕密度曲线、做热评时间线甚至只是单纯想看看某首歌下面到底有多少人在讲同一个故事。但真动手的时候问题就来了。B站的弹幕不是直接写在HTML里的QQ音乐的评论区也不是翻页就能拿到的。这两个目标恰好代表了爬虫领域两种典型的难题动态加载数据的抓取和接口参数加密的破解。把这两个场景吃透你对Python爬虫的理解会上一个大台阶。这篇内容适合已经会写基本requests请求、看得懂HTML结构、但一遇到“数据不在源码里”就卡住的开发者。我会把B站弹幕和QQ音乐热评这两条线拆开讲从接口分析、参数构造、数据解析到存储落地每一步都给出可复现的操作和背后的逻辑。中间会穿插我自己踩过的坑比如弹幕接口的protobuf格式怎么处理、QQ音乐评论的加密参数怎么定位、请求频率怎么控制才不会被限。看完之后你手里应该有两套能直接跑的代码框架以及一套分析动态数据接口的通用方法论。注意本文所有操作仅用于个人学习和技术研究抓取的数据请勿用于商业用途或大规模分发。请求频率务必控制在合理范围内尊重目标平台的服务承载能力。2. B站弹幕爬取从接口定位到protobuf解码2.1 为什么弹幕不在HTML里——动态加载的本质如果你用浏览器打开一个B站视频页面右键查看网页源代码搜索弹幕文字大概率什么都找不到。这不是因为你操作错了而是因为弹幕数据根本不在初始HTML文档里。页面加载完成后JavaScript会再发一个异步请求去拿弹幕数据然后动态渲染到播放器上。这个异步请求就是我们需要的目标。打开浏览器的开发者工具切到Network面板筛选XHR或Fetch请求刷新页面你会看到一堆请求飞过去。其中有一个请求的URL里包含dm或者danmaku相关的路径返回的内容看起来像一堆乱码——这就是弹幕接口。具体来说B站的弹幕接口大致是这样的形式https://api.bilibili.com/x/v1/dm/list.so?oid视频的cid或者新版的https://api.bilibili.com/x/v2/dm/web/seg.so?type1oid视频的cidsegment_index1这里的关键参数是oid它对应视频的cid。cid不是视频的aid或bvid而是每个视频分P对应的唯一标识。要拿到cid需要先请求视频信息接口import requests def get_cid(bvid): url fhttps://api.bilibili.com/x/web-interface/view?bvid{bvid} 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/ } resp requests.get(url, headersheaders) data resp.json() if data[code] ! 0: raise Exception(f获取视频信息失败: {data[message]}) cid data[data][cid] title data[data][title] return cid, title拿到cid之后就可以请求弹幕数据了。但这里有个分水岭老接口list.so返回的是XML格式新接口seg.so返回的是protobuf格式。XML好处理直接解析就行protobuf需要额外的解码步骤。2.2 XML格式弹幕的解析lxml和XPath的配合先说过渡期的XML接口。请求list.so拿到的响应内容是一段XML结构大致如下i d p时间,模式,字号,颜色,时间戳,池,用户ID,行ID弹幕内容/d d p...另一条弹幕/d /ip属性里是一串逗号分隔的参数其中第一个是弹幕出现的时间秒第四个是颜色第六个是弹幕池0普通池1字幕池2特殊池。解析的时候用lxml配合XPath最顺手from lxml import etree def parse_xml_danmaku(xml_content): root etree.fromstring(xml_content) danmaku_list [] for d in root.xpath(//d): p_attr d.get(p) text d.text if not p_attr or not text: continue parts p_attr.split(,) danmaku_list.append({ time: float(parts[0]), mode: int(parts[1]), font_size: int(parts[2]), color: int(parts[3]), timestamp: int(parts[4]), pool: int(parts[5]), user_id: parts[6], row_id: parts[7], content: text }) return danmaku_list这里有个细节etree.fromstring接收的是bytes或str如果响应内容是bytes直接传进去就行。XPath用//d能匹配所有d标签不管它们在哪个层级。解析出来的time字段是弹幕在视频中出现的时间点后面做弹幕密度分析就靠它。实操心得XML接口返回的弹幕有数量上限通常只给一部分。如果你需要完整弹幕还是得走protobuf的分段接口按segment_index逐段拉取。2.3 protobuf格式弹幕的解码从二进制到可读文本新版接口seg.so返回的是protobuf序列化后的二进制数据。直接看是一堆乱码但protobuf的好处是结构定义清晰。B站弹幕的protobuf定义大致是这样的简化版message DanmakuElem { int64 id 1; int32 progress 2; int32 mode 3; int32 fontsize 4; uint32 color 5; string midHash 6; string content 7; int64 ctime 8; int32 weight 9; string action 10; int32 pool 11; string idStr 12; }要解码这个数据有两种方式。一种是安装protobuf库手写.proto文件然后编译另一种是用现成的第三方库比如bilibili-api-python或者dm.py这类封装好的工具。如果你只是想快速拿到数据推荐用现成的库from bilibili_api import video async def get_danmaku(bvid): v video.Video(bvidbvid) cid await v.get_cid(0) danmaku await video.get_danmaku(cid) return danmaku但如果你想理解底层原理手写解码逻辑更有价值。protobuf的二进制格式是“字段编号类型值”的序列可以用protobuf库的DecodeError来逐步解析。不过对于大多数应用场景直接用封装库就够了省下来的时间可以花在数据分析上。2.4 弹幕数据的存储与去重别让重复数据污染分析弹幕数据抓下来之后第一件事是去重。B站的弹幕接口在分段拉取时边界处可能会有重复。去重的依据可以用user_id content time的组合或者直接用弹幕的id字段protobuf里有idStr。存储方面如果数据量不大几万条以内用JSON文件就够了。如果要做长期分析建议上SQLite或MySQL。下面是一个SQLite的建表语句CREATE TABLE danmaku ( id INTEGER PRIMARY KEY AUTOINCREMENT, bvid TEXT NOT NULL, cid INTEGER NOT NULL, time REAL NOT NULL, mode INTEGER, color INTEGER, user_id TEXT, content TEXT NOT NULL, timestamp INTEGER, UNIQUE(bvid, user_id, time, content) );UNIQUE约束能自动去重插入时用INSERT OR IGNORE就行。这样即使重复抓取也不会产生冗余数据。3. QQ音乐热评爬取加密参数与接口逆向3.1 热评接口的定位从页面到XHRQQ音乐的评论区在网页版上是可以看到的但翻页的时候页面不会整体刷新而是局部加载。打开开发者工具切到Network面板点击评论区的“下一页”按钮你会看到一个新的请求发出去。这个请求的URL通常长这样https://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg? g_tkxxxloginUinxxxhostUin0formatjsoninCharsetutf8 outCharsetutf-8notice0platformyqq.jsonneedNewCode0 cid205892reqtype2psize15pagenum1参数不少但真正关键的是cid歌曲ID、pagenum页码、psize每页数量。g_tk和loginUin在未登录状态下可以留空或填默认值。reqtype2表示拿的是热评reqtype1是最新评论。但直接请求这个URL有时候会返回403或者空数据。原因在于QQ音乐对请求头有校验特别是Referer和User-Agent。加上正确的请求头之后基本就能拿到数据import requests def get_qq_comments(song_id, page1, page_size15): url https://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg params { g_tk: 5381, loginUin: 0, hostUin: 0, format: json, inCharset: utf8, outCharset: utf-8, notice: 0, platform: yqq.json, needNewCode: 0, cid: str(song_id), reqtype: 2, psize: str(page_size), pagenum: str(page) } 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://y.qq.com/ } resp requests.get(url, paramsparams, headersheaders) return resp.json()返回的JSON结构里comment.commentlist就是评论列表每条评论包含rootcommentcontent评论内容、time时间戳、praisenum点赞数、nick昵称等字段。3.2 参数加密的应对什么时候需要逆向什么时候可以绕QQ音乐的接口在大多数情况下不需要复杂的加密逆向。g_tk这个参数在未登录状态下可以固定填5381它本质上是一个CSRF token登录后才会动态生成。如果你只是抓公开的热评数据不涉及用户隐私用固定值就够了。但有些接口会校验sign参数这个就需要逆向JS了。不过热评接口目前没有这个要求。我的建议是先尝试直接请求遇到403再考虑逆向。很多教程一上来就讲JS逆向其实大部分公开数据接口的防护并没有那么严。如果确实遇到了加密参数通用的排查思路是在开发者工具的Sources面板里搜索参数名比如sign找到生成该参数的JS函数把JS代码抠出来用PyExecJS或js2py在Python里执行或者用playwright直接模拟浏览器行为绕过JS逆向对于QQ音乐热评这个场景前两步基本用不上直接请求就能拿到数据。3.3 翻页与频率控制别把接口打挂了QQ音乐热评的翻页逻辑很简单改pagenum就行。但要注意两点一是每页数量psize不要设太大默认15条比较稳设成50可能会被截断二是请求间隔要控制建议每次请求之间sleep1到2秒。import time import random def crawl_all_comments(song_id, max_pages20): all_comments [] for page in range(1, max_pages 1): data get_qq_comments(song_id, pagepage) comments data.get(comment, {}).get(commentlist, []) if not comments: break for c in comments: all_comments.append({ content: c.get(rootcommentcontent, ), time: c.get(time, 0), praise: c.get(praisenum, 0), nick: c.get(nick, ), song_id: song_id }) time.sleep(random.uniform(1.0, 2.0)) return all_commentsrandom.uniform比固定sleep更好因为固定间隔的请求模式容易被识别为爬虫。随机化之后请求行为更接近真人操作。注意如果返回的数据里commentlist为空不一定是被限了可能是这首歌的评论确实翻完了。可以先检查comment.total字段看看总共有多少条评论再决定翻多少页。4. 两条线的交汇数据清洗、分析与可视化4.1 弹幕数据的清洗时间对齐与异常值处理弹幕数据抓下来之后原始的时间字段是秒数但直接拿来做分析会有问题。比如有些弹幕的时间是负数表示在视频开始前发送的有些弹幕的时间超过了视频总时长可能是数据异常。清洗的时候需要把这些异常值过滤掉。def clean_danmaku(danmaku_list, video_duration): cleaned [] for d in danmaku_list: if d[time] 0 or d[time] video_duration: continue if not d[content].strip(): continue cleaned.append(d) return cleanedvideo_duration可以从视频信息接口里拿单位是秒。过滤之后弹幕的时间分布就准确了。接下来可以做弹幕密度分析把视频时长分成若干个区间比如每10秒一个桶统计每个区间内的弹幕数量。用matplotlib画出来就是一条弹幕密度曲线。峰值出现的地方往往是视频的高潮或槽点。import matplotlib.pyplot as plt def plot_danmaku_density(danmaku_list, duration, bucket_size10): buckets [0] * (int(duration // bucket_size) 1) for d in danmaku_list: idx int(d[time] // bucket_size) if idx len(buckets): buckets[idx] 1 plt.figure(figsize(12, 4)) plt.bar(range(len(buckets)), buckets, width0.8) plt.xlabel(Time (x10s)) plt.ylabel(Danmaku Count) plt.title(Danmaku Density) plt.show()这个图能直观地告诉你观众在哪个时间点最活跃。做视频内容分析的时候这个信息很有价值。4.2 热评的情感倾向用SnowNLP做快速分析QQ音乐热评的情感分析可以用SnowNLP这个库它专门针对中文文本开箱即用。安装很简单pip install snownlp用法也直接from snownlp import SnowNLP def analyze_sentiment(comments): results [] for c in comments: text c[content] if not text.strip(): continue s SnowNLP(text) results.append({ content: text, sentiment: s.sentiments, praise: c[praise] }) return resultssentiments返回0到1之间的值越接近1表示越正面越接近0表示越负面。把结果按点赞数加权平均就能得到这首歌热评的整体情感倾向。不过SnowNLP的准确率有限对于反讽、网络梗的识别不太好。如果要做更精细的分析可以考虑用预训练的中文情感模型比如bert-base-chinese微调后的版本。但对于快速探索性分析SnowNLP足够了。4.3 词云生成jieba分词与wordcloud的配合词云是展示文本数据最直观的方式之一。中文词云需要先分词用jieba然后生成词云图用wordcloud。import jieba from wordcloud import WordCloud def generate_wordcloud(texts, output_pathwordcloud.png): combined .join(texts) words jieba.cut(combined) filtered [w for w in words if len(w) 1] text .join(filtered) wc WordCloud( font_pathsimhei.ttf, width800, height400, background_colorwhite, max_words100 ) wc.generate(text) wc.to_file(output_path)font_path必须指定中文字体否则会显示成方块。simhei.ttf是黑体Windows系统里一般都有Linux下可能需要手动下载。分词之后可以进一步过滤停用词把“的”、“了”、“是”这类高频但无意义的词去掉。停用词表可以从网上下载现成的也可以自己根据数据特点整理。5. 踩坑记录那些让我熬夜的报错和解决方案5.1 B站接口返回412请求头里的Referer不能少第一次抓B站弹幕的时候接口一直返回412状态码。查了半天才发现B站的接口对Referer有校验必须带上https://www.bilibili.com/。而且User-Agent也不能用默认的python-requests得伪装成浏览器。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/ }加上这两个头之后412就消失了。这个坑的本质是很多平台的接口会校验请求来源不是随便发个GET就能拿数据的。遇到403或412先检查请求头。5.2 QQ音乐评论翻页到后面返回空可能是pagenum超限了QQ音乐热评接口对翻页深度有限制通常翻到第10页或第15页之后返回的commentlist就是空的了。这不是被限流而是接口本身的设计。解决办法是先请求第一页拿到comment.total字段算出总页数然后只翻到有效范围内。def get_total_pages(song_id, page_size15): data get_qq_comments(song_id, page1, page_sizepage_size) total data.get(comment, {}).get(total, 0) return (total page_size - 1) // page_size如果确实需要更多评论可以换用reqtype1最新评论接口它的翻页深度限制可能不同。或者用playwright模拟浏览器滚动触发页面自身的加载逻辑。5.3 protobuf解码报错版本不匹配是常见原因用protobuf库解码B站弹幕时如果.proto文件的定义和实际数据不匹配会报DecodeError。常见原因是B站更新了弹幕结构增加了新字段。解决办法是去B站的接口文档或开源项目里找最新的.proto定义或者直接用封装好的库让库的维护者去跟进结构变化。如果非要自己解码可以用protobuf的UnknownFieldSet来跳过未知字段但这样会丢失部分数据。更稳妥的做法是保持.proto文件更新。5.4 请求频率过高被限sleep和代理的取舍连续快速请求同一个接口大概率会被限流。表现是返回403、412或者直接超时。解决办法有两个一是加sleep二是用代理IP。对于个人学习用途加sleep就够了代理IP的成本和复杂度都更高。sleep的时间建议在1到3秒之间并且加一点随机性time.sleep(random.uniform(1.0, 3.0))如果目标数据量很大可以考虑用scrapy框架它内置了AutoThrottle扩展能根据服务器响应自动调整请求频率。配置起来也不复杂# settings.py AUTOTHROTTLE_ENABLED True AUTOTHROTTLE_START_DELAY 1.0 AUTOTHROTTLE_MAX_DELAY 10.0 AUTOTHROTTLE_TARGET_CONCURRENCY 1.0这样就不用自己手动调sleep了框架会帮你控制节奏。6. 从脚本到工具让爬虫更好用的几个改造6.1 用scrapy重构把零散脚本变成可维护的项目一开始用requests写脚本没问题但当你要抓多个视频、多个歌曲还要处理重试、去重、存储的时候scrapy的优势就出来了。把B站弹幕和QQ音乐热评分别做成两个Spider共用一套Item Pipeline做存储代码结构会清晰很多。import scrapy class BilibiliDanmakuSpider(scrapy.Spider): name bilibili_danmaku def __init__(self, bvidNone, *args, **kwargs): super().__init__(*args, **kwargs) self.bvid bvid def start_requests(self): url fhttps://api.bilibili.com/x/web-interface/view?bvid{self.bvid} yield scrapy.Request(url, callbackself.parse_video_info) def parse_video_info(self, response): data response.json() cid data[data][cid] danmaku_url fhttps://api.bilibili.com/x/v1/dm/list.so?oid{cid} yield scrapy.Request(danmaku_url, callbackself.parse_danmaku) def parse_danmaku(self, response): # 解析XML弹幕 passscrapy的Request支持meta参数可以在不同回调之间传递数据。start_requests方法可以接收命令行参数这样就能通过scrapy crawl bilibili_danmaku -a bvidBV1xx411c7mD来指定视频。6.2 加一个简单的可视化界面tkinter够用了如果你想让非技术用户也能用你的爬虫可以加一个tkinter界面。不用做得多漂亮一个输入框加一个按钮就行import tkinter as tk from tkinter import messagebox def on_crawl(): bvid entry.get() if not bvid: messagebox.showwarning(提示, 请输入BVID) return # 调用爬虫逻辑 messagebox.showinfo(完成, f已抓取 {bvid} 的弹幕) root tk.Tk() root.title(弹幕爬取工具) root.geometry(400x200) tk.Label(root, text输入BVID:).pack(pady10) entry tk.Entry(root, width40) entry.pack(pady5) tk.Button(root, text开始抓取, commandon_crawl).pack(pady20) root.mainloop()这个界面虽然简陋但足够让不懂命令行的用户操作了。打包成exe可以用pyinstallerpyinstaller --onefile --windowed danmaku_tool.py6.3 数据导出CSV和Excel的取舍抓下来的数据最终要给别人看或者做进一步分析。CSV通用性好Excel更直观。用pandas可以一键导出import pandas as pd df pd.DataFrame(danmaku_list) df.to_csv(danmaku.csv, indexFalse, encodingutf-8-sig) df.to_excel(danmaku.xlsx, indexFalse)encodingutf-8-sig是为了让Excel打开CSV时不乱码。如果数据量超过Excel的行数限制约104万行就只能用CSV了。7. 一些关于合规和边界的个人体会做爬虫这些年我越来越觉得技术本身是中性的关键在于怎么用。B站弹幕和QQ音乐热评都是公开数据个人学习研究没问题但大规模抓取、商业使用、或者把数据转卖给第三方就越界了。我的习惯是抓取频率控制在每秒1次以下单次任务不超过1000条数据只存在本地不公开分享原始数据。这样既满足学习需求又不会给平台造成负担。另外平台的反爬策略一直在变今天能用的接口明天可能就改了。所以代码要写得灵活一点把接口地址、请求头、解析逻辑都做成可配置的改起来方便。遇到接口变更先去开发者工具里看看新的请求长什么样然后调整代码就行。最后分享一个小技巧如果你只是想快速看看某个视频的弹幕或者某首歌的评论不想写代码可以用浏览器开发者工具的“Copy as cURL”功能把请求复制出来然后粘贴到Postman或者Insomnia里直接发。这样连Python都不用写适合快速验证接口是否可用。
RELATED

相关推荐

JavaWeb课设实战:基于Servlet+JSP的个人博客系统开发全程解析

JavaWeb课设实战:基于Servlet+JSP的个人博客系统开发全程解析

简介:JavaWeb课程设计常用的个人博客系统项目包,覆盖源代码、数据库SQL脚本、开发文档及报告PDF,适合计算机专业学生完成期末大作业、课程设计,也可作为毕业设计或初期项目演示的参考。资源基于JavaWeb分层架构,包含前…

📅 2026/10/9 17:26:49
Oracle 游标详解:从隐式到显式,一次搞懂游标生命周期与 TaoToken 调试实践

Oracle 游标详解:从隐式到显式,一次搞懂游标生命周期与 TaoToken 调试实践

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

📅 2026/10/9 17:26:49
北京大学MOOC Java程序设计:零基础系统入门与核心难点突破指南

北京大学MOOC Java程序设计:零基础系统入门与核心难点突破指南

1. 这门课到底在讲什么:从标题拆解核心定位“Java程序设计”这五个字看起来平平无奇,但加上“北京大学MOOC”这个限定,它的分量就完全不一样了。我前后完整跟过三轮这门课,也推荐过不少朋友去学,发现很多人对它的认知存…

📅 2026/10/9 17:26:49
MORE NEWS

更多资讯

📰

PCA9422与STM32F756ZG的低功耗电源管理方案设计

做低功耗物联网设备,最让人头疼的往往不是主控和传感器怎么选,而是整机电源这一摊子事。前阵子我手上一个便携式项目要求同时解决锂电池充电、多路电压输出和极低的待机功耗,干脆把 PCA9422 这颗电源管理芯片和 STM32F756ZG 主控搭在一起&…

📰

Vijeo Citect 6.1在Windows XP SP3上的工业组态部署指南

简介:Vijeo Citect 6.1是施耐德电气推出的工业级SCADA上位机软件,专为自动化工程师、系统集成人员及工控项目实施者设计,用于构建可视化监控界面、实现PLC实时数据采集、报警管理与历史数据归档。本资源为官方安装包完整镜像,含31…

📰

自动化立体仓库规划评估:先算清货位、吞吐与投资回收期

简介:自动化立体仓库是现代物流系统高密度存储与快速存取的核心设施,这份docx文档面向物流规划工程师、仓储管理人员及学习智能物流的学生,系统梳理了立体仓库的优缺点、核心功能与规划设计评估流程。资料先分析高层货架与巷道堆垛机在空间利…

📰

200个生产级登录模板:HTML5+CSS3+JS工程化实践

简介:本资源是面向前端开发者与网页设计学习者的200套登录界面模板合集,聚焦HTML5语义化结构、CSS3响应式布局与动效、JavaScript表单验证及jQuery交互增强四大核心能力,可快速支撑企业后台、SaaS平台、教育系统等多场景登录页开发需求。压缩…

📰

PHP心理咨询网站课程设计:角色权限、预约状态机与测评计分实战

简介:这份资源是一份面向高校计算机相关专业学生与毕业设计开发者的完整项目文档,主题为基于 PHP 的大学生心理咨询网站设计与实现,适合作为课程设计、毕业设计参考或 PHP Web 开发入门练手项目。压缩包内共 1 个 docx 文件,约 1.…

📰

TIA-942中文版实战:数据中心Tier等级与设计落地指南

简介:TIA-942标准中文完整版PDF,由美国电信工业协会发布,是数据中心电信基础设施设计与建设的权威参考。面向数据中心规划、运维、综合布线工程师及相关专业学习者,可系统指导机房分级、空间布局、缆线系统、冗余设计等核心环节。…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬