技术说明
本站如何构建、存储了什么,以及哪些数字是实测、哪些是本站推算的。
页面渲染
Next.js 15 App Router、React 19 与严格模式的 TypeScript。样式使用 Tailwind v4,调色盘以主题令牌声明在单一样式表中;没有组件库也没有图表库。五种图表全是手写 SVG,因此能在服务器端渲染并继承主题变量——这也是浅色模式在运行期零成本的原因。
语言区段下的每一页都按请求渲染,而非静态生成。这是刻意的选择:页首会显示各会话的状态(谁登录了、他的书签),所以语言布局设置 dynamic = "force-dynamic",整个子树退出静态生成。语言本身放在网址里;middleware 先从 cookie、再从 Accept-Language 协商出语言,并把结果写到请求头上,让根布局能设置 <html lang>。
页面永远不直接调用官方 API,而是调用单一服务模块。它按请求决定要返回实时 API、本地缓存,或在未配置密钥时返回固定种子的演示数据——并把这个决定连同数据一起返回,让 UI 能加以标注。
本地索引
持久化使用 node:sqlite,即 Node 22 起内置的 SQLite 绑定。没有原生模块要编译、没有数据库服务器要运行、也没有 ORM——整个数据层就是一个 SQL 文件加上几个小型的类型化辅助函数。文件位置由 DATABASE_PATH 决定,默认 ./data/brawlinsights.db,并以 WAL 日志、外键启用与五秒 busy timeout 打开。
模式在每次启动时以 CREATE TABLE IF NOT EXISTS 创建。由于这不会给已有数据表补列,新列由另一个步骤处理:检查每张表并补上缺失的 ALTER TABLE,已有安装因此能直接获得新列,而不必推倒重来。
它保存官方 API 不提供的一切:
- 让名称搜索成为可能的玩家与俱乐部目录;
- 名称记录与俱乐部记录,由轮询之间的变化比对重建;
- 每小时的玩家进度快照,趋势图表的数据来源;
- 归档的对战记录,以及由其汇总的每日统计;
- 装备中的皮肤,与从观察到的资料学来的排位段位名称表;
- 账号、会话、书签、浏览记录、公告板帖子与公告。
与官方 API 通信
单一模块封装 api.brawlstars.com/v1。每个响应同时放入 Next 的数据缓存,带有各端点的重新验证窗口与缓存标签;玩家与俱乐部资料还会在短暂 TTL 内直接复用 SQLite 副本,之后才发出新请求。除 404 以外的失败一律改用最后存储的副本,而不是让页面报错;404 代表账号已不存在,页面报告未找到,刷新队列则把该标签从索引移除。
| 请求 | 重新验证间隔 |
|---|---|
| 玩家资料 | 120 s |
| 对战记录 | 60 s |
| 俱乐部、俱乐部成员 | 300 s |
| 排行榜 | 900 s |
| 活动轮换 | 600 s |
| 英雄名册 | 86400 s |
API 密钥绑定发出请求的公网 IP,因此 403 accessDenied.invalidIp 是生产环境最常见的失败。它会被专门检测,并在健康端点以直白提示呈现,而不是记成一条普通错误。
刷新队列
API 没有推送通道,也没有“自某时间起变更”的过滤器,保持新鲜的唯一办法就是轮询——而平等地轮询一切既慢又浪费。因此每个已收录的玩家都落在一个优先档位中,记录要老过该档位允许的年龄才成为刷新候选。候选再按档位高者先、记录旧者先处理。
在这条资料刷新流程中,只有 hot 与 warm 档位会连同资料一并拉取对战记录——冷账号自上次查看以来多半没再打过,而每份记录都是额外一个请求。俱乐部以 warm 门槛另有一轮扫描、书签俱乐部优先;刷新玩家时也会顺带刷新其俱乐部,让俱乐部页面不需要第三条队列就能保持新鲜。
不过对战记录并不受限于这些档位。另一条独立的采集流程——通常占每轮最大的份额——按各账号记录距上次收集的时间遍历整个索引(冷账号也在内),凡在 HARVEST_INTERVAL(30 分钟)内未被采集的玩家都会拉取一次。没有人查看的账号仍然在打对战,而那些对战正是强度榜样本增长的来源——建立语料库的是这条流程,不是资料队列。
每个对外请求都要先从令牌桶取得令牌,桶以每秒 BS_API_RPS(默认 8)个令牌补充,容量上限 BS_API_BURST(默认 16)。桶是单一共享对象,无论多少循环或手动刷新重叠,整体速率都守得住;取不到令牌的调用方会精确等待所需时间,而不是空转。
循环有两种驱动方式。其一是进程内定时器:每 SYNC_INTERVAL_MS(默认 30 秒)一轮,每轮最多花 SYNC_BUDGET 个请求(默认 100),正常情况下约一半给对战记录采集、约三分之一给玩家资料、其余给俱乐部;当还有超过一千个新发现的账号等待第一次资料拉取时,分配会反转为以资料优先。其二是在无法维持后台定时器的平台上,由外部调度器调用 /api/cron。重叠的循环会立即返回而不是倍增请求速率;开机后第一轮随机延迟数秒,避免重启风暴同时打向 API;遇到 429 或 IP 不符的 403 会提前中断循环,而不是猛敲一扇已经关上的门。每六小时另有一轮清理:删除 90 天前的归档对战、120 天前的汇总与一年前的快照。
玩家或俱乐部页上的“立即刷新”按钮通过独立端点绕过所有 TTL,但每个标签有 20 秒冷却,按住按钮也无法变成放大器。/api/health 报告实时或演示模式、索引大小、归档量、队列深度与最后一次错误。
对战归档
API 永远只返回玩家最近 25 场对战,也没有历史端点。本站所有回看超过 25 场的功能,都建立在那些窗口被归档的基础上:每次资料浏览与每次队列刷新都会存入新内容,并以键值确保重复看到同一场对战是无操作。
每场对战归档时,也会滚入以日、模式、地图、区间与英雄为键的每日汇总表,统计出场、胜场、平局与明星球员次数。每场对战更新四行——实际地图与合成的 all 地图,各写两次:一次记在粗区间(ranked 或 trophies),一次记在精确区带(如 ranked:masters、trophies:500-750)。all 地图行让全模式强度榜成为单次索引读取而非扫描;区带行则让强度榜上的段位与奖杯范围筛选是真实的,而不是装饰。友谊赛与无法识别玩家英雄的对战会被跳过。
生存战没有胜负字段,只有名次,因此前半名次计为胜场:单人 10 取 5、双人 5 取 3、其他 3 取 2。这条规则在胜率、强度榜与各英雄统计中一体适用。
由于归档来自本站见过的账号,它是样本而非全体对战总体——而且是偏向被查询账号的样本。这对绝对数量的影响远大于对相对形状的影响。
强度榜的计分方式
按原始胜率排名,会把上周手感火热、只打了 40 场的冷门英雄排上顶端;按出场率排名,只是给受欢迎程度换个标签。分数混合两者,且胜率按我们实际相信的程度打折。
rows = { b : picks(b) ≥ 200 }
globalWinRate = Σ wins / Σ picks
shrunk = (wins + globalWinRate * 1500) / (picks + 1500)
pickRate = picks / total picks
popularity = log10(1 + pickRate * rows * 3) / log10(4)
score = (shrunk - globalWinRate) * 100 + popularity * 1.81500 是以虚拟对战数表示的先验:200 场的英雄会被大幅拉回全体平均,30,000 场的则几乎保有自己的胜率。这就是经验贝叶斯收缩的想法,用得粗糙但透明。出场率项取对数,让出场十倍的英雄只值一点加分,而不是十倍分数。
接着从分数分布、而非固定门槛切出梯度:先在入榜英雄上计算平均与标准差,再按每位英雄高出或低于平均几个标准差归位——S 在 +1.25、A 在 +0.5、B 在 −0.25、C 在 −1、更低则为 D。值得知道的推论:梯度是相对于当前版本环境的,就算版本再平衡,榜上也永远有 S 级。
只有所选窗口至少累积 5,000 次出场(默认窗口七天)时,强度榜才会以真实数据建立。低于门槛时页面退回固定种子生成器并自我标注为演示数据,而不是拿几百场摆出一副自信的排名。
直接来自 API
以下字段直接读自官方响应、原样显示。API 公开的比多数社区封装所建模的更多,因此其他网站靠估的几个数字,在这里是实测值。
- 奖杯、最高奖杯、经验等级、3v3/单人/双人胜场
- 排位段位索引与显示名称、排位分数、赛季 id
- 赛季与历史最高的排位段位与分数
- 总威望等级、声望与声望阶级
- 各英雄:能力等级、段位、奖杯、威望、当前与最长连胜
- 各英雄:随身工具、星徽能力、装备、超级充能、装备中皮肤
- 俱乐部成员资格、成员名单与职位、入会奖杯门槛
- 各国排行榜与实时活动轮换
- 每位玩家最近 25 场对战,含模式、地图、类型、结果与奖杯变化
- 英雄名册本身,由同步脚本抓入生成文件——最后同步于 2026/09/15
本站推算
以下数字不以任何形式存在于 API 中。它们由本站观察到的数据计算而来,且每一项在出现之处都有标注。
- 名称记录与俱乐部记录——由前后轮询的差异比对建立
- 奖杯、排位分数、威望与声望走势——来自每小时快照表
- 强度榜、胜率、出场率与明星球员率——来自对战归档
- 皮肤使用率排行——统计自已收录资料的装备中皮肤字段
- 排位段位名称表——从观察到的资料学习,并非写死
- 估计游玩时间——由胜场数与经验等级推得,误差约 ±15%,因为 API 不提供计时
- 排位分数时间线——排位对战不含分数变化,胜败按约 ±30 分模拟,重建一个赛季的形状
- 各英雄的稀有度与定位——名册中仅有的两个 API 未提供的字段,按名称查内置对照表,查不到就标
unknown而不猜 - 计算器表格(升级成本、奖杯增减、星光宝箱概率、奖杯之路)——从游戏内转录,快照 2026-09-16
仍属模拟的部分
少数页面尚未由真实数据支撑,且会在页面上明说,而不是悄悄端出猜测:
- 排位段位分布。 页面已接入真实查询,统计每份完整收录资料上记录的排位段位;只有在带段位的已收录资料少于 200 份时,才退回模拟的天梯形状——样本太小又偏斜,硬画形状反而误导。退回时会明说,并标出当前的数量。
- 样本太薄的强度榜。 归档出场少于 5,000 次的模式、地图或区间会退回模拟分布,而不是用寥寥数场排名。页面会标出已有数量与所需数量。
- 徽章、对战卡片与资料称号。 这些以任何形式都不存在于 API 中,因此对应的排行页是布局演示,并带有明确告示。
- 演示模式。 未配置 API 密钥时,整站以你搜索的标签为种子的固定生成器运行,同一标签永远产生相同数字。受影响的每一页都有警示横幅。
索引对搜索结果的意义,请见搜索规格。