Python爬虫实战:B站弹幕与QQ音乐热评抓取全流程 1. 从两个爬虫项目说起为什么选B站弹幕和QQ音乐热评做爬虫的人都有一个共识练手项目选得好学习效率能翻倍。我前后带过不少刚入门的朋友发现一个规律——凡是拿电商网站练手的十有八九卡在登录态和验证码上三天就放弃了而拿B站弹幕和QQ音乐热评练手的基本都能跑通全流程因为这两个目标的数据结构清晰、反爬门槛适中、数据本身还有意思。B站弹幕本质上是一堆带时间戳的XML或JSON文本每条弹幕记录了发送时间、模式、字号、颜色和内容。爬下来之后你能做词云、情绪分析、名场面时间轴定位甚至能还原一场直播的弹幕密度曲线。QQ音乐热评则是另一种形态——它是用户评论的集合包含昵称、点赞数、评论内容和时间。这两类数据放在一起练刚好覆盖了爬虫最核心的两个场景结构化数据接口抓取和半结构化页面解析。这篇文章面向的是已经装好Python、知道requests和xpath大概怎么用、但还没完整跑通过一个真实项目的朋友。我会把两个项目从接口分析到数据落地的全过程拆开讲包括我踩过的坑、参数怎么定、为什么这么选以及遇到反爬时怎么绕。代码可以直接抄但更重要的是理解每一步背后的判断逻辑。提示本文所有操作仅用于个人学习和技术研究抓取频率请控制在合理范围内不要对目标服务器造成压力。2. 项目整体设计与技术选型思路2.1 为什么不用Scrapy而先用requests热搜词里出现了scrapy但我建议这两个项目先用requests跑通原因很实际。Scrapy适合大规模、多页面的分布式抓取它有一套完整的调度器和中间件体系。但对于B站弹幕和QQ音乐热评这种单接口、参数明确、数据量可控的场景Scrapy的启动成本反而成了负担——你要配settings、写middleware、调并发最后发现核心逻辑就一个GET请求。我的判断标准是这样的如果目标数据能通过一个或几个明确的API接口拿到且不需要登录态维持、不需要大规模并发就用requests加xpath或json解析。如果要做全站抓取、需要去重队列、需要断点续爬再上Scrapy。这两个项目属于前者所以技术栈定为请求层requests 自定义请求头解析层jsonB站弹幕xpath/reQQ音乐热评存储层csv文件 sqlite可选辅助工具time控制频率、random生成随机延迟2.2 B站弹幕的数据形态判断B站弹幕有两种获取路径。一种是老版的XML接口返回的是d标签包裹的弹幕格式是时间,模式,字号,颜色,时间戳,弹幕池,用户哈希,弹幕ID。另一种是新版的protobuf接口数据更全但解析复杂。对于练手来说XML接口足够用而且解析逻辑直观。关键问题是弹幕接口需要哪些参数核心参数是oid也就是视频的cid弹幕ID不是aid视频ID。很多人在这里卡住拿着视频URL里的BV号直接去请求弹幕接口结果返回空。正确的流程是先通过BV号拿到aid和cid再用cid去请求弹幕。2.3 QQ音乐热评的接口特征QQ音乐热评的接口相对隐蔽它不像B站那样有公开的弹幕接口文档。实际抓取时你需要通过浏览器开发者工具观察评论加载时的网络请求。通常评论数据会以JSON形式返回包含commentlist数组每条评论有nick、praisenum、rootcommentcontent等字段。这里有个坑QQ音乐的评论接口有分页参数和时间戳签名。早期版本只需要topid歌曲ID和pagenum就能拿到数据后来增加了cmd和req等参数。我的做法是先用浏览器抓一次完整请求把URL和请求头原样复制到代码里跑通之后再逐步精简参数找出哪些是必需的、哪些可以省略。2.4 两个项目的共性技术点虽然目标不同但两个项目共享同一套核心逻辑技术环节B站弹幕QQ音乐热评请求方式GET参数在URLGET参数在URL数据格式XMLJSON解析工具lxmlxpathjson 字典取值反爬机制频率限制、UA校验频率限制、Referer校验存储方案CSV按视频分文件CSV按歌曲分文件把这两个项目放在一起练你能一次性掌握XML解析和JSON解析两种技能同时理解不同网站的反爬策略差异。3. B站弹幕爬取的核心细节与实操要点3.1 从BV号到cid的转换逻辑B站视频URL里的BV号是视频的唯一标识但弹幕接口需要的是cid。转换路径是BV号 →aid→cid。具体操作是请求视频页面在返回的HTML中搜索cid:后面的数字。我试过直接用正则匹配cid:(\d)成功率很高。这里有个细节B站页面返回的HTML中cid可能出现多次要取第一个匹配结果。另外如果视频有分P每个分P的cid不同需要根据实际需求选择。对于单P视频直接取第一个即可。import re import requests def get_cid(bvid): url fhttps://www.bilibili.com/video/{bvid} headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(url, headersheaders) cid_match re.search(rcid:(\d), resp.text) if cid_match: return cid_match.group(1) return None3.2 弹幕接口的请求与解析拿到cid后请求弹幕接口。接口地址是https://comment.bilibili.com/{cid}.xml。返回的XML结构如下i d p时间,模式,字号,颜色,时间戳,弹幕池,用户哈希,弹幕ID弹幕内容/d /i解析时用lxml的xpath提取所有d标签然后按逗号分割p属性。我通常只保留时间、内容和颜色三个字段其他字段对于分析来说冗余。from lxml import etree def parse_danmaku(cid): url fhttps://comment.bilibili.com/{cid}.xml headers {User-Agent: Mozilla/5.0} resp requests.get(url, headersheaders) resp.encoding utf-8 xml etree.HTML(resp.text) danmaku_list xml.xpath(//d) results [] for d in danmaku_list: p_attr d.xpath(./p)[0] content d.xpath(./text())[0] parts p_attr.split(,) results.append({ time: float(parts[0]), content: content, color: parts[3] }) return results3.3 弹幕爬取的注意事项第一编码问题。B站弹幕XML的编码是UTF-8但requests有时会误判为ISO-8859-1必须手动设置resp.encoding utf-8否则中文弹幕会变成乱码。第二频率控制。连续请求多个视频的弹幕接口时建议每次请求间隔1到2秒。我实测过间隔低于0.5秒时大约请求20次后会触发限流返回空数据或403。第三弹幕池概念。弹幕的p属性中有一个字段是弹幕池值为0表示普通弹幕池1表示字幕池2表示特殊弹幕池。做分析时通常只取普通弹幕池的数据。注意不要用多线程疯狂请求弹幕接口B站对弹幕接口的频率限制比视频页面更严格。单线程加延迟是最稳妥的方案。4. QQ音乐热评爬取的核心细节与实操要点4.1 定位评论接口的实操方法打开QQ音乐网页版进入任意歌曲页面按F12打开开发者工具切换到Network面板筛选XHR请求。刷新页面后你会看到几个返回JSON的请求其中一个的响应内容包含commentlist字段那就是评论接口。我通常会把请求URL复制出来观察参数结构。典型的URL包含topid歌曲ID、pagenum页码、cmd命令类型等。其中topid是核心参数其他参数在不同版本中可能变化。4.2 请求头的关键字段QQ音乐对请求头的校验比B站严格。以下字段是必需的User-Agent模拟浏览器Referer必须设置为https://y.qq.com/否则返回403Cookie部分接口需要登录态但热评接口通常不需要我试过只带User-Agent请求结果返回的是空评论列表。加上Referer后数据正常返回。这说明QQ音乐通过Referer做了来源校验。4.3 评论数据的解析与分页返回的JSON结构中评论数据在commentlist数组里。每条评论的关键字段字段名含义备注nick用户昵称可能包含特殊字符praisenum点赞数整数rootcommentcontent评论内容核心数据time评论时间时间戳格式分页逻辑是递增pagenum直到返回的commentlist为空或达到预设页数。我一般设置最多抓取10页每页20条共200条评论足够做分析用。import json import requests import time def get_qq_comments(song_id, max_pages10): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: https://y.qq.com/ } all_comments [] for page in range(max_pages): url fhttps://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg?topid{song_id}pagenum{page} resp requests.get(url, headersheaders) data json.loads(resp.text) comments data.get(commentlist, []) if not comments: break for c in comments: all_comments.append({ nick: c.get(nick, ), praise: c.get(praisenum, 0), content: c.get(rootcommentcontent, ) }) time.sleep(1.5) return all_comments4.4 热评爬取的避坑经验坑一评论内容被截断。部分长评论在接口中只返回前50个字完整内容需要额外请求详情接口。如果做情感分析截断会影响结果建议只分析短评论或接受截断。坑二点赞数排序不稳定。接口返回的评论是按时间排序的不是按点赞数。要拿热评需要在本地按praisenum降序排列。坑三歌曲ID获取。QQ音乐的歌曲ID藏在页面源码的songmid字段中需要通过搜索接口或页面解析获取。我通常直接用搜索接口传入歌名取第一条结果的songmid。5. 两个项目的完整实操流程与参数计算5.1 B站弹幕爬取的端到端流程完整流程分为五步输入BV号从用户输入或配置文件中读取。获取cid请求视频页面正则提取cid。请求弹幕XML用cid构造弹幕接口URL。解析XML提取时间、内容、颜色。存储CSV按视频BV号命名文件。参数计算方面弹幕的时间字段是浮点数单位是秒。如果要按分钟聚合用int(time // 60)即可。颜色字段是十进制整数转成十六进制就是RGB颜色值。import csv def save_danmaku(bvid, danmaku_list): filename f{bvid}_danmaku.csv with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[time, content, color]) writer.writeheader() writer.writerows(danmaku_list) print(f已保存 {len(danmaku_list)} 条弹幕到 {filename})5.2 QQ音乐热评爬取的端到端流程输入歌名通过搜索接口获取songmid。构造评论接口URL拼接topid和pagenum。循环请求分页直到无数据或达到上限。本地排序按点赞数降序。存储CSV按歌名命名文件。搜索接口的URL格式是https://c.y.qq.com/soso/fcgi-bin/client_search_cp?w{歌名}formatjson。返回的JSON中data.song.list[0].songmid就是目标歌曲ID。5.3 频率控制与随机延迟的实操参数我在这两个项目中都用了随机延迟具体参数是time.sleep(random.uniform(1.0, 2.5))。为什么不用固定延迟因为固定延迟的请求间隔呈现规律性容易被识别为爬虫。随机延迟模拟了人类操作的不可预测性。实测数据B站弹幕接口在1.5秒间隔下连续请求50个视频无异常。QQ音乐评论接口在2秒间隔下连续请求30首歌无异常。低于这个间隔触发限流的概率明显上升。5.4 数据存储的选型对比存储方式优点缺点适用场景CSV简单、可直接用Excel打开不支持复杂查询小规模数据SQLite支持SQL查询、单文件需要额外学习中等规模MySQL支持并发、功能强需要安装配置大规模对于练手项目CSV足够。如果要做弹幕时间序列分析SQLite更方便因为可以用SQL做聚合查询。6. 常见问题与排查技巧实录6.1 B站弹幕返回空的排查路径现象请求弹幕接口返回空XML或只有i/i。排查步骤检查cid是否正确。用浏览器直接访问https://comment.bilibili.com/{cid}.xml看是否有数据。检查请求头。缺少User-Agent时B站可能返回空。检查频率。如果短时间内请求过多等待10分钟再试。检查视频状态。部分视频关闭了弹幕功能或弹幕池为空。我遇到过一次cid提取正确但弹幕为空后来发现那个视频是刚发布的还没有弹幕。所以拿到空数据不一定是代码问题也可能是数据源本身为空。6.2 QQ音乐评论接口返回403的解决现象请求评论接口返回403 Forbidden。原因Referer缺失或User-Agent被识别为爬虫。解决补全Referer: https://y.qq.com/并使用完整的浏览器User-Agent。如果仍然403尝试添加Cookie字段从浏览器中复制登录后的Cookie。6.3 中文乱码的通用处理无论是B站还是QQ音乐中文乱码的根源都是编码不一致。通用处理方案resp.encoding resp.apparent_encodingapparent_encoding会自动检测编码但有时不准。更稳妥的做法是手动指定B站用utf-8QQ音乐用utf-8。如果返回的是\u开头的Unicode转义用json.loads会自动解码。6.4 常见问题速查表问题可能原因解决方案弹幕为空cid错误/频率限制/视频无弹幕浏览器验证cid、加延迟、换视频评论403Referer缺失补全Referer和UA中文乱码编码不一致手动设置encoding数据重复分页参数未递增检查pagenum循环逻辑程序卡死未设超时requests加timeout参数提示所有请求都建议加timeout10避免网络异常时程序无限等待。6.5 独家避坑技巧技巧一先手动验证接口。写代码之前先用浏览器或Postman手动请求一次接口确认能拿到数据。这一步能排除80%的接口地址错误。技巧二保存原始响应。解析之前先把resp.text保存到文件解析失败时可以反复调试不用重复请求。技巧三用try-except包裹解析逻辑。单条数据解析失败不应该导致整个程序崩溃。我在解析弹幕时遇到过某条弹幕的p属性字段数不足直接索引会报错用try-except跳过即可。技巧四分页抓取时设置最大页数。不要用while True无限循环设置一个合理的上限比如20页。否则接口异常时可能陷入死循环。7. 从练手到进阶这两个项目还能怎么扩展跑通基础版本之后这两个项目有很多扩展方向。B站弹幕可以做时间轴密度分析——把弹幕按秒聚合画出密度曲线高峰位置通常对应视频的精彩片段。还可以做词频统计用jieba分词后生成词云。QQ音乐热评可以做情感分析用SnowNLP或textblob判断评论的正负面倾向。如果想练分布式爬虫可以把这两个项目改造成Scrapy版本加入Redis去重队列用多台机器分别抓取不同的视频或歌曲。但我的建议是先把单机版本跑稳理解每一个请求和解析的细节再上分布式。否则出了问题你都不知道是调度器的锅还是解析逻辑的锅。另外热搜词里提到的scrapy playwright 动态iframe那是另一个层面的问题——当数据不在HTML源码里而是通过JavaScript动态渲染时才需要用到Playwright。B站弹幕和QQ音乐热评都不属于这种情况它们的接口返回的是纯文本或JSON用requests足够。不要为了用新技术而用新技术工具选型要看实际场景。我在实际使用中发现这两个项目最大的价值不是代码本身而是帮你建立一套接口分析的方法论打开开发者工具、观察请求、复制参数、手动验证、代码实现、频率控制、异常处理。这套方法论可以迁移到任何网站的爬取任务上。踩过几次坑之后你拿到一个新目标基本能在半小时内判断出用什么技术栈、大概需要多少工作量。