基于RISC-V开发板的B站消息监测终端:Python脚本与硬件交互实践
1. 项目概述与核心思路拆解
最近在折腾一块性能不错的RISC-V开发板,总琢磨着怎么把它用起来,而不是让它吃灰。正好,我每天都会刷B站,但有时候忙起来或者摸鱼摸得不够专业,会错过一些重要的私信、评论或者系统通知。虽然B站有App推送,但一来手机通知容易被淹没,二来总感觉缺了点极客的仪式感。于是,一个想法冒了出来:能不能用这块开发板,配合一个自己写的Python脚本,来实时监测B站的未读消息,并用一种更直观、更物理化的方式提醒我呢?比如让板载的LED灯闪烁,或者通过一个小屏幕显示未读数。
这个项目的核心,其实就是实现一个轻量级的、可定制的B站消息监测终端。它不依赖手机App,而是直接与B站的服务端API进行交互,定时轮询未读消息状态,再通过开发板的硬件接口给出反馈。听起来好像就是个“网络请求+硬件控制”的小程序,但里面涉及到几个关键的技术点:如何安全地模拟登录B站、如何解析其API返回的复杂数据、如何在资源相对有限的嵌入式环境下稳定运行Python网络程序,以及如何设计一个合理的轮询策略以避免对B站服务器造成不必要的压力或被风控。
选择RISC-V开发板,一方面是出于对开源架构的兴趣和手头有现成设备,另一方面也是想验证在非x86/ARM的主流生态下,用Python做这种网络+硬件交互项目的可行性。实测下来,只要选对工具链和库,体验相当流畅。
2. 技术选型与环境搭建要点
工欲善其事,必先利其器。在动手写代码之前,我们需要明确技术栈并搭建好开发环境。
2.1 硬件平台选择:为何是RISC-V?
我使用的是一款基于阿里平头哥C906核心的RISC-V开发板,主频在1GHz以上,内存有512MB或1GB的版本。选择它主要基于几点考虑:
- 性能足够:监测脚本本身计算量不大,但网络请求和JSON解析需要一定的CPU资源。这款板子的性能足以流畅运行一个完整的Linux发行版(如Debian)和Python环境。
- GPIO与外围设备:板子自带用户可编程的LED灯,并且通过排针引出了标准的GPIO、I2C、SPI等接口,方便后续扩展(例如连接OLED屏幕)。
- 社区与生态:虽然相比树莓派,RISC-V的生态还在成长,但这款板子的Linux系统镜像、基础驱动支持都比较完善,降低了底层折腾的成本。
当然,如果你手头有树莓派、香橙派或者其他ARM开发板,这个项目完全可以无缝迁移,原理和代码几乎通用。
2.2 软件栈与依赖库解析
脚本的核心逻辑在Python 3上实现。主要依赖以下几个库:
requests:用于发送HTTP请求到B站API。这是最核心的网络库,比标准库的urllib更简洁易用。json:用于解析B站API返回的JSON格式数据。time/datetime:用于实现定时任务和日志时间戳。logging:用于记录脚本运行日志,便于排查问题。- 硬件控制库:根据你的开发板型号和要驱动的设备而定。
- 如果只是控制板载LED,Linux下通常可以通过读写
/sys/class/leds/目录下的文件来实现,无需额外库。 - 如果需要驱动I2C的OLED屏幕,可能需要
smbus2或Adafruit_SSD1306等库。
- 如果只是控制板载LED,Linux下通常可以通过读写
安装这些库非常简单,在开发板的Linux终端里执行:
sudo apt update sudo apt install python3-pip pip3 install requests # 如果需要OLED屏幕 pip3 install smbus2 pillow Adafruit-SSD1306注意:在嵌入式设备上,建议优先使用系统包管理器(
apt)安装库,如果版本不满足再用pip。同时,注意pip安装时可能涉及本地编译,确保设备上有基本的编译工具链(build-essential)。
2.3 B站API接口分析与逆向
这是项目最具挑战性的部分。B站没有公开的、稳定的官方消息API文档供第三方使用。因此,我们需要通过“抓包”的方式,分析B站客户端或网页端的网络请求,找到获取未读消息的那个关键请求。
常用方法:
- 使用浏览器开发者工具:在电脑浏览器上登录B站网页版,打开“网络(Network)”选项卡,筛选XHR/Fetch请求。然后点击页面上的“消息”图标,观察此时产生了哪些网络请求。寻找包含“unread”、“message”、“count”等关键词的请求。
- 使用抓包工具:如Charles、Fiddler或mitmproxy,对手机App的流量进行抓包分析。这种方法能获取到更接近原生App的API。
经过分析,我找到了核心接口(请注意,B站的接口路径和参数可能随时变更,以下为示例):
- 接口地址:
https://api.bilibili.com/x/msgfeed/unread - 请求方法:GET
- 必要认证:需要在请求头(Headers)中携带用户的登录凭证(Cookie)。
Cookie的获取与安全:
- 获取:在浏览器登录B站后,从开发者工具的“应用(Application)” -> “Cookies”中,可以找到
SESSDATA、bili_jct、DedeUserID等关键字段。将这些字段组合成Cookie字符串。 - 安全警告:Cookie等同于你的账号密码,绝对不要直接硬编码在脚本里,更不要上传到公开的代码仓库(如GitHub)。
- 正确做法:将Cookie存储在开发板本地的一个配置文件(如
config.json)中,并设置该文件权限为仅当前用户可读(chmod 600 config.json)。 - 进阶思路:可以考虑实现一个简单的OAuth2授权流程,但这对于个人脚本来说过于复杂。更务实的做法是定期手动更新Cookie。
- 正确做法:将Cookie存储在开发板本地的一个配置文件(如
3. Python脚本核心实现详解
有了前面的铺垫,我们来一步步构建脚本。我将脚本命名为bilibili_monitor.py。
3.1 配置文件与常量定义
首先,创建一个config.json文件来存放敏感信息和配置:
{ "cookies": { "SESSDATA": "你的SESSDATA值", "bili_jct": "你的bili_jct值", "DedeUserID": "你的DedeUserID" }, "check_interval_seconds": 300, "led_path": "/sys/class/leds/board:green:user/brightness" }check_interval_seconds是轮询间隔,设为300秒(5分钟)是一个比较友好的频率,既不会错过重要消息,也不会对B站服务器造成骚扰。
在Python脚本开头,我们读取配置并定义常量:
import json import requests import time import logging from pathlib import Path # 读取配置 CONFIG_PATH = Path.home() / '.config' / 'bilibili_monitor' / 'config.json' with open(CONFIG_PATH, 'r') as f: config = json.load(f) COOKIES = config['cookies'] CHECK_INTERVAL = config['check_interval_seconds'] LED_PATH = config.get('led_path') # 硬件相关,可能为空 # API地址 (示例,需根据实际情况调整) API_UNREAD = "https://api.bilibili.com/x/msgfeed/unread" HEADERS = { 'User-Agent': 'Mozilla/5.0 (X11; Linux riscv64) AppleWebKit/537.36', 'Referer': 'https://www.bilibili.com/' } # 设置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('/var/log/bilibili_monitor.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__)3.2 消息获取与解析函数
这是脚本的核心函数,负责请求API并解析未读数。
def get_unread_count(): """ 获取B站未读消息总数 返回: 成功返回未读数(int),失败返回-1 """ try: # 构造Cookie字符串 cookie_str = '; '.join([f'{k}={v}' for k, v in COOKIES.items()]) headers = HEADERS.copy() headers['Cookie'] = cookie_str response = requests.get(API_UNREAD, headers=headers, timeout=10) response.raise_for_status() # 检查HTTP错误 data = response.json() # B站API返回格式通常为 {"code":0, "data":{...}, "message":"0"} if data.get('code') == 0: # 需要根据实际API返回结构解析,这里是一个示例 # 假设返回的data结构为: {"at": 2, "reply": 5, "like": 1, ...} unread_data = data.get('data', {}) # 计算所有类型的未读消息总和 total_unread = sum([v for v in unread_data.values() if isinstance(v, int)]) logger.info(f"未读消息总数: {total_unread}") return total_unread else: logger.error(f"API返回错误: {data.get('message')}") return -1 except requests.exceptions.RequestException as e: logger.error(f"网络请求失败: {e}") return -1 except json.JSONDecodeError as e: logger.error(f"JSON解析失败: {e}") return -1 except KeyError as e: logger.error(f"解析API数据结构时键错误: {e}") return -1实操心得:B站的API返回结构可能非常复杂且嵌套很深。务必使用
logger详细记录下完整的返回数据(data变量),然后仔细分析其结构,找到真正的未读数字段。我在这里就踩过坑,最初只解析了第一层,漏掉了嵌套在更深层的未读数。
3.3 硬件反馈控制函数
根据未读数,控制开发板上的硬件给出反馈。这里以实现最简单的LED闪烁为例。
def hardware_feedback(unread_count): """ 根据未读数控制硬件反馈 :param unread_count: 未读消息数 """ if not LED_PATH or unread_count < 0: return led_file = Path(LED_PATH) if not led_file.exists(): logger.warning(f"LED路径不存在: {LED_PATH}") return try: if unread_count == 0: # 没有未读,熄灭LED led_file.write_text('0') logger.debug("LED已熄灭") else: # 有未读,让LED闪烁 # 闪烁模式:快速闪烁几次后长亮,循环 for _ in range(min(unread_count, 5)): # 最多闪烁5次 led_file.write_text('1') time.sleep(0.2) led_file.write_text('0') time.sleep(0.2) # 闪烁结束后,如果还有未读,则长亮作为持续提醒 if unread_count > 0: led_file.write_text('1') logger.debug(f"LED已根据未读数 {unread_count} 进行反馈") except IOError as e: logger.error(f"控制LED时发生IO错误: {e}")扩展思路:如果你连接了OLED屏幕,可以在这个函数里添加显示逻辑,比如调用Adafruit_SSD1306库,在屏幕上绘制一个简单的界面,显示“B站未读:X”。
3.4 主循环与异常处理
最后,将上述函数组合起来,形成一个持续运行的主循环。
def main(): logger.info("B站消息监测脚本启动") last_unread = 0 while True: try: current_unread = get_unread_count() if current_unread >= 0: # 获取成功 if current_unread != last_unread: logger.info(f"未读消息数发生变化: {last_unread} -> {current_unread}") hardware_feedback(current_unread) last_unread = current_unread else: logger.debug(f"未读消息数未变: {current_unread}") else: logger.warning("获取未读数失败,本次跳过硬件反馈") # 等待下一个检查周期 time.sleep(CHECK_INTERVAL) except KeyboardInterrupt: logger.info("检测到键盘中断,脚本退出") # 退出前确保LED熄灭 if LED_PATH: try: Path(LED_PATH).write_text('0') except: pass break except Exception as e: logger.critical(f"主循环发生未预期错误: {e}", exc_info=True) # 发生严重错误,等待更长时间后重试,避免疯狂刷日志 time.sleep(CHECK_INTERVAL * 3) if __name__ == '__main__': main()注意事项:
time.sleep(CHECK_INTERVAL)是简单的定时方式。对于更精确的定时任务,可以考虑使用schedule库或者系统的cron来定时执行脚本。但使用主循环+sleep的方式,可以保持硬件反馈状态的持续性(比如LED长亮)。
4. 部署、优化与问题排查
脚本写好了,接下来就是把它放到开发板上跑起来,并让它稳定可靠地运行。
4.1 系统服务化部署
我们不希望一直开着一个终端运行python3 bilibili_monitor.py。最好的方式是将其注册为一个系统服务(如systemd服务),实现开机自启和后台运行。
- 创建服务文件:在开发板上创建
/etc/systemd/system/bilibili-monitor.service[Unit] Description=Bilibili Unread Message Monitor After=network.target [Service] Type=simple User=pi # 替换为你的用户名,如debian WorkingDirectory=/home/pi/bilibili_monitor # 替换为你的脚本所在目录 ExecStart=/usr/bin/python3 /home/pi/bilibili_monitor/bilibili_monitor.py Restart=on-failure RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target - 启用并启动服务:
sudo systemctl daemon-reload sudo systemctl enable bilibili-monitor.service sudo systemctl start bilibili-monitor.service - 查看服务状态和日志:
sudo systemctl status bilibili-monitor.service sudo journalctl -u bilibili-monitor.service -f
4.2 性能优化与资源考量
在资源受限的开发板上运行长期服务,需要注意资源使用。
- 内存:
requests库会占用一些内存。如果内存非常紧张(如只有256MB),可以考虑使用更轻量的urequests(MicroPython版)或http.client标准库,但会牺牲易用性。 - CPU与网络:轮询间隔
CHECK_INTERVAL是关键。太短(如10秒)会增加CPU和网络负担,也可能触发B站的风控机制。5-10分钟是一个比较合理的区间。 - 日志管理:日志文件会不断增长。需要配置日志轮转(logrotate)。可以编辑
/etc/logrotate.d/bilibili-monitor:/var/log/bilibili_monitor.log { daily rotate 7 compress delaycompress missingok notifempty create 644 root root }
4.3 常见问题与排查实录
在实际运行中,你可能会遇到以下问题:
问题1:脚本运行后,日志显示“API返回错误:-401”或“未登录”。
- 原因:Cookie失效了。B站的登录状态(Cookie)通常有一定有效期,也可能因为异地登录等安全原因失效。
- 解决:重新在浏览器登录B站,获取新的Cookie值,更新
config.json文件,然后重启服务:sudo systemctl restart bilibili-monitor.service。
问题2:LED灯没有任何反应。
- 排查步骤:
- 检查路径:首先确认
LED_PATH配置是否正确。可以通过ls /sys/class/leds/命令查看可用的LED设备。 - 手动测试:在终端执行
echo 1 | sudo tee /sys/class/leds/你的led路径/brightness看LED是否能亮起。 - 检查权限:运行服务的用户(如
pi)是否有权限写入LED文件?通常需要root权限或用户属于gpio组。可以在服务文件中将User=root,或者将LED设备文件权限改为可写:sudo chmod 666 /sys/class/leds/.../brightness(不推荐,安全性低)。 - 查看脚本日志:
sudo journalctl -u bilibili-monitor.service查看是否有权限错误。
- 检查路径:首先确认
问题3:脚本运行一段时间后,CPU或内存占用异常高。
- 可能原因:内存泄漏或某个异常导致循环卡死。
- 排查:
- 使用
top或htop命令查看进程资源占用。 - 检查日志中是否有大量重复的错误信息。
- 尝试在脚本的
main循环中,在每次循环开始和结束时打印简单日志,观察循环是否正常。 - 考虑在
hardware_feedback或get_unread_count函数中增加超时设置,防止某个操作阻塞整个进程。
- 使用
问题4:网络不稳定导致请求频繁失败。
- 优化:在
requests.get中已经设置了timeout=10。可以进一步增加重试机制。使用requests的适配器或者urllib3的重试功能:from requests.adapters import HTTPAdapter from requests.packages.urllib3.util.retry import Retry session = requests.Session() retries = Retry(total=3, backoff_factor=1, status_forcelist=[500, 502, 503, 504]) session.mount('https://', HTTPAdapter(max_retries=retries)) # 然后用session.get代替requests.get
5. 项目扩展与进阶玩法
这个基础框架搭建好后,你可以根据自己的想法进行无限扩展:
- 多平台聚合:不止B站,可以同时监测邮箱、GitHub通知、其他社交平台消息等,让开发板成为一个统一的消息中心。
- 更丰富的硬件反馈:
- OLED屏幕显示:显示未读消息的详细分类(@我的、回复、赞)。
- RGB LED:用不同颜色代表不同消息类型(如蓝色代表回复,红色代表@)。
- 蜂鸣器:有重要消息(如@)时发出短促提示音。
- 连接智能家居:通过MQTT协议,让家里的智能灯变色(比如收到UP主更新时,灯光变成B站蓝)。
- 消息过滤与优先级:在脚本中增加逻辑,只对特定UP主的@、特定关键词的评论进行提醒,忽略普通的点赞通知。
- 云端状态同步:将未读状态上传到私有云(如Home Assistant),方便在手机或电脑上远程查看。
- 降低功耗模式:如果使用电池供电,可以设计成平时深度睡眠,每隔一段时间唤醒检查,检查完继续睡眠。
这个项目麻雀虽小,五脏俱全。它串联起了网络爬虫、API逆向、嵌入式Linux编程、硬件交互、系统服务部署等多个环节。最重要的是,它解决了一个真实的小痛点,并且整个过程充满了动手的乐趣。当你第一次看到开发板上的LED灯因为B站的一条新消息而闪烁起来时,那种“自己造轮子”的成就感,是直接用现成App无法比拟的。
