Python 爬虫项目:爬取 B 站视频标题与播放量
前言
B 站(哔哩哔哩)作为国内领先的视频内容平台,其视频标题、播放量等数据是分析内容趋势、用户偏好的重要依据。相较于静态网页爬取,B 站页面融合了动态加载等特性,对新手而言是进阶爬虫学习的典型场景。本文从零基础视角出发,系统讲解如何构建稳定的 B 站视频数据爬虫,涵盖动态页面分析、数据精准提取、反爬策略适配等核心知识点,帮助读者掌握针对主流视频平台的爬虫开发思路。
摘要
本文以B 站热门视频榜单(https://www.bilibili.com/v/popular/rank/all)为爬取目标,采用 requests 库发送 HTTP 请求获取页面源码,通过 BeautifulSoup 解析 HTML 结构,精准提取视频标题、播放量、UP 主、弹幕数等核心数据,并实现数据的结构化存储与可视化展示。针对 B 站的反爬机制,加入请求头伪装、请求延时等策略,保证爬虫的稳定性与合法性,所有代码均可直接复用。
| 核心技术 | 作用 |
|---|---|
| requests | 发送 HTTP 请求,获取 B 站榜单页面源码 |
| BeautifulSoup4 | 解析 HTML 文档,定位视频数据节点 |
| time | 设置请求延时,降低反爬风险 |
| csv | 将爬取数据存储为 CSV 文件,便于后续分析 |
| re | 清洗播放量 / 弹幕数等数字数据,提取纯数值 |
一、环境准备
1.1 安装依赖库
爬虫开发前需安装核心依赖,打开终端执行以下命令:
bash
运行
pip install requests beautifulsoup4 1.2 环境说明
- Python 版本:3.7+(兼容主流版本)
- 操作系统:Windows/macOS/Linux 均可
- 依赖库版本:requests≥2.28.0,beautifulsoup4≥4.11.0
二、爬虫原理剖析
2.1 核心流程
- 发送请求:向 B 站热门榜单页面发送 GET 请求,获取页面 HTML 源码;
- 页面解析:通过 HTML 标签、类名定位视频条目,提取目标数据节点;
- 数据清洗:将「100 万」「89.5 万」等格式化数字转换为纯数值,便于分析;
- 存储数据:将结构化数据保存为 CSV 文件,解决中文乱码问题;
- 反爬适配:完善请求头、添加请求延时,模拟正常浏览器访问。
2.2 页面结构分析
通过浏览器「开发者工具」(F12)分析 B 站热门榜单页面:
- 视频条目:
<div>标签包裹单个视频信息; - 视频标题:
<a href="视频链接">标题文本</a>; - 播放量 / 弹幕数:
<div>标签内包含「播放量 弹幕数」文本(格式:100.5万 1.2万); - UP 主信息:
<span>UP主名称</span>。
三、完整代码实现
python
运行
import requests from bs4 import BeautifulSoup import time import csv import re from typing import List, Dict class BilibiliSpider: """B站热门视频爬虫类,封装核心爬取逻辑""" def __init__(self): # 请求头:模拟Chrome浏览器,添加多字段降低反爬概率 self.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/", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate, br" } # 目标URL:B站全品类热门榜单 self.target_url = "https://www.bilibili.com/v/popular/rank/all" # 存储爬取的视频数据 self.video_data: List[Dict[str, str | int]] = [] def send_request(self) -> str: """发送GET请求,获取页面源码,包含完整异常处理""" try: # 设置15秒超时,避免请求卡死 response = requests.get( url=self.target_url, headers=self.headers, timeout=15 ) # 校验HTTP响应状态码(非200则抛出异常) response.raise_for_status() # 自动识别编码,解决乱码问题 response.encoding = response.apparent_encoding return response.text except requests.exceptions.ConnectTimeout: print("请求超时:无法连接到B站服务器") return "" except requests.exceptions.HTTPError as e: print(f"HTTP错误:{e.response.status_code}") return "" except requests.exceptions.RequestException as e: print(f"请求失败:{e}") return "" def format_number(self, num_str: str) -> int: """格式化数字字符串为整数(如:100.5万 → 1005000)""" if not num_str or num_str == "0": return 0 # 处理万级单位 if "万" in num_str: # 提取数字部分并转换 num = re.findall(r"\d+\.?\d*", num_str)[0] return int(float(num) * 10000) # 处理亿级单位(B站部分热门视频会出现) elif "亿" in num_str: num = re.findall(r"\d+\.?\d*", num_str)[0] return int(float(num) * 100000000) # 纯数字直接转换 else: return int(re.sub(r"\D", "", num_str)) def parse_page(self, html: str) -> None: """解析页面源码,提取视频标题、播放量等核心数据""" if not html: print("页面源码为空,解析终止") return soup = BeautifulSoup(html, "html.parser") # 定位所有视频条目 video_items = soup.find_all("div", class_="rank-item") for rank, item in enumerate(video_items, 1): # 1. 提取视频标题 title_tag = item.find("a", class_="title") video_title = title_tag.get_text(strip=True) if title_tag else "无标题" # 2. 提取视频链接 video_link = "https:" + title_tag["href"] if (title_tag and "href" in title_tag.attrs) else "#" # 3. 提取UP主名称 up_name_tag = item.find("span", class_="data-box up-name") up_name = up_name_tag.get_text(strip=True) if up_name_tag else "未知UP主" # 4. 提取播放量和弹幕数 detail_data_tag = item.find("div", class_="detail-data") if detail_data_tag: detail_text = detail_data_tag.get_text(strip=True) # 拆分播放量和弹幕数(格式:播放量 弹幕数) detail_list = detail_text.split() play_count_str = detail_list[0] if len(detail_list) >= 1 else "0" danmu_count_str = detail_list[1] if len(detail_list) >= 2 else "0" # 格式化数字 play_count = self.format_number(play_count_str) danmu_count = self.format_number(danmu_count_str) else: play_count = 0 danmu_count = 0 # 封装数据 self.video_data.append({ "排名": rank, "视频标题": video_title, "UP主": up_name, "播放量": play_count, "弹幕数": danmu_count, "视频链接": video_link }) print(f"已提取第{rank}名:{video_title} | 播放量:{play_count} | 弹幕数:{danmu_count}") def save_to_csv(self) -> None: """将爬取的视频数据保存为CSV文件,解决中文乱码问题""" if not self.video_data: print("无视频数据可保存") return # 定义CSV表头 csv_headers = ["排名", "视频标题", "UP主", "播放量", "弹幕数", "视频链接"] # 保存路径:当前目录下的bilibili_rank.csv save_path = "bilibili_rank.csv" try: # 使用utf-8-sig编码,兼容Windows/Mac系统,避免中文乱码 with open(save_path, "w",, encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=csv_headers) writer.writeheader() writer.writerows(self.video_data) print(f"数据保存成功!共 {len(self.video_data)} 条视频数据,文件路径:{save_path}") except Exception as e: print(f"数据保存失败:{e}") def run(self) -> None: """爬虫主执行函数,整合所有流程""" print("开始爬取B站热门视频榜单数据...") # 1. 发送请求获取页面源码 html = self.send_request() # 2. 解析页面提取数据 self.parse_page(html) # 3. 保存数据到CSV self.save_to_csv() # 4. 延时退出,模拟正常操作 time.sleep(2) print("B站视频数据爬取完成!") if __name__ == "__main__": # 实例化并运行爬虫 bilibili_spider = BilibiliSpider() bilibili_spider.run() 四、代码详细解释
4.1 核心模块说明
4.1.1 初始化模块(init)
- 配置完善的请求头:除
User-Agent外,添加Referer(来源页)、Accept-Language(语言偏好)等字段,最大化模拟真实浏览器请求; - 定义目标 URL 为 B 站全品类热门榜单,便于获取高价值视频数据;
- 初始化列表存储视频数据,指定数据类型(
str | int)提升代码规范性。
4.1.2 请求模块(send_request)
- 细分异常类型:针对超时、HTTP 错误、通用请求异常分别处理,精准定位问题;
- 自动编码识别:使用
response.apparent_encoding替代固定编码,适配 B 站不同页面的编码规则; - 设置 15 秒超时时间,避免爬虫因服务器响应慢而长时间阻塞。
4.1.3 数字格式化模块(format_number)
- 核心工具函数:解决 B 站「100.5 万」「2.3 亿」等格式化数字的分析难题;
- 正则提取:通过
re.findall(r"\d+\.?\d*", num_str)精准提取数字部分,忽略单位; - 单位转换:按「万」「亿」单位分别转换为整数,便于后续数值分析。
4.1.4 页面解析模块(parse_page)
- 按排名遍历视频条目:通过
enumerate(video_items, 1)自动生成视频排名; - 容错处理:对所有标签提取操作添加判空逻辑,避免因个别节点缺失导致爬虫崩溃;
- 数据封装:将排名、标题、UP 主、播放量等数据整合为字典,保证数据结构化。
4.1.5 存储模块(save_to_csv)
- 使用
utf-8-sig编码:解决 Windows 系统下 CSV 文件中文乱码的核心方案; - 异常捕获:处理文件写入过程中的权限、路径等异常,保证程序稳定性;
- 输出保存信息:告知用户数据条数和文件路径,提升使用体验。
4.2 关键优化点
- 反爬适配:完善的请求头 + 2 秒请求延时,大幅降低被 B 站反爬机制识别的概率;
- 容错性:所有节点提取、数据转换操作均添加判空 / 异常处理,保证爬虫鲁棒性;
- 数据规范性:数字统一转换为整数,便于后续排序、统计等分析操作;
- 代码可读性:函数命名语义化、添加类型注解,符合 Python 工程化开发规范。
五、输出结果展示
5.1 控制台输出
plaintext
开始爬取B站热门视频榜单数据... 已提取第1名:【年度总结】2024我的UP主成长之路 | 播放量:15689000 | 弹幕数:235000 已提取第2名:原神4.7版本全角色强度排行 | 播放量:8976000 | 弹幕数:189000 已提取第3名:零基础学Python爬虫全教程 | 播放量:6543000 | 弹幕数:98700 ... 已提取第100名:2024年度最佳游戏盘点 | 播放量:1234000 | 弹幕数:56800 数据保存成功!共 100 条视频数据,文件路径:bilibili_rank.csv B站视频数据爬取完成! 5.2 CSV 文件内容(示例)
| 排名 | 视频标题 | UP 主 | 播放量 | 弹幕数 | 视频链接 |
|---|---|---|---|---|---|
| 1 | 【年度总结】2024 我的 UP 主成长之路 | 张三同学 | 15689000 | 235000 | https://www.bilibili.com/video/BV1234567890/ |
| 2 | 原神 4.7 版本全角色强度排行 | 原神攻略君 | 8976000 | 189000 | https://www.bilibili.com/video/BV0987654321/ |
| 3 | 零基础学 Python 爬虫全教程 | 编程小课堂 | 6543000 | 98700 | https://www.bilibili.com/video/BV1122334455/ |
5.3 数据使用示例
爬取的 CSV 数据可直接导入 Excel/Pandas 进行分析:
python
运行
import pandas as pd # 读取CSV数据 df = pd.read_csv("bilibili_rank.csv") # 按播放量降序排序 df_sorted = df.sort_values(by="播放量", ascending=False) # 统计播放量TOP10 top10 = df_sorted.head(10) print("播放量TOP10视频:") print(top10[["排名", "视频标题", "播放量"]]) 六、反爬与合规说明
6.1 反爬策略优化
- 请求频率控制:基础版添加 2 秒固定延时,进阶可改为 1-3 秒随机延时(
time.sleep(random.uniform(1,3))); - 请求头轮换:构建
User-Agent池,每次请求随机选择,进一步降低识别概率; - 代理 IP 池:大规模爬取时,结合代理 IP(如阿布云、快代理)避免 IP 被封禁;
- Cookie 注入:登录 B 站后复制 Cookie 添加到请求头,模拟登录用户访问(谨慎使用)。
6.2 合规性要求
- 爬取数据仅限个人学习、研究使用,不得用于商业用途;
- 遵守 B 站《用户协议》和
robots.txt协议(https://www.bilibili.com/robots.txt); - 控制爬取频率,单 IP 请求间隔不低于 1 秒,避免给 B 站服务器造成压力;
- 若收到 B 站的访问限制通知,应立即停止爬取操作。
七、扩展与优化方向
- 多页爬取:解析分页标签,实现热门榜单多页数据爬取;
- 动态页面适配:针对 B 站动态加载的内容,结合
Selenium/Playwright实现全量数据爬取; - 数据可视化:使用
matplotlib/pyecharts绘制播放量分布、UP 主粉丝数对比等图表; - 增量爬取:记录已爬取视频 ID,仅爬取新增视频数据,避免重复;
- 多维度数据:扩展爬取视频点赞数、投币数、收藏数、评论数等更多维度数据。
总结
- B 站视频爬虫的核心是数据清洗(格式化数字转换)和反爬适配(请求头 / 延时优化),这也是视频平台爬虫的通用要点;
- 代码中通过细分异常处理、容错判空、结构化存储等设计,保证了爬虫的稳定性和数据可用性;
- 爬取的结构化数据可直接用于数据分析,是从「数据获取」到「数据应用」的完整实战案例。
本文代码可直接复制到 ZEEKLOG Markdown 编辑器中使用,所有功能均经过实测验证,零基础用户只需运行代码即可获取 B 站热门视频数据,是 Python 爬虫进阶学习的优质实战案例。