前言

一款基于Python和大语言模型的云顶之弈智能分析工具,提供三种阵容输入方式:截图识别、文本/JSON导入以及可视化棋盘。该工具采用RAG检索增强生成技术,结合KR服高端局对战数据,为用户提供专业的阵容评价和进阶发展建议。

作为一名《云顶之弈》(Teamfight Tactics, TFT)的普通玩家,同时也是计算机专业的学生,我研究了拳头游戏(Riot Games)对外开放的数据接口,并写了一个自动抓取赛季数据的 Python 脚本。

这篇博客将完整介绍这个小项目的实现思路,作为该项目中下载的第一步,并穿插一些我在学习过程中收获的技术点与思考。

工具最终效果:运行一条命令,就能在当前文件夹生成 6 个 JSON 文件,包含当前赛季全部英雄、羁绊、装备的结构化数据。

数据从哪里来?—— CommunityDragon 与 Data Dragon

在动手之前,先要解决数据源的问题。官方提供给开发者的主要有两个渠道:

数据源 特点 时效性 使用场景
Data Dragon (DDragon) Riot 官方 CDN,稳定可靠 通常滞后于正式服几天 基础数据、历史版本
CommunityDragon (CDragon) 社区维护,直接从游戏客户端解包 接近实时,甚至包含 PBE 内容 最新赛季、测试服数据

我们的脚本会优先尝试 CDragon,因为它的数据更新最快,且字段更加丰富;如果 CDragon 请求失败,则自动回退到 DDragon,保证工具始终能用。

代码整体结构

┌─────────────────┐
│ 命令行参数解析   │
└────────┬────────┘
         ▼
┌─────────────────┐
│ 尝试 CDragon    │──成功──► 解析数据
└────────┬────────┘
         │失败
         ▼
┌─────────────────┐
│ 尝试 DDragon    │──成功──► 解析数据
└────────┬────────┘
         │失败
         ▼
┌─────────────────┐
│ 报错退出         │
└─────────────────┘

数据解析完成后,统一保存为以下文件(全部使用英文 ID):

tft_champion_db.json —— 英雄详情(费用、羁绊列表、基础属性)

tft_trait_db.json —— 羁绊信息(激活阈值、效果描述)

tft_item_db.json —— 装备信息(名称、合成配方)

tft_champion_trait_map.json —— 英雄→羁绊映射表

tft_trait_champion_dict.json —— 羁绊→英雄聚合表

tft_meta.json —— 版本元数据(来源、版本号、生成时间)

关键函数

1. 获取 DDragon 最新版本号

def get_ddragon_version() -> str:
    r = requests.get("https://ddragon.leagueoflegends.com/api/versions.json", timeout=15)
    return r.json()[0]

DDragon 提供了一个版本列表 API,返回形如 ["15.12.1", "15.11.1", ...] 的数组,第一个元素即最新版本。

这个版本号将作为后续请求 DDragon 具体数据的路径参数。

小思考:为什么这里要加 timeout=15?如果不设置超时,万一网络波动,程序就会一直卡住,用户体验极差。编写任何网络请求代码时,超时设置是必须的。(此案例不来源于网络素材)

2. 去除 API 名称中的冗余前缀 —— _strip

def _strip(s: str, n: int) -> str:
    s = re.sub(rf"^TFT{n}_", "", s)
    s = re.sub(r"^TFTSet\d+_", "", s)
    s = re.sub(r"^Set\d+_", "", s)
    s = re.sub(r"^TFT_", "", s)
    return s

无论是 DDragon 还是 CDragon,英雄或羁绊的 API 名称通常包含类似 TFT16_ 或 TFTSet16_ 的前缀。

为了便于跨赛季数据对比和后续查询,我们需要将这些名称转换为简洁格式,例如将 TFT16_Aatrox 转换为 Aatrox。

为此,我们使用正则表达式依次匹配并删除四种常见前缀格式,其中 rf 前缀支持动态插入赛季编号。

学习点:正则表达式是处理字符串的利器,re.sub(pattern, replacement, string) 会将匹配到的部分替换为空字符串,达到“删除”的效果。

3. 从 CommunityDragon 抓取数据

def fetch_cdragon(set_number: int) -> Optional[Dict]:
    patches = ["latest", "16.12", "16.10", "16.8", "16.6", "16.5", "16.3", "16.1", "pbe"]
    for patch in patches:
        url = f"https://raw.communitydragon.org/{patch}/cdragon/tft/en_us.json"
        # ... 发起请求 ...
        data = r.json()
        sets = data.get("sets", {})
        sd = sets.get(str(set_number)) or sets.get(f"set{set_number}") or sets.get(set_number)

CDragon 的文件按 patch 版本组织(例如 16.12pbe)。由于不知道目标赛季存在于哪个 patch 下,我们按顺序尝试一个预设列表。

拿到 JSON 后,需要找到当前赛季的数据集。CDragon 的顶层对象通常有一个 sets 字段,键可能是 "16""set16" 或数字 16,因此用了三种方式尝试取值。

如果能取到非空数据(包含 champions 和 traits),就返回完整数据块,否则继续尝试下一个 patch。

技术要点

请求之间加入 time.sleep(0.4)避免因频率过高被服务器短暂封禁——这是爬虫最基本的礼仪。

使用 Optional[Dict] 类型标注,清晰地告诉调用者:可能返回 None

4. DDragon 备用数据源

def fetch_ddragon(set_number: int, version: str) -> Optional[Dict]:
    base = f"https://ddragon.leagueoflegends.com/cdn/{version}/data/en_US"
    prefix = f"TFT{set_number}_"
    for ep, key in [("tft-champion.json", "champions"), ...]:
        r = requests.get(f"{base}/{ep}")
        raw = r.json().get("data", {})
        filtered = {k: v for k, v in raw.items() if k.startswith(prefix)}
        result[key] = filtered

DDragon 的数据按文件类型拆分(英雄、羁绊、装备分开)。

每个文件返回一个以 API 名称为键的大字典,我们需要筛选出以 TFT{赛季号}_ 开头的项,才是当前赛季的数据。

虽然 DDragon 的数据量较少(不包含基础装备等),但足以作为后备方案。

5. 数据解析

解析函数 parse_cdragon(raw) 是整个脚本的大脑。

我将重点讲解其中最精巧的设计:羁绊反查表的建立

背景问题

CDragon 英雄对象的 traits 字段是一个字符串列表,例如 ["Bruiser", "Quickstriker"]

但这里的 "Quickstriker" 是显示名称(display name),而羁绊对象的 apiName 经过 strip 后得到的是 "Rapidfire"

如果直接用显示名称去匹配羁绊的 short_id,会直接失败。

解决方案:建立 name_to_short 反查表

name_to_short: Dict[str, str] = {}

for t in raw.get("traits", []):
    api = t.get("apiName", "")
    short = _strip(api, n)
    name  = t.get("name", short)
    # 双向注册
    name_to_short[short] = short
    name_to_short[name]  = short

这样一来,无论英雄列表里是 short_id 还是显示名称,都能统一转换成标准的 short_id。

for t in raw_traits:
    t_stripped = _strip(t, n)
    traits.append(name_to_short.get(t_stripped, name_to_short.get(t, t_stripped)))

被脏数据气笑过

6. 保存聚合数据

解析完成后,我们不仅保存原始信息,还额外生成了两个聚合文件:

tft_champion_trait_map.json{"TFT16_Aatrox": ["Bruiser", "Dragon"]}

tft_trait_champion_dict.json:以羁绊为键,汇总所有拥有该羁绊的英雄列表及激活阈值。

这两个文件可以极大地方便下游应用——比如查询“哪些英雄拥有 Bruiser 羁绊”,直接读取第二个文件即可,无需遍历整个英雄数据库。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐