
写程序的人都有过这种经历明明看过很多次宝可梦图鉴但真要你说清楚皮卡丘和伊布的区别绝大多数人只能回答“一个是电系一个是普通系”“一只黄色的一只褐色的”。这些回答没有错但它经不起追问两者的种族值差距到底有多大谁的速度更快谁的攻击更有优势普通玩家可以靠印象回答但做技术的读者应该换一种方式——把问题变成数据用代码去接近它。这也是“宝可梦学院”系列第三集想做的主题皮卡丘与伊布初次接近。这里的“接近”不是剧情里的相遇而是我们用 Python 面对两只宝可梦的官方结构化数据完成一次完整的 API 调用、字段解析、数据对比和可视化呈现。本文会同步解决一个很多人容易忽略的问题当你面对一个公开接口时如何从“能跑通”走向“写得稳”。我的判断很明确这不是一篇纯粹的宝可梦粉丝文而是一个以宝可梦数据为载体的 Python 数据分析小项目。它适合正处于爬虫和数据分析入门阶段的读者也适合想找一个有趣练手项目来巩固 requests、pandas、matplotlib 知识的人。读完这篇文章你会得到一份可以直接运行的代码也会掌握一套之后不管采集什么公开数据都可以复用的流程。1. 这篇文章真正要解决的问题很多后端开发者对 REST API 并不陌生但真正切换到“数据分析”视角时往往会卡在三个地方第一个是数据获取。知道有公开接口但不知道接口返回的 JSON 长什么样更不知道怎么把嵌套结构变成规整的表格。第二个是数据对比。拿到了数据却只会一行行打印不会用 pandas 做视角更清晰的横向比较。第三个是结果呈现。数据算出来了但不知道怎么画成图表让结论一眼可见。这三个问题叠加在一起就是一门“准实战课”用两只宝可梦的数据把从请求到可视化的完整链路走一遍。为什么选皮卡丘和伊布因为这两只宝可梦在印象层面很容易被新手混淆都是小型、毛茸茸、人气极高的角色。但它们的种族值分布其实差异明显尤其体现在特攻、速度等维度上。这种“直觉认知”和“数据事实”之间的偏差恰好是学习数据分析最好的切入点数据不是用来验证你已知的东西而是用来修正你以为正确的东西。这篇文章的适用读者也可以收敛一下Python 基础语法已经学完想找项目巩固的人。刚开始接触 requests、pandas、matplotlib 的初学者。对宝可梦题材感兴趣想把兴趣转化为技术练习的开发者。还没想清楚“API 返回的 JSON 到底怎么进入业务代码”的后端新人。如果你不属于这几类这篇文章依然有参考价值。因为它的核心方法论是通用的一切能用数据描述的对象都可以用同一种模式完成“采集—清洗—分析—可视化”。宝可梦只是载体不是限制。2. PokeAPI 接口与基础概念2.1 REST API 与 JSON 的快速回顾开始写代码之前有一组概念必须先说清楚否则后面看代码时会觉得处处是“魔法”。REST API 简单理解就是通过 HTTP 请求去操作或读取某个服务器上的资源。常见的请求方式有 GET、POST、PUT、DELETE。本文只用到 GET也就是“只读不写”。你向接口地址发一个 GET 请求服务器返回一段结构化文本多数情况下是 JSON也可能是 XML、YAML 或普通文本。JSON 是 JavaScript Object Notation 的缩写虽然名字里有 JavaScript但它已经成为几乎所有后端接口通用的数据交换格式。JSON 的基本结构只有两种对象{}即键值对的集合数组[]即有序的列表。字段名是字符串值可以是字符串、数字、布尔值、null、对象或数组。理解这一层拿到接口文档之后才不会慌张。2.2 PokeAPI 是什么PokeAPI 是一个面向开发和教学的公开宝可梦数据接口提供宝可梦名称、属性、种族值、技能、进化链、形象图片等大量结构化数据。它不需要注册账号不需要 API Key直接发起 GET 请求即可得到结果。这一点对初学者非常友好因为你不用先处理鉴权流程可以把全部注意力放在数据结构和代码逻辑上。本文使用到的核心端点是https://pokeapi.co/api/v2/pokemon/{name}把{name}替换成小写的宝可梦英文名比如pikachu或eevee就会返回该宝可梦的详细数据。返回内容非常庞大但本文只需要其中三部分name宝可梦名称。types属性列表皮卡丘是电属性伊布是一般属性。stats种族值列表包含 HP、攻击、防御、特攻、特防、速度六项。这里顺带解释一个容易混淆的概念种族值不是个体值也不是努力值。种族值是宝可梦种类层面的基础能力参数决定了同一种宝可梦的成长上限与属性偏向。皮卡丘的基础速度、特攻都很突出这与其“灵巧、电气系”的设定一致伊布则以均衡著称各项种族值相对平均进化后才会出现明显的属性分化。文章后面的代码不涉及这些游戏数值的具体来源只把它们当作 API 返回的数据项来使用。2.3 表格对比直接爬官网与调用 PokeAPI 的区别很多新手会问为什么不直接去宝可梦百科或者图鉴网站抓页面这里有一个关键区别对比维度页面爬虫调用 PokeAPI数据结构HTML需要解析标签JSON字段语义清晰解析成本需要 BeautifulSoup / XPath直接用字典索引维护成本页面改版后脚本极易失效接口保持稳定适配成本低学习价值更偏向爬虫方向更偏向 API 使用与数据工程请求语义获取“给人看的页面”获取“给程序用的数据”这并不意味着页面爬虫没有价值而是在处理有公开 API 的数据源时调用 API 是更合理、更稳妥、更高效的第一选择。PokeAPI 把“获取数据”这件事的门槛降到了最低你不需要处理反爬、登录态和页面结构可以把时间花在真正的数据处理上。3. 环境准备与前置条件实操之前先确认环境。本文示例代码风格偏通用依赖版本以你本机当前实际安装为准。以下是我的建议起点不是硬性限制操作系统Windows 10/11、macOS、主流 Linux 发行版均可。Python 版本建议 3.8 及以上。包管理工具pip 或 conda。代码编辑器VS Code、PyCharm 或任何你熟悉的编辑器。建议在项目目录下创建虚拟环境防止依赖污染系统 Python。以 Windows 为例命令如下mkdir pokemon-academy cd pokemon-academy python -m venv venv venv\Scripts\activatemacOS 和 Linux 下激活虚拟环境的命令是source venv/bin/activate激活后命令行提示符前方会出现(venv)。如果你用的是 conda也可以先conda create -n pokemon python3.9再conda activate pokemon效果等价。接着安装依赖。为了方便复现我建议在项目根目录创建一个requirements.txt文件内容如下requests2.31.0 pandas2.1.4 matplotlib3.8.2 seaborn0.13.0然后执行安装pip install -r requirements.txt版本号可以不是完全一致但尽量保持大版本相近避免因为 API 变化导致示例代码无法运行。安装完成后可以快速验证环境python -c import requests, pandas, matplotlib, seaborn; print(env ok)如果看到env ok说明依赖全部可用。这一小步很多人会跳过但它能提前暴露依赖缺失问题节省后面排错的时间。4. 核心流程拆解整个项目的流程可以拆成四步每走一步都要清楚自己在做什么。4.1 发起请求获取原始 JSON使用requests.get()访问 PokeAPI把返回内容转成 JSON 对象。这一步的核心目标是“拿到数据”。最容易犯的错误是不设置超时时间也不检查状态码一旦接口超时或地址写错程序会一直挂着或者报出难以理解的错误。因此无论如何都要加timeout参数并且用raise_for_status()检查响应状态。4.2 从 JSON 中提取目标字段PokeAPI 返回的数据层级较多。types是一个数组数组里的每个元素又有slot和type字段stats也是一个数组每个元素有base_stat、effort、stat三个字段。这要求我们写代码时必须认清结构先取数组再遍历再取内层字段。初学者最容易在这里遇到KeyError原因往往是写错了内层字段名或者没有考虑到某些字段可能不存在。4.3 构造规整的 DataFrame把两只宝可梦的提取结果统一装进 pandas 的 DataFrame。每一行代表一只宝可梦每一列代表一个维度。这样做的意义是让“对比”这件事变得非常简单直接打印表格就能看出差异直接取列就能做计算直接交给 seaborn 就能绘图。4.4 可视化呈现差异用柱状图对比六项种族值让两组数据在同一坐标系下呈现。这一步是可选的但对理解数据有很大帮助。一个优秀的分析流程不应该止步于打印表格能够把结论“画出来”才算完整。每一步出错的表现都不一样。第一步容易超时第二步容易报 KeyError第三步容易出现 NaN第四步容易出中文显示问题。下面的完整代码会专门针对这些坑做防护。5. 完整示例与代码实现下面所有代码都按文件拆分讲解你可以直接复制使用。5.1 获取并解析数据的核心函数新建文件pokemon_data.py代码如下# 文件路径pokemon_data.py import requests def fetch_pokemon_data(name: str) - dict: 通过 PokeAPI 获取指定宝可梦的原始 JSON 数据。 Args: name: 宝可梦英文名如 pikachu、eevee。 Returns: 解析后的 JSON 字典。 url fhttps://pokeapi.co/api/v2/pokemon/{name.lower().strip()} resp requests.get(url, timeout10) resp.raise_for_status() return resp.json() def parse_pokemon(data: dict) - dict: 从原始 JSON 中提取需要的字段。 Args: data: fetch_pokemon_data 返回的字典。 Returns: 扁平化后的字典包含名称、属性、六项种族值。 name data.get(name, unknown) # types 是数组里面每个元素包含 type.name types [item[type][name] for item in data.get(types, [])] # stats 是数组里面每个元素包含 base_stat 和 stat.name stats {} for item in data.get(stats, []): stat_name item[stat][name] stats[stat_name] item[base_stat] result { name: name, types: , .join(types), } result.update(stats) return result这个文件里的两个函数是后面所有分析的基础。fetch_pokemon_data只负责“和数据源对话”parse_pokemon只负责“把原始数据映射成业务需要的结构”。把职责拆开的好处是测试的时候可以单独 mock 任何一个环节不用每次都发真实请求。5.2 构建 DataFrame 的脚本新建文件build_dataset.py代码如下# 文件路径build_dataset.py import pandas as pd from pokemon_data import fetch_pokemon_data, parse_pokemon def build_comparison_dataframe(pokemon_names): 批量获取多只宝可梦数据并组装成 DataFrame。 rows [] for name in pokemon_names: raw fetch_pokemon_data(name) parsed parse_pokemon(raw) rows.append(parsed) return pd.DataFrame(rows) if __name__ __main__: names [pikachu, eevee] df build_comparison_dataframe(names) print(df.to_string(indexFalse))运行这段代码后你会看到一张规整的表格。表格中包含名称、属性、hp、attack、defense、special-attack、special-defense、speed 等列。具体数值我在这里不做断言因为 PokeAPI 的字段值由数据源维护未来可能调整。但你只要看到两个不为空的行、且各列均为数字就说明解析成功。这里有一个关键点parse_pokemon使用了data.get(types, [])和data.get(stats, [])即使接口结构微调也不会因为缺少字段而直接崩溃。这种“优先取字典的容错方式”是写数据处理代码时应该养成的习惯。5.3 可视化对比代码新建文件visualize.py代码如下# 文件路径visualize.py import matplotlib.pyplot as plt import pandas as pd import seaborn as sns STAT_COLUMNS [ hp, attack, defense, special-attack, special-defense, speed, ] def plot_stat_comparison(df: pd.DataFrame, save_path: str pokemon_stat_comparison.png): 绘制两只宝可梦种族值的柱状对比图。 df_melted df.melt( id_varsname, value_varsSTAT_COLUMNS, var_namestat, value_namevalue, ) plt.figure(figsize(10, 6)) ax sns.barplot( datadf_melted, xstat, yvalue, huename, ) ax.set_title(Pikachu vs Eevee Base Stats) ax.set_xlabel(Stat) ax.set_ylabel(Base Value) ax.legend(titlePokemon) ax.grid(axisy, linestyle--, alpha0.5) plt.tight_layout() plt.savefig(save_path, dpi120) plt.show()这段代码的核心是melt函数。你可以把它理解为“把宽表转成长表”原来的表格是一行一只宝可梦、一列一项属性转置后变成每只宝可梦有多行每行对应一个属性。这样 seaborn 才能在同一张图中按属性分组、按宝可梦着色。5.4 完整的调度入口为了让你一条命令跑完全部流程再提供一个汇总脚本main.py# 文件路径main.py from build_dataset import build_comparison_dataframe from visualize import plot_stat_comparison def main(): names [pikachu, eevee] df build_comparison_dataframe(names) print(数据预览) print(df.to_string(indexFalse)) plot_stat_comparison(df) print(图片已保存为 pokemon_stat_comparison.png) if __name__ __main__: main()执行命令python main.py如果一切正常你会先看到 DataFrame 的打印结果然后弹出一张柱状图图片同时保存到当前目录。到这里“皮卡丘与伊布初次接近”这个主题的代码路线就全部跑通了。6. 运行结果与效果验证程序运行成功不等于结果正确。判断这个项目的成功标准有三个层次。第一个层次程序无报错。这意味着环境依赖、网络请求、代码语法三个方面都是通的。第二个层次DataFrame 中出现了两行完整数据且没有任何列为空。这代表字段解析逻辑没有写错。第三个层次图中能直观看出皮卡丘和伊布在某一项或某几项属性上的差异并且这种差异和你对宝可梦的常识一致。如果程序运行失败不要急着重新跑。先按以下顺序检查看报错位置是请求阶段还是解析阶段。如果是requests.exceptions.ConnectionError说明网络不通检查能否正常访问外网。看报错信息里是否出现KeyError: xxx。如果是打开浏览器直接访问https://pokeapi.co/api/v2/pokemon/pikachu对比你代码里写的字段名和接口实际返回的字段名。如果图表不显示检查matplotlib的显示后端。使用 Jupyter Notebook 时可能需要加上%matplotlib inline使用纯脚本执行则不需要。一个容易被忽视的验证方法是打印raw.keys()先看顶层有哪些字段再逐层深入。这比记忆接口文档更可靠因为它反映的是当前真实返回结构。python -c import requests; datarequests.get(https://pokeapi.co/api/v2/pokemon/pikachu, timeout10).json(); print(data.keys())7. 常见问题与排查思路问题现象可能原因排查方式解决方案请求超时或连接失败网络环境无法访问外网或接口暂时不可用尝试curl https://pokeapi.co/api/v2/pokemon/pikachu检查网络在代码中加大 timeout 并增加重试机制报KeyError: base_stat字段名写错或接口返回结构与预期不一致打印data[stats]查看真实结构使用stat.get(base_stat)容错DataFrame 中某列为空某只宝可梦缺少对应字段打印raw.keys()核对使用默认值填充如data.get(stat, 0)图表中文乱码matplotlib 默认字体不支持中文检查终端回显绘图标签统一使用英文避免字体问题seaborn 找不到配色seaborn 版本过低或参数不兼容查看异常堆栈升级 seaborn或改用 matplotlib 原生 bar 绘图频繁请求后被限流请求频率过高观察响应状态码是否为 429加入time.sleep()控制请求间隔其中限流问题值得单独强调。PokeAPI 是免费公共接口免费不等于无节制。任何公共数据源都需要被尊重常见做法是循环请求时每两次之间至少间隔 0.5 到 1 秒批量场景必须做本地缓存。对个人学习项目来说请求十几只宝可梦完全没问题但如果写循环拉全量图鉴就要认真设计退避和暂停机制。8. 最佳实践与工程建议到这里项目已经能跑了。但如果只是“能跑”还不足以应对更真实的数据任务。下面这些工程习惯能在这个小项目基础上帮你更进一步。第一所有的网络请求都要封装成带重试的函数。网络是不可靠的超时和瞬时断连非常常见。可以用requests.adapters.HTTPAdapter配合urllib3.util.retry.Retry实现自动重试避免脚本挂在一次偶然的网络波动上。from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session requests.Session() retry Retry( total3, backoff_factor1, status_forcelist[500, 502, 503, 504], ) session.mount(https://, HTTPAdapter(max_retriesretry))第二原始响应一定要缓存。每调用一次 API就会消耗一次外部资源也会让你的脚本运行时间受制于网络。最简单的做法是把原始 JSON 保存到本地文件下一次运行时先检查缓存命中则跳过请求。import json from pathlib import Path def fetch_with_cache(name: str, cache_dir: Path Path(cache)): cache_dir.mkdir(exist_okTrue) cache_file cache_dir / f{name}.json if cache_file.exists(): return json.loads(cache_file.read_text(encodingutf-8)) data fetch_pokemon_data(name) cache_file.write_text(json.dumps(data, ensure_asciiFalse, indent2), encodingutf-8) return data第三字段解析要采用“防御性编程”。不要假设接口一定会返回预期的每个字段而是用.get()、默认值和类型检查把不确定性限制在可控范围内。对于分析型脚本坏数据可以直接丢弃或填充默认值对于生产型任务则要打日志并告警。第四把数据处理逻辑和可视化逻辑分开。数据获取、清洗、分析、可视化是四个不同职责。今天你画的是柱状图明天可能要改成雷达图如果清洗和分析逻辑没有分离改一次图就要动一遍数据代码维护成本会迅速上升。第五注意数据版权与使用边界。PokeAPI 公开接口让数据获取变得简单但宝可梦相关的图片、名称、世界观素材都有其版权归属。学习项目在本地运行没有问题如果要公开部署、商用、或者抓取图片资源必须仔细阅读数据源的使用条款确保自己的行为在合规范围内。这也是技术人应该有的底线意识。第六如果要在团队协作中使用这类脚本建议把 API 地址、缓存目录、请求间隔等配置抽成独立配置项而不是散落在代码中。可以用config.py、环境变量或者 YAML 文件来管理。这样别人接手时不需要通读全部代码也能知道哪些地方可以调整。9. 总结与后续学习方向回到标题皮卡丘与伊布初次接近。这个“接近”如果只停留在“两只宝可梦在同一张图上出现”那文章价值就太弱了。真正的接近是你第一次完整地操控一个公开数据接口把一只宝可梦从 JSON 对象变成 DataFrame 中一行结构化的记录又把它从数字变成图形。从此之后你对这两只宝可梦的了解不再依赖记忆而是可以随时通过代码复现、校验、扩展。这篇文章真正讲清楚的事情有三件。第一REST API 的 JSON 返回结构并不神秘按“先看顶层字段再逐层深入”的方式可以快速定位目标数据。第二pandas 的价值不在于它能把数据打印得好看而在于它把“数据对比”从手工模式切换成了结构化模式。第三可视化不是锦上添花它是发现数据差异的重要手段但前提是图表的输入数据必须经过严格校验。如果想继续深入这里有三个方向可以参考方向一把范围从 2 只扩展到整个皮卡丘家族或伊布家族观察不同宝可梦之间的种族值分布规律。方向二结合 PokeAPI 的evolution-chain端点和species端点做宝可梦进化链的数据关联分析。方向三进入图像领域把宝可梦图片下载下来用卷积神经网络或传统的 OpenCV 特征做皮卡丘与伊布的图像分类。无论选择哪个方向基础能力的底层都是同一套学会获取数据学会清洗数据学会用图表和代码验证你的判断。建议你把这份代码保存好之后做其他公开 API 练习时只需要替换接口地址和字段解析逻辑整个流程就能复用。