王者战力查询API实战从英雄目录到战力榜

背景:为什么这个接口适合做战力数据工具

王者荣耀战力数据常见的使用场景并不复杂:输入英雄,选择系统和平台,拿到不同省、市、区县的战力分布,再根据最低战力、最高战力或完整榜单做展示。难点在于,英雄 ID、区服枚举、数据层级和接口响应结构如果不统一,后续做前端看板、机器人查询或定时分析时会反复写胶水代码。

极数本源的「王者战力查询」接口把这件事压缩成一个 HTTP GET 接口:

  • action=heroes:获取英雄目录,包含中文名、ename、称号和头像地址。
  • action=query:查询指定英雄在指定区服的全国战力分布。
  • type=all|min|max:分别对应完整列表、最低战力、最高战力三种返回策略。

截至 2026-06-18 实测,action=heroes 返回 130 个英雄;query&hero_id=107&zone=aqq&type=min 可以拿到赵云在 Android QQ 区的最低战力结构化数据。

接口能力速览

接口地址:

text 复制代码
GET https://v1.apizero.cn/api/wzry

核心参数如下:

参数 必填 说明 示例
action heroes 获取英雄列表;query 查询战力 query
hero 条件必填 英雄中文名,action=query 时与 hero_id 二选一 赵云
hero_id 条件必填 英雄 ename,优先级高于 hero 107
zone 查询必填 区服代码 aqq
type allminmax,默认 all min

区服代码建议在业务侧做成枚举,避免用户输入不可控:

zone 系统 平台
aqq Android QQ
awx Android 微信
iqq iOS QQ
iwx iOS 微信

type 的差异尤其重要:

type 返回重点 适合场景
all 约 90 条省、市、区县混合战力列表 地图、表格、筛选分析
min 省级、市级、区县级最低战力及相近分数 找低门槛地区、做推荐
max 省级、市级、区县级最高战力及相近分数 展示顶尖战力、做对比

这里有一个容易踩坑的字段名:响应里的 rank 表示战力分数,不是榜单名次。比如 rank: 10577 的含义是战力分为 10577,而不是第 10577 名。

认证、限流与缓存策略

接口支持匿名调用,也支持 API Key 鉴权。匿名调用适合开发调试;如果要做定时任务、Web 服务或多人查询工具,建议申请 API Key,并在后端通过 Authorization 请求头携带。

text 复制代码
Authorization: Bearer sk_live_xxxxxxxxxxxxxx

配额和缓存信息可以直接影响工程设计:

项目 匿名调用 已认证用户
每日免费额度 50 次 200 次
QPS 限制 2 req/s 5 req/s
英雄列表缓存 24 小时 24 小时
战力数据缓存 1 小时 1 小时

实践中建议这样处理:

  • 英雄列表本地缓存 24 小时,启动时或缓存过期后再刷新。
  • 批量查询时按更保守的速度限流,匿名调用至少间隔 0.5 秒。
  • API Key 不要写进前端代码,前端请求自己的后端,由后端转发。
  • 战力数据有 1 小时缓存,分钟级高频轮询没有必要。

先看真实响应结构

英雄列表响应的 data.list 是后续查询的基础。每个英雄都有 nameename,其中 ename 就是查询时可用的 hero_id

json 复制代码
{
  "code": 0,
  "msg": "成功",
  "data": {
    "action": "heroes",
    "count": 130,
    "list": [
      {
        "name": "赵云",
        "title": "苍天翔龙",
        "ename": "107",
        "avatar": "https://game.gtimg.cn/images/yxzj/img201606/heroimg/107/107.jpg"
      }
    ]
  }
}

查询 type=mintype=max 时,rank_data 不是数组,而是 extreme + similar 结构:

json 复制代码
{
  "action": "query",
  "hero": {
    "name": "赵云",
    "title": "苍天翔龙",
    "ename": "107"
  },
  "zone": {
    "code": "aqq",
    "system": "Android",
    "platform": "QQ"
  },
  "type_code": "min",
  "syn_date": "2026-06-18",
  "rank_data": {
    "extreme": {
      "province": {
        "address": "天津",
        "level": "province",
        "rank": 10577
      }
    },
    "similar": {
      "province": []
    }
  }
}

如果是 type=allrank_data 会变成列表,每一项包含 addresslevelrankadcode。因此代码里不能把三种 type 的返回结构混为一谈。

Python 封装:查询赵云最低战力并导出 CSV

下面这份脚本只使用 Python 标准库,可以直接运行。没有 API Key 时会走匿名调用;如果配置了环境变量 APIZERO_KEY,脚本会自动加上鉴权头。

保存为 wzry_power.py

python 复制代码
import csv
import json
import os
import time
from pathlib import Path
from typing import Any
from urllib.parse import urlencode
from urllib.request import Request, urlopen


BASE_URL = "https://v1.apizero.cn/api/wzry"
API_KEY = os.getenv("APIZERO_KEY")

ZONE_NAMES = {
    "aqq": "Android QQ",
    "awx": "Android 微信",
    "iqq": "iOS QQ",
    "iwx": "iOS 微信",
}

LEVEL_NAMES = {
    "province": "省级",
    "city": "市级",
    "district": "区县级",
}

ERROR_HINTS = {
    4000: "参数错误:检查 action、zone、type,或确认 hero/hero_id 是否存在",
    4015: "匿名调用每日额度已用完:请配置 API Key",
    4029: "QPS 超限:降低请求频率后重试",
    4030: "今日额度已用完:等待额度刷新或调整调用策略",
    5020: "上游 HTTP 失败:稍后重试",
    5021: "上游响应异常或该英雄在该区服暂无战力数据",
}


def call_wzry(params: dict[str, Any], retries: int = 2) -> dict[str, Any]:
    headers = {}
    if API_KEY:
        headers["Authorization"] = f"Bearer {API_KEY}"

    for attempt in range(retries + 1):
        query = urlencode(params)
        request = Request(f"{BASE_URL}?{query}", headers=headers, method="GET")
        with urlopen(request, timeout=15) as response:
            payload = json.loads(response.read().decode("utf-8"))

        if payload.get("code") == 0:
            return payload["data"]

        code = int(payload.get("code", -1))
        message = payload.get("msg", "未知错误")
        hint = ERROR_HINTS.get(code, "未收录错误码,请查看接口返回")

        if code == 4029 and attempt < retries:
            time.sleep(1.5 * (attempt + 1))
            continue

        raise RuntimeError(f"API 调用失败 code={code}, msg={message}, hint={hint}")

    raise RuntimeError("重试后仍未成功")


def fetch_heroes() -> list[dict[str, Any]]:
    data = call_wzry({"action": "heroes"})
    return data["list"]


def find_hero(heroes: list[dict[str, Any]], name: str) -> dict[str, Any]:
    for hero in heroes:
        if hero["name"] == name:
            return hero
    raise ValueError(f"英雄不存在:{name}")


def query_power(hero_id: str, zone: str = "aqq", type_code: str = "min") -> dict[str, Any]:
    if zone not in ZONE_NAMES:
        raise ValueError(f"非法区服:{zone}")
    if type_code not in {"all", "min", "max"}:
        raise ValueError(f"非法查询类型:{type_code}")

    return call_wzry(
        {
            "action": "query",
            "hero_id": hero_id,
            "zone": zone,
            "type": type_code,
        }
    )


def flatten_minmax(data: dict[str, Any]) -> list[dict[str, Any]]:
    hero_name = data["hero"]["name"]
    zone_code = data["zone"]["code"]
    type_code = data["type_code"]
    syn_date = data["syn_date"]
    rank_data = data["rank_data"]

    rows: list[dict[str, Any]] = []
    for level, level_name in LEVEL_NAMES.items():
        extreme_item = rank_data.get("extreme", {}).get(level)
        if extreme_item:
            rows.append(
                {
                    "hero": hero_name,
                    "zone": zone_code,
                    "type_code": type_code,
                    "syn_date": syn_date,
                    "level": level_name,
                    "group": "extreme",
                    "address": extreme_item["address"],
                    "rank": extreme_item["rank"],
                    "adcode": extreme_item.get("adcode", ""),
                }
            )

        for item in rank_data.get("similar", {}).get(level, []):
            rows.append(
                {
                    "hero": hero_name,
                    "zone": zone_code,
                    "type_code": type_code,
                    "syn_date": syn_date,
                    "level": level_name,
                    "group": "similar",
                    "address": item["address"],
                    "rank": item["rank"],
                    "adcode": item.get("adcode", ""),
                }
            )

    return rows


def write_csv(rows: list[dict[str, Any]], output: Path) -> None:
    fieldnames = [
        "hero",
        "zone",
        "type_code",
        "syn_date",
        "level",
        "group",
        "address",
        "rank",
        "adcode",
    ]
    with output.open("w", newline="", encoding="utf-8-sig") as file:
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)


def main() -> None:
    heroes = fetch_heroes()
    hero = find_hero(heroes, "赵云")

    data = query_power(hero_id=hero["ename"], zone="aqq", type_code="min")
    rows = flatten_minmax(data)

    for row in rows[:8]:
        print(
            f"{row['hero']} | {row['zone']} | {row['level']} | "
            f"{row['group']} | {row['address']} | 战力 {row['rank']}"
        )

    write_csv(rows, Path("zhaoyun_aqq_min.csv"))
    print("已导出:zhaoyun_aqq_min.csv")


if __name__ == "__main__":
    main()

运行:

bash 复制代码
python wzry_power.py

如果你有 API Key,可以这样配置:

bash 复制代码
export APIZERO_KEY="sk_live_xxxxxxxxxxxxxx"
python wzry_power.py

这段封装做了几件生产环境里很有价值的事:

  • 请求参数只使用文档中存在的字段:actionhero_idzonetype
  • zonetype 做本地校验,提前拦截无效输入。
  • 4029 QPS 超限做了简单退避重试。
  • min/max 的嵌套结构摊平成 CSV,方便 Excel、BI 或 Pandas 继续处理。
  • 使用 utf-8-sig 写 CSV,Windows Excel 打开中文更省心。

如果要处理 type=all,应该怎么展开

type=all 的结构更适合做地图和表格,因为它直接返回一组地区战力记录。可以单独写一个展开函数:

python 复制代码
def flatten_all(data: dict[str, Any]) -> list[dict[str, Any]]:
    hero_name = data["hero"]["name"]
    zone_code = data["zone"]["code"]
    syn_date = data["syn_date"]

    rows = []
    for item in data["rank_data"]:
        rows.append(
            {
                "hero": hero_name,
                "zone": zone_code,
                "syn_date": syn_date,
                "level": item["level"],
                "address": item["address"],
                "rank": item["rank"],
                "adcode": item.get("adcode", ""),
            }
        )
    return rows

调用时只需要把查询类型改成 all

python 复制代码
data = query_power(hero_id="107", zone="aqq", type_code="all")
rows = flatten_all(data)

注意:all 返回的 level 仍然是英文代码,例如 provincecitydistrict。如果是面向用户展示,建议在前端或后端统一映射为「省级」「市级」「区县级」。

错误码应该如何落到业务逻辑

接口返回的 HTTP 状态码不一定能表达全部业务错误,真正要看的还是 JSON 里的 code

code 含义 建议处理
4000 参数错误、英雄未找到 给用户明确提示,并打印参数用于排查
4015 匿名每日额度用完 引导配置 API Key
4029 QPS 超限 退避重试,批量任务降低并发
4030 今日额度用完 停止任务,等待额度刷新
5020 上游 HTTP 失败 稍后重试,记录 request_id
5021 上游格式异常或暂无数据 展示空态,不要当成程序崩溃

对用户侧工具来说,最重要的是区分「用户可修正」和「系统暂不可用」:

  • 4000 通常是参数问题,应该让用户换英雄、换区服或检查输入。
  • 40154030 是额度问题,应该减少调用或启用 Key。
  • 50205021 更像数据源问题,应该保留日志并给出友好的空态。

工程化落地建议

1. 英雄目录做本地索引

用户通常输入的是中文英雄名,但查询时用 hero_id 更稳定,因为 hero_id 的优先级高于 hero。建议服务启动后先拉取 action=heroes,生成两个索引:

python 复制代码
heroes = fetch_heroes()
by_name = {hero["name"]: hero for hero in heroes}
by_id = {hero["ename"]: hero for hero in heroes}

这样既能支持中文搜索,也能在内部统一用 ename 查询。

2. 批量查询要主动限速

假设要查询 20 个英雄、4 个区服、3 种类型,总请求数是:

text 复制代码
20 * 4 * 3 = 240

这个量级已经超过单日免费额度,且很容易触发 QPS 限制。因此批量任务要先判断是否真的需要全部组合。常见优化方式包括:

  • 默认只查用户关心的 zone
  • 首页只展示 min,详情页再查 all
  • 同一英雄同一区服的数据缓存 1 小时。
  • 离线任务分批运行,不要瞬间打满。

3. 前端展示要解释 rank 字段

如果直接把字段名 rank 展示给用户,很容易被理解成「排名」。更好的做法是在 UI 上显示为「战力」或「战力分」。字段名不改,展示语义要改。

示例:

text 复制代码
天津 | 省级最低战力:10577
新疆/克州 | 市级最低战力:6315
内蒙古/乌兰察布市/化德县 | 区县级最低战力:1974

4. 记录 request_id 方便排查

当接口返回错误时,响应里可能带有 request_id。生产环境建议把它和请求参数一起写入日志。这样排查上游异常、额度问题或格式变化时,会比只看错误文本有效得多。

可以扩展成哪些项目

这个接口非常适合做小而完整的数据项目:

  • 王者英雄战力查询网页:英雄搜索、区服切换、最低战力推荐。
  • QQ/微信群机器人:输入「赵云 安卓QQ 最低」返回省市区最低战力。
  • 数据看板:按 province/city/district 聚合,展示不同英雄的门槛变化。
  • 定时报告:每天拉取重点英雄数据,保存 CSV 后生成趋势图。
  • 游戏资料聚合页:英雄头像、称号、战力区服数据一起展示。

如果只是做个人工具,Python 脚本加 CSV 已经够用;如果要做多人访问服务,建议加一层后端缓存,把 API Key、限流和错误处理都放在服务端。

小结

王者战力查询 API 的优势是边界清晰:一个接口、两个 action、四个区服、三种查询类型。真正写代码时,重点不在「怎么发 GET 请求」,而在于把返回结构理解透:heroes 是英雄目录,query 是战力数据;all 返回列表,min/max 返回 extreme + similarrank 是战力分数,不是名次。

只要在工程侧补上本地缓存、参数枚举、错误码处理和限流策略,就可以很快把它包装成一个可用的战力查询工具。

参考资料:极数本源王者战力查询 API 文档