1. 项目背景与核心需求
最近在做一个B站数据分析项目时,发现市面上缺少一个轻量级的、可持续更新的数据采集方案。大多数现成工具要么功能过于复杂,要么无法实现增量采集和去重存储。于是决定自己动手开发这个"B站情报站"系统,专门用于UP主和分区视频数据的自动化采集。
这个系统的核心要解决三个问题:
- 如何高效获取B站UP主和分区视频的基础数据(播放量、点赞、收藏等)
- 如何实现增量采集避免重复获取已存在的数据
- 如何将采集结果持久化存储并支持灵活导出
经过两周的开发和调试,最终实现了一个日均能稳定采集5万+条数据的系统。下面分享具体实现方案和踩过的坑。
2. 技术选型与架构设计
2.1 为什么选择Python作为开发语言
Python在爬虫领域有天然优势:
- Requests/BeautifulSoup等成熟库简化了HTTP请求和HTML解析
- 丰富的异步IO支持(aiohttp、asyncio)提升采集效率
- Pandas等数据分析库方便后续处理
- 代码可读性强,便于维护和扩展
对比过Node.js和Java的方案后,最终选择Python 3.8+作为开发环境,主要考虑到开发效率和生态完善度。
2.2 核心组件设计
系统采用分层架构:
code复制采集层 -> 处理层 -> 存储层 -> 输出层
-
采集层:负责与B站API/网页交互
- 使用aiohttp实现异步请求
- 自定义请求头模拟浏览器行为
- 实现自动重试和代理切换机制
-
处理层:
- 数据清洗(去除HTML标签、格式化时间等)
- 增量比对(基于视频bvid去重)
- 数据校验(检查关键字段完整性)
-
存储层:
- SQLite作为主数据库(轻量、无需服务)
- CSV作为辅助导出格式
-
输出层:
- 命令行实时进度显示
- 生成带时间戳的CSV文件
- 数据库可视化工具支持
3. 关键实现细节
3.1 B站数据接口分析
B站没有公开完整的API文档,需要通过浏览器开发者工具分析:
- UP主信息接口:
code复制https://api.bilibili.com/x/space/acc/info?mid={uid}
返回JSON包含昵称、签名、粉丝数等基础信息
- UP主视频列表:
code复制https://api.bilibili.com/x/space/wbi/arc/search?mid={uid}
需要处理WBI签名验证,返回视频列表和分页信息
- 分区视频列表:
code复制https://api.bilibili.com/x/web-interface/web/region?rid={分区ID}
重要提示:请求频率需控制在合理范围(建议≤5次/秒),避免触发反爬
3.2 增量采集实现方案
核心思路是通过视频唯一标识bvid进行去重:
python复制def is_new_video(bvid):
cursor.execute("SELECT 1 FROM videos WHERE bvid=?", (bvid,))
return cursor.fetchone() is None
采集流程:
- 获取UP主最新视频列表
- 遍历列表检查bvid是否已存在
- 只插入新发现的视频记录
- 记录最后采集时间用于下次增量
3.3 数据持久化方案
SQLite数据库设计
sql复制CREATE TABLE up_info (
uid INTEGER PRIMARY KEY,
name TEXT,
fans INTEGER,
last_update TIMESTAMP
);
CREATE TABLE videos (
bvid TEXT PRIMARY KEY,
title TEXT,
up_uid INTEGER,
view INTEGER,
danmaku INTEGER,
reply INTEGER,
favorite INTEGER,
coin INTEGER,
share INTEGER,
pubdate TIMESTAMP,
FOREIGN KEY(up_uid) REFERENCES up_info(uid)
);
CSV导出实现
python复制def export_to_csv(data, filename):
df = pd.DataFrame(data)
df.to_csv(
f"{filename}_{datetime.now().strftime('%Y%m%d')}.csv",
index=False,
encoding='utf_8_sig'
)
4. 实战中的问题与解决方案
4.1 反爬机制突破
B站的反爬策略包括:
- WBI签名验证 - 需要从网页JS中提取算法
- 请求频率限制 - 需要添加随机延迟
- Cookie验证 - 需要定期更新Cookie
解决方案:
python复制# 在请求头中添加随机延迟
async def fetch(url):
await asyncio.sleep(random.uniform(0.5, 1.5))
headers = {
'User-Agent': random.choice(USER_AGENTS),
'Cookie': get_valid_cookie()
}
async with session.get(url, headers=headers) as resp:
return await resp.json()
4.2 数据一致性保障
遇到的主要问题:
- 视频数据字段可能缺失(如新视频没有分享数)
- UP主可能更改昵称导致历史数据不一致
处理方案:
- 设置字段默认值
python复制video_data.get('share', 0) # 缺失时返回0
- 建立数据版本控制
sql复制ALTER TABLE up_info ADD COLUMN name_history TEXT;
4.3 性能优化技巧
- 批量插入提升SQLite写入速度
python复制cursor.executemany(
"INSERT OR IGNORE INTO videos VALUES (?,?,?,?,?,?,?,?,?,?)",
video_list
)
- 使用内存数据库加速临时操作
python复制temp_db = sqlite3.connect(':memory:')
# 处理完成后再写入磁盘数据库
- 合理使用索引
sql复制CREATE INDEX idx_videos_up ON videos(up_uid);
CREATE INDEX idx_videos_date ON videos(pubdate);
5. 完整使用示例
5.1 采集指定UP主数据
python复制from bilibili_spider import BilibiliSpider
spider = BilibiliSpider(database='bilibili.db')
spider.crawl_up(uid='123456') # 替换为目标UP主UID
spider.export_csv('up_videos')
5.2 采集分区视频数据
python复制# 动画分区rid=1
spider.crawl_region(rid=1, pages=10) # 采集前10页
5.3 数据库查询示例
python复制import sqlite3
from tabulate import tabulate
conn = sqlite3.connect('bilibili.db')
df = pd.read_sql("""
SELECT name, title, view, favorite
FROM videos JOIN up_info ON up_uid=uid
ORDER BY view DESC LIMIT 10
""", conn)
print(tabulate(df, headers='keys', tablefmt='psql'))
6. 扩展思路与建议
-
定时任务集成:
可以使用APScheduler实现每天自动采集:python复制from apscheduler.schedulers.blocking import BlockingScheduler sched = BlockingScheduler() @sched.scheduled_job('cron', hour=3) def daily_job(): spider.crawl_up(uid='123456') sched.start() -
数据可视化扩展:
使用Matplotlib生成基础统计图表:python复制df.plot(x='pubdate', y='view', kind='line') plt.title('视频播放量趋势') plt.savefig('views_trend.png') -
异常处理建议:
- 网络请求添加重试机制
- 数据库操作使用事务
- 关键步骤添加日志记录
在实际使用中,建议先从少量数据开始测试,确认无误后再进行全量采集。对于热门UP主,可以适当增加采集频率(如每6小时一次),而冷门UP主可以降低到每周一次。
