1. 项目概述:一个B站UP主动态追踪器的诞生
最近在捣鼓一个挺有意思的小玩意儿,我把它叫做“B站UP主动态追踪器”。说白了,这就是一个能帮你自动监控Bilibili上你关注的UP主有没有发布新视频、新动态或者直播开播的小工具。灵感来源于我自己作为一个老B站用户,关注了上百个UP主,每天手动去刷新主页、查看动态,效率实在太低,还经常错过一些喜欢的UP主半夜“诈尸”更新的视频。
这个项目,Artistkisa/bilibili-up-update-tracker,核心目标就是解决这个痛点。它不是一个复杂的全功能B站客户端,而是一个轻量级、专注的“哨兵”。你可以把它想象成一个24小时不休息的私人助理,它只干一件事:盯紧你指定的UP主列表,一旦有风吹草动(新内容发布),就立刻通过你预设的方式(比如邮件、微信、Telegram机器人)通知你。这样一来,你就不用再被信息流淹没,或者因为错过更新而懊恼了。
它适合谁呢?首先是像我这样的深度B站用户,关注列表很长,有强烈的信息筛选需求。其次是内容创作者或运营人员,需要及时追踪竞品或同领域UP主的更新动态。再者,对于一些学习类、知识类UP主的粉丝,及时获取更新意味着能第一时间跟进学习。这个工具的技术栈并不高深,主要涉及网络请求、数据解析、定时任务和消息推送,非常适合有一定Python基础的朋友用来练手,也能给完全的新手展示一个完整的小项目是如何从想法到落地的。
2. 核心设计思路与架构选型
2.1 需求拆解与技术选型逻辑
做一个追踪器,听起来简单,但拆开来看,核心流程就四步:获取UP主信息 -> 解析最新动态 -> 与历史记录对比 -> 发送通知。每一步的技术选型,都直接关系到项目的稳定性、易用性和可维护性。
首先,如何获取UP主信息?最直接的想法当然是调用B站的官方API。但经过调研,B站对未登录状态和公开API的请求有一定频率限制,且部分关键API可能需要鉴权。对于个人小工具,最稳定、最通用的方式其实是模拟浏览器请求,直接抓取UP主的个人空间页面。这里我选择了requests库来处理HTTP请求,配合BeautifulSoup4来解析HTML页面。为什么不直接用API?因为公开API可能变动,而网页结构相对稳定,且不需要处理复杂的签名和令牌,对于我们的核心需求——获取视频、动态、直播列表——完全够用。
其次,如何识别“新”内容?这就需要本地持久化存储上一次检查时的状态。最简单的方案就是用一个文件(如JSON)记录每个UP主最新一条内容的唯一标识(如视频的BV号、动态的ID)和发布时间。每次检查时,将线上获取的最新内容ID与本地记录的对比,如果不一致,就说明有更新。数据库(如SQLite)当然更强大,但对于初期版本,JSON文件更轻量,无需额外服务,读写也方便。
然后,如何定时检查?我们不可能手动运行脚本。在服务器上,最经典的就是cron定时任务。但在项目内,为了跨平台和易于集成,我使用了Python内置的schedule库,或者更简单的time.sleep循环。考虑到健壮性,我选择了schedule,它能更清晰地管理定时任务规则。
最后,也是体验最关键的一环:如何通知?通知渠道必须可靠、及时且对用户友好。我设计了多通道支持:
- 邮件(SMTP):最通用,几乎所有邮箱都支持。配置好发件邮箱(建议使用QQ邮箱、163邮箱的SMTP服务)和接收邮箱即可。
- Server酱(微信通知):国内开发者很喜欢的工具,通过关注其公众号,绑定后可以用API向自己的微信发送消息,非常方便。
- Telegram Bot:对于有海外环境的用户,Telegram机器人的API稳定且功能强大,推送格式也丰富。 我通过抽象一个
Notifier基类,让每种通知方式实现自己的send方法,这样后续扩展新的通知渠道(如钉钉、Slack)会非常容易。
2.2 项目目录结构设计
一个清晰的结构是项目可维护的基础。我的目录结构如下:
bilibili-tracker/ ├── config.yaml # 配置文件,存放UP主列表、通知设置、检查间隔等 ├── history.json # 数据存储文件,记录每个UP主的最新内容状态 ├── main.py # 主程序入口 ├── core/ # 核心逻辑模块 │ ├── __init__.py │ ├── fetcher.py # 负责抓取和解析UP主页面 │ ├── comparator.py # 负责比较新旧内容,判断是否更新 │ ├── notifier.py # 通知发送模块的基类和具体实现 │ └── storage.py # 负责读写 history.json ├── notify_services/ # 具体通知服务实现 │ ├── __init__.py │ ├── email_notifier.py │ ├── wechat_notifier.py │ └── telegram_notifier.py └── utils/ # 工具函数 ├── __init__.py └── logger.py # 日志记录,便于排查问题使用config.yaml而不是config.py是为了将配置与代码分离,用户无需修改Python代码就能调整设置,更安全也更友好。history.json是项目的“记忆中枢”,它的结构设计至关重要。
3. 核心模块深度解析与实现细节
3.1 页面抓取与解析:Fetcher模块
这是整个项目的“眼睛”。我们的目标是从一个UP主的空间主页(例如https://space.bilibili.com/123456)中,提取出视频、动态、直播这三类信息。
实现步骤与难点:
构造请求头:这是绕过反爬的基础。需要模拟一个真实的浏览器请求,至少包含
User-Agent。我通常会使用一个常见的Chrome浏览器User-Agent字符串。headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com/' }获取并解析页面:使用
requests.get获取HTML,然后用BeautifulSoup解析。关键在于找到正确的CSS选择器。B站的页面结构比较复杂,而且可能经常微调。我通过浏览器开发者工具,仔细分析了UP主空间页面的结构。- 视频列表:通常位于一个ID为
submit-video-list的div下的li标签中。每个li包含视频的标题、BV号(>{ "123456": { "video": { "identifier": "BV1xx411c7LU", "publish_time": "2023-10-27 20:00:00", "title": "最新视频标题" }, "dynamic": { "identifier": "1234567890123456789", "publish_time": "2023-10-27 21:30:00", "text": "最新动态内容摘要..." }, "live": { "is_living": false, "room_id": "213" } } }这种结构使得查询和更新都非常高效。
3.3 多渠道通知实现:Notifier模块
通知是用户感知项目的直接方式,必须稳定、及时、信息清晰。我采用了策略模式,将通知发送抽象成独立的服务。
以邮件通知为例,关键配置和实现:
配置获取:从
config.yaml读取SMTP服务器地址、端口、发件邮箱、授权码(注意不是邮箱密码)、接收邮箱列表。notifier: email: enabled: true smtp_server: smtp.qq.com smtp_port: 465 sender: your_email@qq.com password: your_authorization_code # QQ邮箱获取的授权码 receivers: - receiver1@example.com - receiver2@example.com邮件构造:使用Python的
smtplib和email库。邮件主题要清晰,如【B站追踪】UP主“老番茄”发布了新视频。正文内容采用HTML格式,可以更美观地展示视频标题、封面图(以链接形式)、直达链接和发布时间。def send(self, update): msg = MIMEMultipart('alternative') msg['Subject'] = f【B站追踪】{update.up_name}发布了新{update.content_type} msg['From'] = self.sender msg['To'] = ', '.join(self.receivers) # 构造HTML正文... html_part = MIMEText(html_body, 'html', 'utf-8') msg.attach(html_part) # 连接服务器并发送 with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server: server.login(self.sender, self.password) server.send_message(msg)
Server酱和Telegram Bot的实现思路类似:
- Server酱:向其提供的API地址发送一个简单的HTTP POST请求,包含
title和desp(描述) 字段即可,微信端就能收到。 - Telegram Bot:首先需要通过
@BotFather创建一个Bot,获取Token。然后使用requests调用Telegram Bot API的sendMessage方法,将消息发送到指定的聊天ID(你的私人聊天或群组)。
实操心得:通知内容的信息密度很重要。不要只发“XXX更新了”,而要把关键信息一次性给全:UP主名、内容类型、标题、直达链接、发布时间。对于视频,可以附上封面图链接;对于动态,可以截取前100字作为预览。这样用户一眼就能决定是否立即查看,提升了工具的价值。
4. 完整部署与运行流程
4.1 环境准备与配置详解
假设你已经在本地或一台云服务器(如腾讯云轻量应用服务器、阿里云ECS)上准备好了Python环境(建议3.7及以上)。
克隆项目与安装依赖:
git clone https://github.com/Artistkisa/bilibili-up-update-tracker.git cd bilibili-up-update-tracker pip install -r requirements.txtrequirements.txt文件应包含:requests>=2.25.1 beautifulsoup4>=4.9.3 PyYAML>=5.4.1 schedule>=1.1.0 tenacity>=8.0.1 # 用于重试编写配置文件
config.yaml:这是最重要的步骤。你需要根据注释仔细填写。# 要监控的UP主列表,key为自定义名称(方便识别),value为B站用户MID(空间网址最后的数字) up_list: 老番茄: 777536 罗翔说刑法: 517327498 手工耿: 280793434 # 检查间隔(秒)。不建议设置过短,建议300秒(5分钟)以上,避免给B站服务器造成压力 check_interval: 300 # 通知配置 notifier: # 邮件通知 email: enabled: false # 设为true启用 smtp_server: smtp.qq.com smtp_port: 465 sender: your_email@qq.com password: your_auth_code # 注意是授权码 receivers: - your_main_email@example.com # Server酱(微信)通知 wechat: enabled: false # 设为true启用 sendkey: your_sendkey # 在Server酱官网获取 # Telegram Bot通知 telegram: enabled: false # 设为true启用 bot_token: your_bot_token # 通过@BotFather获取 chat_id: your_chat_id # 你的Telegram用户ID或群组ID如何获取UP主的MID?打开UP主的B站空间主页,地址栏URL中
space.bilibili.com/后面的数字就是MID。如何获取邮箱授权码?以QQ邮箱为例,登录后进入“设置”->“账户”,找到“POP3/IMAP/SMTP服务”项,生成授权码。如何获取Server酱的SendKey?访问Server酱官网,用GitHub登录后即可获得。如何获取Telegram的Chat ID?可以先给Bot发条消息,然后访问https://api.telegram.org/bot<YourBOTToken>/getUpdates就能看到包含chat.id的响应。
4.2 主程序逻辑与运行方式
主程序
main.py的流程是一个清晰的循环:import time import schedule from core.fetcher import BilibiliFetcher from core.comparator import UpdateComparator from core.storage import JsonStorage from notify_services import get_enabled_notifiers # 一个工厂函数,根据配置返回启用的通知器列表 def job(): """定时执行的任务""" storage = JsonStorage('history.json') fetcher = BilibiliFetcher() comparator = UpdateComparator(storage) notifiers = get_enabled_notifiers() # 从配置加载 config = load_config('config.yaml') for up_name, up_mid in config['up_list'].items(): print(f正在检查 {up_name} ({up_mid})...) # 1. 抓取 updates = fetcher.fetch_all(up_mid, up_name) # 2. 比对 new_updates = comparator.compare(up_mid, updates) # 3. 通知 for update in new_updates: for notifier in notifiers: try: notifier.send(update) print(f已通过 {notifier.name} 发送通知: {update.title}) except Exception as e: print(f{notifier.name} 通知发送失败: {e}') # 4. 更新存储 storage.update(up_mid, updates) if __name__ == '__main__': # 立即运行一次 job() # 然后按配置间隔定时运行 schedule.every(config['check_interval']).seconds.do(job) while True: schedule.run_pending() time.sleep(1)运行与后台常驻:
- 直接运行:在终端执行
python main.py。程序会立即检查一次,然后每隔设定的间隔循环执行。按Ctrl+C可以终止。 - 后台运行(Linux/Mac):使用
nohup命令让它在后台运行,并将日志输出到文件:nohup python main.py > tracker.log 2>&1 &。 - 使用进程管理工具:对于生产环境,更推荐使用
systemd(Linux) 或supervisor来管理进程,可以实现开机自启、崩溃重启、日志轮转等功能。
4.3 配置系统服务(以systemd为例)
在Linux服务器上,创建一个系统服务可以让追踪器更稳定地运行。
- 创建服务文件:
sudo vim /etc/systemd/system/bilibili-tracker.service - 写入以下内容(根据你的实际路径修改):
[Unit] Description=Bilibili UP Update Tracker After=network.target [Service] Type=simple User=your_username WorkingDirectory=/path/to/bilibili-up-update-tracker ExecStart=/usr/bin/python3 /path/to/bilibili-up-update-tracker/main.py Restart=on-failure RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target - 启用并启动服务:
sudo systemctl daemon-reload sudo systemctl enable bilibili-tracker.service sudo systemctl start bilibili-tracker.service - 查看状态和日志:
sudo systemctl status bilibili-tracker.service sudo journalctl -u bilibili-tracker.service -f
5. 常见问题排查与优化经验
在实际部署和运行中,你肯定会遇到各种各样的问题。下面是我踩过坑后总结的一些典型问题及其解决方法。
5.1 抓取失败或数据解析错误
问题表现:日志中频繁出现
requests.exceptions.RequestException或AttributeError(BeautifulSoup找不到对象)。可能原因与解决方案:
IP被暂时限制:这是最常见的问题。B站对高频请求会进行限制。
- 解决:首要方法是大幅增加检查间隔,我建议至少300秒(5分钟)一次。对于监控几十个UP主,这个频率完全足够。
- 优化:在
fetcher.py的请求函数中加入随机延迟time.sleep(random.uniform(1, 3)),模拟人类操作。 - 进阶:如果确实需要高频,可以考虑使用代理IP池,但这大大增加了项目复杂度,背离了轻量化的初衷。
网页结构变更:B站前端改版,导致我们写的CSS选择器失效。
- 解决:这是无法避免的。需要定期维护代码。当发现抓取不到数据时,用浏览器开发者工具重新分析页面结构,更新
fetcher.py中的选择器或API接口地址。 - 预防:将选择器字符串、API URL模板等易变部分提取到配置文件或常量文件中,方便统一修改。
- 解决:这是无法避免的。需要定期维护代码。当发现抓取不到数据时,用浏览器开发者工具重新分析页面结构,更新
动态数据接口变更:动态数据通过API获取,接口地址或参数可能变化。
- 解决:同样需要重新抓包分析。在浏览器中打开UP主动态页,按F12进入“网络”(Network)选项卡,过滤XHR/Fetch请求,寻找包含动态数据的请求,观察其URL和参数格式。
5.2 通知发送失败
邮件通知失败:
- SMTP连接错误:检查
smtp_server和smtp_port是否正确。QQ邮箱SSL端口是465或587。 - 认证失败:99%的情况是密码填错了。请务必使用邮箱的授权码,而不是登录密码。检查发件邮箱是否已开启SMTP服务。
- 被接收方邮件服务器拒收:如果发件邮箱是免费邮箱且发送量突然增大,可能被判定为垃圾邮件。可以尝试在邮件正文中加入“此邮件为自动发送的更新通知,请勿直接回复”等说明,或者使用更具信誉度的邮箱服务。
Server酱/Telegram通知失败:
- 配置错误:仔细检查
sendkey或bot_token和chat_id是否复制完整,有无多余空格。 - 网络问题:Server酱和Telegram的API服务器在海外,国内服务器直接访问可能不稳定或超时。可以考虑在请求时设置合理的超时时间(如10秒),并添加重试机制。
5.3 历史记录文件异常
问题表现:
history.json文件损坏、格式错误,导致程序无法读取或对比出错。原因与解决:
- 程序异常中断:在写入文件时程序崩溃,可能导致JSON文件不完整。
- 解决:在
storage.py的update方法中,采用“先写临时文件,再替换原文件”的策略。这是原子操作,可以避免文件损坏。
def update(self, up_mid, latest_updates): # ... 更新内存中的数据 ... # 先写入临时文件 temp_file = self.file_path + '.tmp' with open(temp_file, 'w', encoding='utf-8') as f: json.dump(self.data, f, ensure_ascii=False, indent=2) # 原子替换 os.replace(temp_file, self.file_path) - 解决:在
- 手动编辑错误:用户直接修改
history.json导致语法错误。- 解决:程序启动时,可以尝试读取JSON文件,如果解析失败,则记录错误并尝试从备份中恢复(如果你实现了备份功能),或者初始化一个空的历史记录。同时,在日志中明确警告用户不要手动修改该文件。
5.4 性能与扩展性考量
当监控的UP主数量增加到几百甚至上千时,简单的循环和文件存储可能会遇到瓶颈。
优化方向:
- 异步抓取:使用
asyncio和aiohttp库,可以并发地对所有UP主进行抓取,而不是一个一个顺序执行,能极大缩短单次检查的总耗时。 - 数据库存储:当数据量变大,JSON文件的读写和查询效率会变低。可以迁移到轻量级的SQLite数据库,甚至更专业的如PostgreSQL。数据库能更方便地支持查询历史记录、统计更新频率等高级功能。
- 任务队列:将“抓取”、“比对”、“通知”拆解成独立的任务,放入消息队列(如Redis)中,由不同的工作进程消费。这能提高系统的并发处理能力和可靠性。
- 分布式部署:如果需要监控的UP主数量极其庞大,可以考虑将UP主列表分片,部署多个追踪器实例,每个实例负责一部分UP主。
当然,对于99%的个人用户场景,监控几十到一两百个UP主,当前这个基于文件、同步请求的架构已经绰绰有余,简单可靠才是第一要义。
6. 项目进阶与个性化改造
这个基础框架搭建好后,就有了无限的扩展可能。你可以根据自己的需求,把它改造成更强大的工具。
1. 增加更多内容类型监控:
- 专栏文章:监控UP主发布的专栏更新。
- 收藏夹:监控特定公开收藏夹里是否有新视频加入。
- 评论区高赞回复:监控UP主在自己视频下的高赞回复(需要处理鉴权)。
2. 丰富通知内容和格式:
- 聚合通知:不要一有更新就发一条,可以每小时或每天聚合一次,将所有更新整理成一份“简报”发送,减少打扰。
- 富媒体通知:Telegram Bot支持发送图片。可以在通知里直接附上视频封面图(下载到本地或使用图床链接)。
- 自定义通知模板:允许用户在配置文件中自定义通知的标题和正文模板,满足个性化需求。
3. 增加Web管理界面:使用Flask或FastAPI搭建一个简单的Web界面,让用户可以通过浏览器添加/删除UP主、查看更新历史、手动触发检查,而不用去修改YAML配置文件和重启程序。
4. 实现“智能过滤”:不是所有更新都值得通知。可以增加过滤规则,例如:
- 关键词过滤:只通知标题或动态内容中包含特定关键词(如“教程”、“发布”、“抽奖”)的更新。
- 视频时长过滤:只通知时长大于10分钟的视频(过滤掉短视频)。
- 动态类型过滤:只通知“转发动态”或“原创动态”。
5. 与其他工具集成:
- RSS生成:为每个监控的UP主生成一个RSS订阅源,这样你就可以用任何RSS阅读器来订阅了。
- Discord/钉钉/Slack通知:参照已有的通知器模式,很容易添加新的通知渠道。
- 数据导出与分析:将更新历史定期导出,分析你关注的UP主的活跃时间段、更新频率等。
这个项目的魅力就在于,它从一个简单的需求出发,代码结构清晰,每个模块职责分明,使得后续的扩展和维护变得非常容易。你可以只花一两个小时就搭好基础版用起来,也可以把它当作一个练手项目,逐步添加上面提到的各种高级功能,在这个过程中深入学习HTTP通信、数据解析、任务调度、消息队列、Web开发等多个方面的知识。
- 视频列表:通常位于一个ID为