1 引言
接入第三方 API 时,最麻烦的往往不是写调用代码,而是搞清楚"有哪些接口、路径是什么、参数怎么传"。传统做法是维护一份静态文档,但文档和实际接口之间容易脱节——接口改了路径、换了参数名,文档没同步,调用就废了。
docs.csqaq.com 提供了 39 个 API 端点用于查询游戏商品的价格、库存、排行等数据。但这些端点的文档散落在 39 个独立页面中,没有任何统一入口。如果要接入这些接口,传统方式是手动翻阅每个文档页面、把路径和参数复制出来——工作量大、易出错、接口更新后也无法及时感知。
CSQAQ Market Lookup 这个 Skill 解决的核心问题是:不要手动维护接口列表,让工具从文档站点的 sitemap 实时同步。所有接口信息来自 docs.csqaq.com 的实际页面,文档和代码始终保持一致。
2 核心问题
docs.csqaq.com 的文档结构:
/sitemap.xml— 列出所有文档页面 URL/api-{id}.md— 每个接口对应一个 Markdown 页面,页面内嵌 OpenAPI YAML 定义
手动维护接口列表的问题在于:接口一旦更新,列表就过时了。而且每次要翻文档页面、复制路径参数,工作量大。
理想工作流:从 sitemap 发现所有文档 → 从每个页面提取 OpenAPI 定义 → 合并成统一目录 → 直接用目录信息调用接口。
3 整体架构
工具分为三层:
docs.csqaq.com
↓
┌──────────────────────────────────────────┐
│ Sync 层:sitemap → 文档页面 → YAML │
│ 输入:sitemap.xml │
│ 输出:endpoints.json / openapi-merged.json│
└──────────────────────────────────────────┘
↓
┌──────────────────────────────────────────┐
│ 索引层:endpoints.json(接口目录) │
│ 字段:doc_id, path, method, │
│ operationId, summary, tags │
└──────────────────────────────────────────┘
↓
┌──────────────────────────────────────────┐
│ 调用层:CLI(sync / list / call) │
│ 用途:查询接口、发起真实 HTTP 调用 │
└──────────────────────────────────────────┘
4 Sync 层:从 sitemap 到接口目录
4.1 从 sitemap 发现文档 ID
sitemap.xml 中的 URL 形如 https://docs.csqaq.com/api-327138094,数字部分即为文档 ID。用正则从 sitemap 中提取所有这类 URL:
API_URL_RE = re.compile(r"^https://docs\.csqaq\.com/api-(\d+)$")
def parse_api_doc_ids(sitemap_xml: str) -> list[str]:
root = ET.fromstring(sitemap_xml)
for node in root.findall(".//sm:url/sm:loc", ns):
loc = node.text.strip()
if match := API_URL_RE.match(loc):
doc_ids.append(match.group(1))
return doc_ids
拿到所有 doc_id 后,逐个抓取对应页面 /api-{doc_id}.md。
4.2 从 Markdown 中提取 OpenAPI YAML
文档页面内嵌一段 YAML 格式的 OpenAPI 定义,用代码块包裹。提取逻辑:
FENCED_YAML_RE = re.compile(r"```yaml\s*(.*?)\s*```", re.DOTALL)
def extract_openapi_yaml(markdown_text: str) -> dict:
match = FENCED_YAML_RE.search(markdown_text)
if not match:
raise ValueError("No fenced YAML block found.")
return yaml.safe_load(match.group(1))
每个文档页面解析后得到一个独立的 OpenAPI spec dict。
4.3 合并多个 OpenAPI spec
39 个文档得到 39 个独立 spec,需要合并成一份统一文档。合并逻辑处理了三个关键问题:
路径合并:遍历每个 spec 的 paths 字段,按 path + method 组合写入合并结果。如果同一 path + method 在多个 spec 中出现,记录为 collision,强制提示人工介入。
for doc_id, spec in specs:
for path, path_item in spec["paths"].items():
for method, operation in path_item.items():
key = (path, method.lower())
if key in merged_paths:
collisions.append({"path": path, "method": method, "doc_id": doc_id})
continue # 跳过重复定义,不做 silent覆盖
merged_paths[key] = operation
Schema 合并:components/schemas 字段去重合并,同名字段保留第一个出现的定义。
Security Schemes 合并:同上。
最终输出三份文件:
| 文件 | 内容 |
|---|---|
endpoints.json |
扁平化接口目录(39 条记录) |
openapi-merged.json |
合并后完整 OpenAPI 文档 |
sync-report.json |
同步报告(成功/失败数、碰撞检测结果) |
4.4 Collision 检测
合并多个 spec 时,同一 path + method 出现多次,不直接覆盖,而是记录到 collision 列表:
collisions.append({
"doc_id": doc_id,
"path": path,
"method": method_lower,
"reason": "duplicate path+method"
})
这防止了 silent override——两个文档定义同一接口时,应该提示人工判断,而不是默默取后者覆盖前者。
实际同步结果:39 个文档,0 个 collision。文档结构本身是干净的。
5 索引层:endpoints.json 的结构
endpoints.json 是查询基础,每条记录:
| 字段 | 含义 |
|---|---|
doc_id |
文档页面 ID(URL 中的数字) |
doc_url |
文档主页 URL |
doc_md_url |
文档原始 Markdown URL |
path |
接口路径,如 /api/v1/current_data |
method |
HTTP 方法(GET/POST/PUT 等) |
operationId |
OpenAPI operationId(文档内唯一标识) |
summary |
接口描述 |
tags |
接口标签分组 |
deprecated |
是否已废弃 |
requires_api_token |
是否需要 Token 认证 |
全部来自文档页面的 OpenAPI YAML,无人工添加内容。
6 调用层:三种端点解析路径
6.1 通过 operationId 定位
python scripts/csqaq_api.py call \
--operation-id ______________api_v1_current_data_get
operationId 全局唯一时直接命中。
6.2 通过 path + method 定位
python scripts/csqaq_api.py call \
--path /api/v1/current_data \
--method GET
只知道接口路径时使用。
6.3 通过 docId + path + method 定位
python scripts/csqaq_api.py call \
--doc-id 327138094 \
--path /api/v1/current_data \
--method GET
多个文档中存在同名 path + method 时,用 --doc-id 精确指定。
三种方式最终都解析成统一的 (path, method) 对,再组装成真实 HTTP 请求。
7 Token 管理与请求构建
API Token 通过环境变量 CSQAQ_API_TOKEN 注入,CLI 自动注入到 HTTP Header:
token = args.api_token or os.getenv(args.token_env)
if token and not has_apitoken_header:
headers["ApiToken"] = token
不需要调用方手动处理 Header。也可用 --api-token 参数直接传入,覆盖环境变量。
请求构建支持 query 参数、header 参数,以及三种 body 格式:
# query 参数
--query type=init
# 三种 body 格式(Content-Type 自动选择)
--json-body '{"key": "value"}' # application/json
--body-file /path/to/file # multipart/form-data
--raw-body "raw text content" # text/plain
8 关键技术决策
8.1 不硬编码接口
传统 API 集成方案的问题:接口列表是项目开始时写的,之后没人管。接口改路径、下线、重命名,代码里的 endpoint URL 慢慢就变成隐藏的 bug。
本工具的做法:接口目录本身是工具的输入,而不是输出。每次运行 sync 都重新从 docs.csqaq.com 拉取最新文档,接口变了同步就会反映最新状态。
8.2 collision 检测优先
合并多个 spec 时,同一 path + method 出现多次,不 silent override,而是记录 collision 强制提示人工介入。
8.3 无额外依赖
同步脚本只依赖 Python 标准库(urllib、xml.etree、json、re、argparse)和 PyYAML。不需要 requests、httpx 等第三方包,有 Python 环境就能跑。
8.4 解析容错
Markdown 中 YAML 块格式不一定完全规范。解析用 yaml.safe_load,单个文档失败只记录到报告中,不影响其他文档的同步。
9 同步结果
| 指标 | 数值 |
|---|---|
| sitemap 发现文档数 | 39 |
| 成功同步数 | 39 |
| 同步失败数 | 0 |
| 提取端点数 | 39 |
| 路径+方法碰撞 | 0 |
39 个文档全部成功同步,无失败、无碰撞。接口覆盖实时数据查询、商品信息、价格数据、排行榜等多个业务模块。
10 适用场景与局限性
适用:需要接入 CSQAQ API 且希望保持接口文档与代码同步的场景;接口数量多、人工维护成本高的团队。
局限:依赖文档站点的 sitemap 结构,文档 URL 格式变化时需同步更新解析正则;工具本身不处理 API 响应解析和结果缓存。
11 总结
这个工具的核心不是 HTTP 调用逻辑,而是保证调用方拿到的接口信息永远和文档站点保持一致。
三个关键设计:
- sitemap 先行:从 sitemap 发现文档,不依赖人工 URL 列表
- OpenAPI YAML 解析:从 Markdown 中提取结构化定义,避免手工录入
- collision 保护:合并时强制检测重复定义,防止 silent data corruption
Skill 目录:https://skillhub.cn/skills/csqaq-market-lookup
评论 (0)
发表评论
请先登录后发表评论