资讯动态

C#实现B站动态抽奖自动化:BiliRaffle组件开发与实战

发布时间:2026/10/23 16:07:41 来源:尧图企业网站定制
简介BiliRaffle 是一款面向 C# 开发者的 B 站动态抽奖组件基于 bilibili-api 与 bilibili-tool 生态构建适合希望快速实现动态抽奖自动化、研究 B 站接口调用与 WPF 桌面应用开发的初中级程序员。资源包共 32 个文件约 60KB以 12 个 cs 源码文件与 4 个 xaml 界面文件为核心辅以 config、xml、dll、sln、csproj 等工程配置与依赖文件以及 png、ico、resx 等界面资源整体结构完整可直接用 Visual Studio 打开编译。项目采用 .NET Framework 4.5 及以上版本解压后运行 BiliRaffle.exe 即可使用涵盖登录窗口、主窗口、ViewModel、抽奖逻辑与 MVVM 辅助类等模块便于读者理解桌面端抽奖工具的架构设计与实现思路。目前已有 697 人学习下载适合作为 C# 桌面开发与 B 站接口实践的参考案例。1. 抽奖工具还能这么写一个把B站动态抽奖自动化的C#组件刷到一条B站动态抽奖要求关注、转发、好友你手动点完一轮发现还有几十条类似的。更麻烦的是开奖后要一个个核对谁转发了、谁关注了手动统计到眼花。BiliRaffle 就是冲着这个场景来的——一个用 C# 写的 B站动态抽奖组件把「识别抽奖动态 → 记录参与者 → 随机开奖 → 输出结果」这条链路自动化。它基于 bilibili-api 这类接口封装思路适合想拿现成组件做二次开发的后端或桌面端开发者也适合想学 C# 调用第三方 API 的新手。项目本身不复杂但麻雀虽小接口鉴权、分页拉取、去重、随机算法这些坑一个不少拿来练手或者直接嵌进自己的工具链都行。2. 项目定位与技术选型为什么用 C# 写一个抽奖组件2.1 它到底解决什么问题B站动态抽奖的痛点不在「抽」这个动作而在「数据从哪来」和「结果怎么算」。一条抽奖动态下面可能有几千条转发评论官方客户端不会给你导出参与者列表手动翻页复制根本不现实。BiliRaffle 的核心价值是把这件事拆成三步通过 bilibili-api 拉取指定动态的转发/评论数据按规则过滤出有效参与者再用随机算法抽出中奖者。它不是一个带界面的成品软件而是一个组件——你可以理解为一组类库加一个可执行的入口。开发者拿到源码后可以把它塞进自己的控制台工具、桌面应用甚至挂到定时任务里。关键词里的 bilibili-tool、bilibili-raffle 说的就是这类工具的统称而 BiliRaffle 是其中用 C# 实现的一个具体版本。适合谁如果你写过 C#想找一个真实调用 REST API、处理 JSON、做分页和去重的练手项目它比那些纯 CRUD 的 Demo 有意思得多。如果你已经在做 B站相关的工具需要一块抽奖逻辑可以直接把核心模块拆出来用。2.2 技术栈拆解项目主体是 C#大概率跑在 .NET 6 或 .NET 8 上具体看 csproj 里的 TargetFramework。为什么选 C# 而不是 Python一个现实原因是如果你要做 Windows 桌面端的抽奖工具C# 加 WPF 或 WinForms 的打包和分发比 Python 省心不用让用户装解释器。另一个原因是 C# 的 async/await 写异步 HTTP 请求很顺手拉取大量分页数据时不会阻塞。依赖方面核心是 HTTP 客户端和 JSON 解析。常见做法是用HttpClient发请求用System.Text.Json或Newtonsoft.Json反序列化响应。bilibili-api 本身有 Python 版本但 C# 这边通常是直接调 B站的 Web API 端点自己封装请求头和参数。项目里应该有一个BiliApiClient之类的类负责统一处理 cookie、User-Agent、Referer 这些鉴权相关的头。提示B站接口对请求头比较敏感尤其是Referer和User-Agent缺了容易被拒。这是后面避坑章节会细说的点。2.3 目录结构长什么样虽然没有拿到完整源码树但按这类项目的惯例结构大致如下BiliRaffle/ ├── BiliRaffle.Core/ # 核心逻辑 │ ├── Api/ # API 封装 │ │ ├── BiliApiClient.cs # HTTP 请求客户端 │ │ └── Models/ # 响应数据模型 │ ├── Raffle/ # 抽奖逻辑 │ │ ├── Participant.cs # 参与者实体 │ │ └── RaffleEngine.cs # 开奖引擎 │ └── Utils/ # 工具类 │ └── RandomHelper.cs # 随机数封装 ├── BiliRaffle.Cli/ # 命令行入口 │ └── Program.cs └── BiliRaffle.Tests/ # 单元测试这个结构的好处是 Core 和 Cli 分离抽奖逻辑不依赖具体入口你换成 WPF 界面或者 Web API 都不用动核心代码。如果你拿到的源码结构不同重点找Api和Raffle这两个目录改造时优先看它们。3. 核心模块拆解从拉取动态到开出中奖名单3.1 接口鉴权与请求封装B站大部分接口需要登录态也就是 cookie 里的SESSDATA。BiliRaffle 要能读到抽奖动态的转发列表就必须带上有效的 cookie。常见做法是把 cookie 放在配置文件或环境变量里代码里统一注入到 HttpClient 的默认请求头。public class BiliApiClient { private readonly HttpClient _http; public BiliApiClient(string sessData) { _http new HttpClient(); // B站接口对 Referer 和 UA 有校验必须带上 _http.DefaultRequestHeaders.Add(User-Agent, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36); _http.DefaultRequestHeaders.Add(Referer, https://www.bilibili.com); // SESSDATA 是登录凭证从 cookie 中提取 _http.DefaultRequestHeaders.Add(Cookie, $SESSDATA{sessData}); } public async Taskstring GetAsync(string url) { var resp await _http.GetAsync(url); resp.EnsureSuccessStatusCode(); return await resp.Content.ReadAsStringAsync(); } }这段代码的关键点有三个User-Agent伪装成浏览器、Referer指向 B站主站、Cookie里带SESSDATA。参数sessData需要你从浏览器登录后的 cookie 里手动复制或者通过扫码登录接口获取。少了任何一个接口大概率返回 -403 或空数据。3.2 分页拉取转发列表一条动态的转发数据是分页返回的每页通常 20 条左右。要拿全所有参与者必须循环请求直到没有下一页。这里有个容易翻车的地方B站的分页接口用offset或pn参数不同接口不一样而且返回的has_more字段要正确判断。public async TaskListParticipant FetchAllReposts(long dynamicId) { var all new ListParticipant(); string offset ; bool hasMore true; while (hasMore) { // 动态转发列表接口offset 为空表示第一页 var url $https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/repost $?dynamic_id{dynamicId}offset{offset}; var json await _client.GetAsync(url); var result JsonSerializer.DeserializeRepostResponse(json); if (result?.Data?.Items null) break; foreach (var item in result.Data.Items) { all.Add(new Participant { Uid item.User.Mid, Name item.User.Name, RepostTime item.RepostTime }); } // 更新 offset 和 has_more准备下一页 offset result.Data.Offset; hasMore result.Data.HasMore; // 加延迟避免请求过快被限流 await Task.Delay(500); } return all; }逻辑说明offset是游标第一页传空字符串后续用上一页返回的值。has_more为 false 时停止循环。Task.Delay(500)是血泪经验——不加延迟连续请求很容易触发风控返回 412 或者直接封 IP 一段时间。参数dynamicId是动态的 ID从动态链接里能拿到比如t.bilibili.com/xxxxx里的数字部分。3.3 参与者去重与过滤拉回来的列表里会有重复——同一个人可能转发了多次或者既转发了又评论了。抽奖规则通常要求「每个用户只算一次」所以要去重。另外还要过滤掉不符合条件的比如抽奖发起者本人、机器人账号、转发内容为空的。public ListParticipant Deduplicate(ListParticipant raw) { // 按 Uid 分组每组取最早一次转发时间 var unique raw .GroupBy(p p.Uid) .Select(g g.OrderBy(p p.RepostTime).First()) .ToList(); // 过滤掉发起者自己假设发起者 Uid 已知 unique unique.Where(p p.Uid ! _hostUid).ToList(); return unique; }GroupBy按用户 ID 分组OrderBy取最早时间保证结果稳定。_hostUid是抽奖发起者的 UID需要提前配置。这一步看起来简单但如果不去重开奖时同一个人可能中两次结果就失去公信力了。3.4 随机开奖算法开奖的核心是「公平随机」。C# 里用Random类就能做但要注意两点一是不要每次 new 一个 Random否则短时间内可能产生相同种子二是如果需要可复现的结果可以用固定种子。public ListParticipant Draw(ListParticipant pool, int winnerCount) { if (pool.Count winnerCount) throw new ArgumentException(参与者数量不足); // 使用 Fisher-Yates 洗牌算法保证每个位置等概率 var shuffled new ListParticipant(pool); var rng Random.Shared; // .NET 6 的线程安全随机源 for (int i shuffled.Count - 1; i 0; i--) { int j rng.Next(i 1); (shuffled[i], shuffled[j]) (shuffled[j], shuffled[i]); } return shuffled.Take(winnerCount).ToList(); }Fisher-Yates 洗牌是标准做法比「随机取一个然后移除」效率高也不会出现概率偏差。Random.Shared是 .NET 6 引入的静态实例线程安全省去了自己管理种子的麻烦。参数winnerCount是中奖人数pool是去重后的参与者列表。开奖结果建议同时输出中奖者的 UID 和昵称方便核对。4. 跑起来环境准备与命令行执行4.1 环境依赖清单在跑之前确认本机有这些依赖项版本要求说明.NET SDK6.0 或以上编译和运行 C# 项目Git任意克隆源码文本编辑器VS / VS Code / Rider改配置和看代码B站账号已登录获取 SESSDATA如果只是跑起来看效果装 .NET SDK 和 Git 就够了。想改代码建议用 Visual Studio 或者 Rider调试体验比 VS Code 好一些。4.2 获取 SESSDATA 并配置SESSDATA 是 B站的登录凭证存在浏览器的 cookie 里。操作步骤登录 B站网页版按 F12 打开开发者工具切到 Application 或 Storage 面板找到 Cookies 下的bilibili.com复制SESSDATA的值。这个值很长类似xxxxxx%2Cxxxxx%2Cxxxxx的格式。拿到后在项目根目录创建一个appsettings.json或者直接改Program.cs里的配置{ Bili: { SessData: 你的SESSDATA值, HostUid: 12345678 }, Raffle: { DynamicId: 987654321, WinnerCount: 3 } }HostUid是抽奖发起者的 UIDDynamicId是抽奖动态的 IDWinnerCount是中奖人数。这三个参数是跑通流程的最小配置。4.3 编译与运行打开终端进入项目目录执行# 还原依赖 dotnet restore # 编译 dotnet build -c Release # 运行命令行入口 dotnet run --project BiliRaffle.Cli如果一切正常你会看到控制台先输出「正在拉取转发列表…」然后显示拉取到的参与者数量最后打印中奖名单。第一次跑建议把WinnerCount设小一点比如 1方便验证。注意如果运行时报 403 或返回空列表先检查 SESSDATA 是否过期。B站的登录态有时效过期后需要重新获取。4.4 验证结果是否正确跑完之后怎么确认结果没毛病三个检查点第一参与者数量是否合理一条几千转发的动态去重后应该接近实际人数第二中奖者是否在参与者列表里可以手动搜一下 UID第三重复跑几次中奖名单应该不同除非用了固定种子。如果每次结果都一样检查随机数是不是被写死了。5. 避坑与常见问题那些让我熬夜的报错5.1 请求返回 -403 或 -412现象接口调用后返回错误码 -403或者 HTTP 状态码 412。原因请求头不完整缺少Referer、User-Agent或者 SESSDATA 过期。解决补齐请求头重新获取 SESSDATA。如果还是不行检查是不是请求频率太高被风控了加长Task.Delay的时间。5.2 分页拉取中途断掉现象拉到一半报错或者has_more一直是 true 导致死循环。原因B站接口在数据量大时可能返回异常结构或者 offset 没有正确更新。解决在循环里加最大页数限制比如最多拉 200 页对返回的 JSON 做空值判断result?.Data?.Items这种写法能避免空引用。5.3 去重后人数对不上现象去重后参与者数量比预期少很多。原因可能把不同用户误判为同一人或者 UID 字段解析错了。解决打印几个原始数据的 UID 看看确认字段映射正确。B站用户唯一标识是mid不是昵称昵称可以重复。5.4 中奖名单被质疑不公平现象开奖后有人觉得结果有猫腻。原因随机过程不透明没有留下可验证的记录。解决开奖前把参与者列表的哈希值公布出来开奖后用固定种子复现结果。虽然 BiliRaffle 本身不一定带这个功能但二次开发时可以加上。5.5 程序跑完就退出看不到结果现象命令行一闪而过或者输出完就关了。原因控制台程序执行完 Main 方法就结束了。解决在最后加一句Console.ReadLine()暂停或者把结果写到文件里。我一般会把中奖名单同时输出到result.txt方便后续核对。6. 二次开发把抽奖组件改成你自己的工具拿到源码后最直接的改造方向是换入口。BiliRaffle.Cli 是命令行你可以新建一个 WPF 项目引用 BiliRaffle.Core做一个带界面的抽奖工具。界面部分只需要三个输入框SESSDATA、动态 ID、中奖人数和一个按钮点击后调用RaffleEngine.Draw把结果展示在列表里。这样非技术用户也能用。第二个方向是加定时任务。抽奖动态往往有截止时间你可以用System.Threading.Timer或者 Quartz.NET在指定时间自动拉取数据并开奖。核心代码不用改只是把FetchAllReposts和Draw串起来挂到定时器上。第三个方向是扩展数据源。现在只拉了转发其实评论也可以作为参与方式。B站的评论接口和转发接口类似你可以照着FetchAllReposts写一个FetchAllComments然后把两个列表合并去重。注意评论接口的分页参数可能不一样需要单独调试。改造时有个技巧先把 Core 里的接口抽象出来。比如定义一个IParticipantSource接口转发和评论各实现一个类这样以后加新的参与方式比如弹幕就不用动开奖逻辑了。public interface IParticipantSource { TaskListParticipant FetchAsync(long dynamicId); } public class RepostSource : IParticipantSource { /* ... */ } public class CommentSource : IParticipantSource { /* ... */ }这样RaffleEngine只依赖接口符合开闭原则。我当初就是没做这层抽象后来加评论功能时把开奖逻辑改得一团糟后悔药都没得吃。最后一个建议不管怎么改开奖前一定先跑一遍「干跑」——只拉数据不开奖确认参与者数量和名单没问题再执行随机。从那以后我每次改完抽奖逻辑都强制走一遍干跑验证再也没出现过开奖后才发现数据拉错的情况。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑