Python自动化实战:200行代码打造B站UP主数据监控助手
1. 项目概述与核心价值
最近在和一些做B站内容的朋友聊天,发现他们每天都要手动打开B站创作中心好几次,就为了看那几个关键数据:视频播放量涨了多少,粉丝数有没有变化,互动数据怎么样。这事儿听起来简单,但日复一日地重复,不仅浪费时间,还容易打断创作时的“心流”状态。作为一个喜欢用代码解决重复劳动的程序员,我就在想,能不能写个小工具,把这些数据直接“搬”到桌面上,一目了然?
于是,就有了这个“B站UP主小助手”的想法。它的核心功能很简单:用Python写一个脚本,定期自动获取并显示指定UP主的视频播放数、粉丝数等关键数据。你别看功能简单,背后涉及到的技术点可不少,从网络请求、数据解析,到本地存储、定时任务,再到最后打包成一个谁都能用的exe文件,每一步都有值得琢磨的地方。我花了点时间,用大约200行代码实现了核心功能,今天就把整个思路、踩过的坑和最终方案完整地分享出来。无论你是刚学Python想找个实战项目练手,还是本身就是UP主想解放双手,这篇文章都能给你提供一条清晰的实现路径。
2. 整体设计与技术选型
在动手写代码之前,得先把整个项目的骨架搭好。我们的目标是做一个能稳定运行、数据准确、并且对使用者友好的小工具。这意味着我们不能只写一个一次性运行的脚本,而是要考虑到持续运行、错误处理以及最终交付的便利性。
2.1 核心需求拆解
首先,我们把“显示视频播放数、粉丝数”这个模糊的需求具体化:
- 数据获取:必须能从B站公开的接口或页面,稳定地获取到指定UP主的粉丝数、最新视频的播放/点赞/投币/收藏数。
- 数据展示:获取到的数据需要以一种直观的方式呈现出来,比如在命令行窗口显示,或者更友好一点,在系统托盘显示一个实时数字。
- 自动更新:数据不能只获取一次,需要能按照设定的时间间隔(比如每10分钟)自动刷新,确保看到的是最新数据。
- 配置简化:对于最终使用者(可能是不懂编程的UP主),最好只需要输入自己的B站UID,然后双击一个exe文件就能运行。
- 稳定与容错:网络可能不稳定,B站的接口也可能变化,程序需要有基本的错误处理和重试机制,不能动不动就崩溃。
2.2 技术栈选择与理由
基于以上需求,我选择了以下技术组合,并解释一下为什么这么选:
- Python 3.8+:这是我们的主力语言。选择Python是因为它在数据处理、网络请求和快速原型开发上效率极高,有丰富的第三方库支持,非常适合这种小型自动化工具。3.8是一个比较稳定且普及的版本,能兼顾库的兼容性和新特性。
- Requests库:用于发送HTTP请求到B站接口。这是Python生态中处理网络请求的“事实标准”,简单易用且功能强大。相比于Python自带的
urllib,Requests的API更加人性化。 - BeautifulSoup4 或 直接解析JSON:用于解析从B站获取到的数据。B站的数据接口通常返回JSON格式,直接用
json库解析即可。但如果某些数据需要从HTML页面中抓取(虽然不推荐,作为备选),BeautifulSoup4是解析HTML/XML的利器。 - Schedule库:用于实现定时任务。我们需要程序每隔一段时间自动执行数据获取函数。虽然可以用
time.sleep()加循环来实现,但schedule库提供了更优雅、更易读的定时任务声明方式(例如schedule.every(10).minutes.do(job))。 - PyInstaller:用于将Python脚本打包成独立的Windows可执行文件(.exe)。这是让工具能交付给非技术用户的关键一步。它能把Python解释器、脚本以及所有依赖库打包在一起,生成一个“开箱即用”的程序。
- Tkinter 或 PyQt5 (可选):如果我们希望做一个有图形界面(GUI)的小窗口来显示数据,而不是只用命令行,就需要用到GUI库。
Tkinter是Python自带的,无需额外安装,但界面比较简陋。PyQt5功能强大、界面美观,但打包后的体积会大很多。为了保持“200行代码”的简洁性和核心目标是“显示数据”,我首选命令行输出,并会介绍如何用系统托盘图标这种更轻量、更不打扰人的方式作为进阶方案。
注意:在数据获取方式上,务必遵守网站规则。我们的目标是获取公开的、非敏感的数据(如UP主主页的公开信息),并且要控制请求频率,避免对B站服务器造成压力,这既是道德要求,也能保证我们工具的长期稳定运行。绝对不要尝试破解或高频请求需要登录才能访问的接口。
3. 核心实现步骤详解
接下来,我们进入实操环节。我会把整个开发过程拆解成几个关键步骤,并附上详细的代码和解释。
3.1 第一步:环境准备与依赖安装
工欲善其事,必先利其器。首先确保你的电脑上安装了Python。打开命令行(CMD或PowerShell),输入python --version检查版本,确保是3.8或以上。
然后,我们创建一个新的项目文件夹,比如叫bilibili_up_assistant,并在里面创建一个requirements.txt文件,列出我们需要的库:
requests>=2.28.0 schedule>=1.2.0 # 如果计划用BeautifulSoup解析HTML,可以加上 # beautifulsoup4>=4.11.0 # 如果计划打包,可以暂时不安装,打包时会自动处理接着,在命令行中进入项目目录,使用pip安装这些依赖:
cd path/to/bilibili_up_assistant pip install -r requirements.txt如果你习惯使用虚拟环境(强烈推荐,可以避免不同项目间的库版本冲突),可以这样操作:
# 创建虚拟环境 python -m venv venv # 激活虚拟环境 (Windows) venv\Scripts\activate # 激活虚拟环境 (macOS/Linux) # source venv/bin/activate # 然后在激活的虚拟环境中安装依赖 pip install -r requirements.txt3.2 第二步:探索与获取B站数据接口
这是最关键也最具挑战性的一步。我们需要找到B站提供UP主公开数据的接口。最直接的方式是分析B站网页的请求。
- 打开开发者工具:用Chrome或Edge浏览器打开一位UP主的主页,例如
https://space.bilibili.com/123456(123456是UID)。按F12打开开发者工具,切换到“网络”(Network)标签页。 - 寻找数据请求:刷新页面,在网络请求列表中,筛选
XHR或Fetch类型的请求。你会看到很多请求,我们需要寻找那些返回了粉丝数、视频信息等数据的请求。 - 分析接口:经过查找,B站通常有一个接口格式类似
https://api.bilibili.com/x/space/acc/info?mid=123456,这个接口返回了UP主的基本信息,其中就包含follower(粉丝数)。另一个接口类似https://api.bilibili.com/x/space/arc/search?mid=123456&ps=30,这个接口返回了UP主的视频列表,里面包含了每个视频的播放量(play)、点赞(like)、投币(coin)、收藏(favorite)等数据。 - 验证接口:你可以直接把这个接口地址复制到浏览器地址栏访问,看看返回的JSON数据是否包含你需要的信息。
实操代码示例:获取UP主粉丝数
我们创建一个名为bilibili_api.py的文件,编写数据获取函数:
import requests import json import time def get_up_info(uid): """ 根据B站UID获取UP主基本信息(含粉丝数) :param uid: B站用户UID,字符串或数字 :return: 包含粉丝数等信息的字典,出错时返回None """ # 构造请求头,模拟浏览器访问,降低被拒绝的风险 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': f'https://space.bilibili.com/{uid}' # 添加来源页,更符合正常访问行为 } # 粉丝数等信息接口 info_url = f'https://api.bilibili.com/x/space/acc/info?mid={uid}' try: # 发送GET请求,设置超时时间 response = requests.get(info_url, headers=headers, timeout=10) response.raise_for_status() # 如果状态码不是200,抛出HTTPError异常 data_json = response.json() # 检查B站接口返回的code,0表示成功 if data_json.get('code') == 0: data = data_json.get('data', {}) # 提取我们需要的信息 up_info = { 'name': data.get('name', 'N/A'), 'uid': uid, 'follower': data.get('follower', 0), # 粉丝数 'face': data.get('face', ''), # 头像 'level': data.get('level', 0), # 等级 } return up_info else: print(f"接口返回错误: {data_json.get('message')}") return None except requests.exceptions.RequestException as e: print(f"网络请求失败: {e}") return None except json.JSONDecodeError as e: print(f"JSON解析失败: {e}") return None # 测试函数 if __name__ == '__main__': # 替换成你想查询的UP主UID,例如“老师好我叫何同学”的UID是“20165629” test_uid = '20165629' info = get_up_info(test_uid) if info: print(f"UP主: {info['name']}") print(f"UID: {info['uid']}") print(f"粉丝数: {info['follower']}") print(f"等级: {info['level']}") else: print("获取信息失败")关键点解析:
- User-Agent:这是告诉服务器我们是什么客户端在访问。使用一个常见的浏览器UA,可以避免被一些简单的反爬机制拦截。
- Referer:表示请求是从哪个页面发起的。添加这个头信息,让请求看起来更像是由用户点击触发的正常行为。
- 异常处理:网络请求充满了不确定性,必须用
try...except包裹。我们捕获了请求异常和JSON解析异常,这样即使网络波动或接口格式变化,程序也不会直接崩溃,而是给出错误提示。 - 超时设置:
timeout=10表示如果10秒内没有收到响应,就认为请求失败。这可以防止程序在糟糕的网络环境下无限期等待。
3.3 第三步:获取视频播放数据
获取了UP主基本信息,我们还需要其视频的播放数据。我们修改bilibili_api.py,增加获取视频列表的函数。
def get_video_list(uid, page_size=5): """ 获取UP主最新的视频列表及播放数据 :param uid: B站用户UID :param page_size: 获取最新的几条视频,默认5条 :return: 视频信息列表,出错返回空列表 """ headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': f'https://space.bilibili.com/{uid}' } # 视频列表接口,pn是页码,ps是每页数量 video_url = f'https://api.bilibili.com/x/space/arc/search?mid={uid}&pn=1&ps={page_size}' try: response = requests.get(video_url, headers=headers, timeout=10) response.raise_for_status() data_json = response.json() if data_json.get('code') == 0: vlist = data_json.get('data', {}).get('list', {}).get('vlist', []) video_info_list = [] for video in vlist: video_info = { 'aid': video.get('aid'), # 视频AV号 'bvid': video.get('bvid'), # 视频BV号 'title': video.get('title'), 'play': video.get('play'), # 播放量 'like': video.get('like'), # 点赞 'coin': video.get('coin'), # 投币 'favorite': video.get('favorite'), # 收藏 'created': video.get('created'), # 发布时间戳 } video_info_list.append(video_info) return video_info_list else: print(f"获取视频列表失败: {data_json.get('message')}") return [] except requests.exceptions.RequestException as e: print(f"获取视频列表请求失败: {e}") return [] except json.JSONDecodeError as e: print(f"视频列表JSON解析失败: {e}") return [] # 整合测试 if __name__ == '__main__': test_uid = '20165629' print("=== UP主基本信息 ===") info = get_up_info(test_uid) if info: print(f"名称: {info['name']}") print(f"粉丝数: {info['follower']}") print("\n=== 最新视频数据 ===") videos = get_video_list(test_uid, 3) # 获取最新3条 for idx, video in enumerate(videos, 1): print(f"视频{idx}: {video['title'][:30]}...") # 只显示前30个字符 print(f" 播放: {video['play']} | 点赞: {video['like']} | 投币: {video['coin']} | 收藏: {video['favorite']}")现在,我们已经有了两个核心函数,可以分别获取UP主的粉丝数和最新视频的播放数据了。
3.4 第四步:构建主程序与定时任务
数据获取功能有了,接下来我们要让程序“活”起来,能够定时执行这些任务,并把结果清晰地展示出来。我们创建一个新的主程序文件main.py。
import schedule import time from datetime import datetime from bilibili_api import get_up_info, get_video_list # 全局配置 TARGET_UID = '20165629' # 替换为你要监控的UP主UID UPDATE_INTERVAL_MINUTES = 10 # 更新间隔(分钟) def fetch_and_display_data(): """核心任务:获取数据并打印到控制台""" current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"\n[{current_time}] 开始获取数据...") # 1. 获取UP主信息 up_info = get_up_info(TARGET_UID) if not up_info: print(" 获取UP主信息失败,跳过本次更新。") return # 2. 获取视频列表 video_list = get_video_list(TARGET_UID, 3) # 获取最新3条 # 3. 格式化输出 print(f" UP主: {up_info['name']} (UID: {TARGET_UID})") print(f" 粉丝数: {up_info['follower']}") print(f" 等级: Lv{up_info['level']}") if video_list: latest_video = video_list[0] # 最新的视频 print(f" 最新视频: 《{latest_video['title'][:40]}...》") print(f" 播放: {latest_video['play']} | 点赞: {latest_video['like']} | 投币: {latest_video['coin']} | 收藏: {latest_video['favorite']}") # 计算最近3条视频的总播放量(可选) total_play = sum(v['play'] for v in video_list if v['play']) print(f" 近3条视频总播放量: {total_play}") else: print(" 未获取到视频数据。") print(f"[{current_time}] 数据获取完成。") def main(): """主函数,设置定时任务并运行""" print("=== B站UP主小助手启动 ===") print(f"监控UID: {TARGET_UID}") print(f"更新间隔: 每 {UPDATE_INTERVAL_MINUTES} 分钟") print("=" * 30) # 立即执行一次 fetch_and_display_data() # 设置定时任务:每 UPDATE_INTERVAL_MINUTES 分钟执行一次 schedule.every(UPDATE_INTERVAL_MINUTES).minutes.do(fetch_and_display_data) print(f"\n程序已启动,将每{UPDATE_INTERVAL_MINUTES}分钟自动更新。按 Ctrl+C 终止程序。\n") # 保持程序运行,并检查定时任务 try: while True: schedule.run_pending() time.sleep(1) # 每秒检查一次,避免CPU空转 except KeyboardInterrupt: print("\n\n程序被用户中断。再见!") if __name__ == '__main__': main()运行与效果: 在命令行中运行python main.py,你会看到程序立即获取一次数据并打印出来,然后每隔10分钟(根据UPDATE_INTERVAL_MINUTES设置)自动执行一次。输出格式清晰,包含了时间戳、UP主信息和最新视频数据。
实操心得:
schedule库的run_pending()需要在一个循环中不断调用,time.sleep(1)让循环每秒检查一次,这是一个非常经典且低耗能的模式。千万不要用time.sleep(60 * UPDATE_INTERVAL_MINUTES)来代替,因为那样会阻塞进程,无法响应其他事件(比如未来的GUI事件)。
4. 进阶优化与功能扩展
基础功能已经实现,但要让这个小工具更实用、更健壮,我们还需要做一些优化。
4.1 增加数据持久化与简单分析
目前数据只在控制台显示,关掉就没了。我们可以增加一个简单的日志功能,把每次获取的数据记录到CSV文件里,方便后续查看趋势。
在main.py中增加一个函数和修改fetch_and_display_data:
import csv import os LOG_FILE = 'up_data_log.csv' def log_to_csv(up_info, video_list): """将数据记录到CSV文件""" file_exists = os.path.isfile(LOG_FILE) current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 准备要记录的数据行 latest_video = video_list[0] if video_list else {} row_data = [ current_time, up_info.get('uid', ''), up_info.get('name', ''), up_info.get('follower', 0), latest_video.get('title', 'N/A')[:50], # 记录标题前50字符 latest_video.get('play', 0), latest_video.get('like', 0), latest_video.get('coin', 0), latest_video.get('favorite', 0), ] try: with open(LOG_FILE, 'a', newline='', encoding='utf-8-sig') as f: # utf-8-sig方便Excel直接打开 writer = csv.writer(f) # 如果是新文件,写入表头 if not file_exists: header = ['时间', 'UID', 'UP主', '粉丝数', '最新视频标题', '播放量', '点赞', '投币', '收藏'] writer.writerow(header) writer.writerow(row_data) # print(f" 数据已记录到 {LOG_FILE}") # 可选:打印日志成功信息 except Exception as e: print(f" 写入日志文件失败: {e}") # 然后在 fetch_and_display_data 函数的最后,成功获取数据后调用日志函数 def fetch_and_display_data(): # ... [前面的获取数据代码不变] ... if up_info and video_list: # ... [前面的打印输出代码不变] ... # 记录到日志文件 log_to_csv(up_info, video_list)这样,每次数据更新都会在up_data_log.csv文件中追加一行,你可以用Excel打开,轻松地绘制粉丝增长曲线或播放量变化图。
4.2 实现系统托盘图标显示(Windows)
对于希望工具更“隐形”、不占用任务栏或命令行窗口的用户,系统托盘图标是一个完美的解决方案。我们可以使用pystray和Pillow库来实现。这会让代码量增加,但用户体验提升巨大。
首先安装额外依赖:
pip install pystray pillow然后创建一个新的文件tray_app.py:
import threading import time from datetime import datetime import pystray from PIL import Image, ImageDraw import schedule from bilibili_api import get_up_info, get_video_list # 配置 TARGET_UID = '20165629' UPDATE_INTERVAL = 10 # 分钟 class UpMonitorTray: def __init__(self): self.latest_data = { 'name': '加载中...', 'follower': '--', 'latest_play': '--', 'update_time': '--' } self.icon = self.create_icon() self.running = True def create_icon(self): """创建一个简单的托盘图标(带字母‘B’)""" image = Image.new('RGB', (64, 64), color='#FB7299') # B站主题色 draw = ImageDraw.Draw(image) # 在图标中央画一个白色的‘B’ draw.text((22, 16), 'B', fill='white', font=None) # 简单绘制 return image def update_data(self): """更新数据的线程函数""" while self.running: try: info = get_up_info(TARGET_UID) videos = get_video_list(TARGET_UID, 1) if info and videos: self.latest_data['name'] = info.get('name', 'N/A') self.latest_data['follower'] = str(info.get('follower', 0)) self.latest_data['latest_play'] = str(videos[0].get('play', 0)) self.latest_data['update_time'] = datetime.now().strftime("%H:%M") # 更新托盘图标提示文本(鼠标悬停时显示) self.update_tray_tooltip() else: self.latest_data['update_time'] = f"{datetime.now().strftime('%H:%M')} (更新失败)" except Exception as e: print(f"数据更新出错: {e}") # 每隔指定分钟更新一次 time.sleep(UPDATE_INTERVAL * 60) def update_tray_tooltip(self): """更新鼠标悬停提示""" tooltip_text = ( f"UP主: {self.latest_data['name']}\n" f"粉丝: {self.latest_data['follower']}\n" f"最新视频播放: {self.latest_data['latest_play']}\n" f"更新于: {self.latest_data['update_time']}" ) # 注意:pystray的icon对象需要在主线程更新,这里需要一些线程同步处理 # 为了简化,我们可以通过重新设置菜单项来间接更新(见下文) pass def on_quit(self, icon, item): """退出菜单项的回调函数""" self.running = False icon.stop() def run(self): """运行托盘应用""" # 创建菜单 menu = ( pystray.MenuItem(f"UP主: {self.latest_data['name']}", lambda: None, enabled=False), pystray.MenuItem(f"粉丝数: {self.latest_data['follower']}", lambda: None, enabled=False), pystray.MenuItem(f"最新播放: {self.latest_data['latest_play']}", lambda: None, enabled=False), pystray.MenuItem(f"更新时间: {self.latest_data['update_time']}", lambda: None, enabled=False), pystray.Menu.SEPARATOR, pystray.MenuItem("退出", self.on_quit) ) icon = pystray.Icon("bilibili_up_monitor", self.icon, "B站UP主助手", menu) # 启动数据更新线程 update_thread = threading.Thread(target=self.update_data, daemon=True) update_thread.start() # 运行一个定时任务来刷新菜单显示(因为菜单文本创建后是静态的) def refresh_menu(): new_menu = ( pystray.MenuItem(f"UP主: {self.latest_data['name']}", lambda: None, enabled=False), pystray.MenuItem(f"粉丝数: {self.latest_data['follower']}", lambda: None, enabled=False), pystray.MenuItem(f"最新播放: {self.latest_data['latest_play']}", lambda: None, enabled=False), pystray.MenuItem(f"更新时间: {self.latest_data['update_time']}", lambda: None, enabled=False), pystray.Menu.SEPARATOR, pystray.MenuItem("退出", self.on_quit) ) icon.menu = new_menu def schedule_refresh(): while self.running: schedule.every(1).minutes.do(refresh_menu) schedule.run_pending() time.sleep(1) refresh_thread = threading.Thread(target=schedule_refresh, daemon=True) refresh_thread.start() # 运行托盘图标 icon.run() if __name__ == '__main__': app = UpMonitorTray() app.run()运行python tray_app.py,你会在电脑右下角的系统托盘区看到一个粉色的“B”图标。鼠标悬停可以看到基本信息,右键点击可以退出。数据会在后台自动更新。
注意事项:系统托盘程序涉及多线程(数据获取、定时刷新、GUI事件循环),编写时要特别注意线程安全。上面的示例是一个简化版本,在实际使用中,你可能需要使用线程锁(
threading.Lock)来保护共享数据(如latest_data),或者使用queue.Queue在线程间传递数据更新事件,以避免潜在的竞态条件。对于初学者,如果觉得复杂,坚持使用命令行版本是完全可行的。
4.3 使用PyInstaller打包成EXE
为了让不懂Python的朋友也能用上这个工具,我们需要把它打包成一个独立的exe文件。PyInstaller是目前最常用的工具。
安装PyInstaller:
pip install pyinstaller打包命令行版本: 在项目根目录下打开命令行,执行:
pyinstaller -F -w -i bilibili.ico main.py-F:打包成单个exe文件。-w:运行时不显示命令行窗口(Windows Only)。如果你希望看到打印的日志,就不要加这个参数。-i bilibili.ico:指定exe的图标文件(需要准备一个.ico格式的图标)。 执行完毕后,会在dist文件夹里找到main.exe。把这个文件发给别人,他双击就能运行(前提是他的系统有基本的运行库,通常Windows 10/11都自带)。
打包系统托盘版本:
pyinstaller -F -w -i bilibili.ico tray_app.py注意,由于托盘版本依赖
pystray和Pillow,PyInstaller可能会需要一些额外参数来正确打包这些库的依赖(如图片文件)。如果打包后运行报错,可能需要查阅PyInstaller和相应库的文档,使用--add-data参数手动添加资源文件。打包常见问题与解决:
- 文件体积大:这是PyInstaller打包Python程序的通病,因为它把Python解释器和所有依赖库都打包进去了。可以使用
--exclude-module尝试排除一些不必要的大库,但需谨慎。 - 运行报错“Failed to execute script”:通常是因为某些动态库或资源文件没有正确打包。可以在打包时去掉
-w参数,运行exe查看命令行输出的具体错误信息。更高级的调试可以使用--debug模式。 - 防病毒软件误报:这是另一个常见问题。PyInstaller打包的程序有时会被一些激进的杀毒软件误报为病毒。解决办法包括:1) 使用PyInstaller的
--key参数进行加密(效果有限);2) 向杀毒软件提交误报文件;3) 使用代码签名证书对exe进行数字签名(成本较高)。对于个人小工具,提前告知使用者“可能被误报,请添加信任”是比较实际的方案。
- 文件体积大:这是PyInstaller打包Python程序的通病,因为它把Python解释器和所有依赖库都打包进去了。可以使用
5. 常见问题排查与优化建议
在实际开发和运行中,你可能会遇到以下问题。这里我整理了一份排查清单和优化思路。
5.1 网络请求失败或返回空数据
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
连接超时 (ConnectTimeout) | 网络不稳定,或目标服务器暂时不可用。 | 1. 检查本地网络。2. 在代码中增加重试机制(见下文)。3. 适当增加timeout参数。 |
| 返回状态码非200 (如403, 404) | 接口地址已变更,或请求头信息被识别为爬虫。 | 1. 按本文3.2节的方法,重新用浏览器开发者工具抓取最新的接口地址。2. 检查并更新headers,特别是User-Agent和Referer,使其更像真实浏览器。3. 如果接口需要登录才能访问(非公开数据),则此方法无效,请勿尝试绕过。 |
返回的JSON中code不为0 | B站接口返回的业务逻辑错误。 | 打印出返回的message字段,根据提示判断。常见原因:UID不存在、请求频率过快被限制。 |
| 返回数据为空列表或空字典 | 接口正常但该UP主无视频或数据。 | 检查UID是否正确,以及UP主是否发布了视频。 |
增加重试机制的代码示例:
import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def create_session_with_retry(retries=3, backoff_factor=0.5): """创建一个带重试机制的requests Session""" session = requests.Session() retry_strategy = Retry( total=retries, backoff_factor=backoff_factor, # 重试等待时间:{backoff factor} * (2 ** ({retry number} - 1)) status_forcelist=[429, 500, 502, 503, 504], # 遇到这些状态码会重试 ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount("http://", adapter) session.mount("https://", adapter) return session # 使用示例 session = create_session_with_retry() try: response = session.get(url, headers=headers, timeout=10) # ... 处理response ... except requests.exceptions.RequestException as e: # 经过重试后仍然失败 print(f"请求最终失败: {e}")5.2 程序运行一段时间后崩溃或停止更新
- 原因1:内存泄漏(不常见于小脚本):确保在循环中没有不断创建不会被释放的大对象。
- 原因2:未捕获的异常导致线程退出:特别是在系统托盘的多线程版本中,务必用
try...except包裹数据获取和处理的代码,并将错误日志记录下来。 - 原因3:系统休眠/唤醒后定时器紊乱:
schedule库的定时是基于系统时间的,如果电脑进入休眠,唤醒后可能错过一些任务。一个更健壮的方法是使用while True循环配合time.sleep()和记录上次执行时间的方式来实现定时,这样能保证唤醒后按间隔继续执行。
5.3 如何监控多个UP主?
当前代码只监控一个UP主。要监控多个,只需将TARGET_UID从一个字符串改为列表,然后循环处理即可。
TARGET_UIDS = ['20165629', '546195', '208259'] # 何同学、老番茄、敖厂长 def fetch_all_data(): for uid in TARGET_UIDS: up_info = get_up_info(uid) # ... 处理并显示或记录每个UP主的数据 ... # 注意:在循环中最好加入短暂的延时,避免短时间内对B站服务器发起大量请求。 time.sleep(1)5.4 想获取更多数据(如评论数、弹幕数)?
你需要找到对应的接口。例如,视频的详细数据(包含评论数、弹幕数)通常有另一个接口,如https://api.bilibili.com/x/web-interface/view?aid=xxx或https://api.bilibili.com/x/web-interface/view?bvid=xxx。同样通过浏览器开发者工具寻找。获取到数据后,仿照get_video_list函数解析新的字段即可。
最后一点个人体会:这个项目虽然代码量不大,但完整地走了一遍从需求分析、技术选型、编码实现、调试优化到最终打包交付的流程,是一个非常好的Python综合练习。在实际操作中,最大的挑战往往不是写代码,而是处理各种边界情况(网络错误、数据格式变化)和让程序对用户更友好。当你看到自己写的小工具在后台默默工作,为你或你的朋友提供便利时,那种成就感是单纯学习语法无法比拟的。如果后续你想把它做得更强大,可以考虑加入数据可视化(用matplotlib画趋势图)、设置图形化配置界面(用PyQt5)、甚至做成一个简单的Web服务,路还很长,乐趣也很多。
