1. 挑完 public-apis 候选本地全绿,接进 Agent 第二天开始 429
在 public-apis 里按 Health、Geocoding、Finance 三个分类挑了五个候选,本地 curl 全绿,接进调研 Agent 的正式流程后第二天开始出现 429,隔天又冒出几个 403。模型总结这段我放在 TaoToken(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=quota_agent_open )上跑,Base URL 用 https://taotoken.net/api ,Key 一次配好;但配额这件事,不能交给模型拍脑袋,必须回到原始文档一条条核。
先把定位说清楚:public-apis 是一份社区维护的接口目录,不是统一 API 网关。它做的事情是把不同服务的文档入口按场景摆在一起,天气、地理编码、财经、新闻、交通、图片、文本分析、开放数据、测试数据、机器学习等五六十个分类,每个条目给一个名称、一句说明和一个指向文档或官网的链接。它的价值在于把"免费天气 API 该去哪找"这类搜索,收敛成"在哪个分类里挑候选"。截至 2026 年 9 月,仓库规模在 48 万 Stars 量级、5 万以上 Forks,属于典型的选型起点型项目。
但选型起点不是接入终点。真正决定一个接口能不能进正式项目的,是三张表:限流表、免费额度表、商用限制表。目录页上"Free"两个字,既不等于当月有配额,也不等于允许商用,更不等于允许把数据再分发给下游客户。我这次的目标很具体:给一个准备上正式项目的数据调研 Agent,把候选接口的配额核对做成可复现的产出物,包含三样东西——配额对照表、价格页摘录、风险备注。下面把整套流程拆开讲,包括脚本怎么写、模型总结怎么接、Claude Code 和 Codex 的配置怎么落。
2. 把"免费"拆成可核对字段:配额对照表怎么设计
第一版表格我踩了坑,只写了"服务名 / 是否免费 / 限流"三列,结果核到第三个服务就发现根本没法填:有的服务按 IP 限,有的按 API Key 限,有的按账号限;有的写 "1000 requests/month",有的写 "5 requests/second, 10000/day",单位不统一,直接对比就是错的。第二版把字段拆细,才变得可复核。
建议的字段结构:
| 字段 | 说明 | 取值示例 |
|---|---|---|
| service | 服务名 | 示例天气服务 |
| category | 目录分类 | Weather |
| auth | 鉴权方式 | No / apiKey / OAuth |
| https | 是否加密访问 | Yes / No / Unknown |
| cors | 前端跨域 | Yes / No / Unknown |
| free_tier | 免费层描述 | 1000 次/月,需注册 |
| rate_limit | 限流维度与数值 | 5 req/s,10000 req/day |
| limit_scope | 限流作用域 | IP / Key / 账号 |
| commercial | 商用许可 | 允许 / 需授权 / 禁止 / 未说明 |
| data_license | 数据授权与再分发 | 需署名 / 禁止再分发 / 未说明 |
| region | 地区可用性 | 全球 / 仅部分地区 |
| doc_url | 核对所用文档页 | 原始文档地址 |
| checked_at | 核对日期 | 2026-09-18 |
| verdict | 结论 | 可进生产 / 仅 Demo / 淘汰 |
其中limit_scope这一列最容易被忽略。同一个 "1000 次/月",按 Key 限意味着你可以多申请几个 Key 做隔离,按 IP 限则意味着部署在同一个出口 IP 上的所有服务共享这份额度,扩容时直接撞墙。上线前不把这一列填清楚,后面做容量规划就是拍脑袋。
commercial和data_license必须分开看。有的服务免费层允许商用调用,但数据本身禁止再分发;有的服务允许再分发,但要求显著署名。这两条对应的风险完全不同:前者影响你的产品能不能卖,后者影响你的页面要不要加版权说明。
还有一个容易漏的点:目录里的 Auth 一列写 No,只说明这次请求不用携带凭证,推不出没有额度、没有频率限制、没有使用条款。CORS 一列写 No 的服务,项目贡献说明里明确写了只能在服务端调用,如果你把它接进浏览器端页面,本地调试可能正常,一上线就全是跨域拦截。这三列只是初筛条件,不是结论。
3. 让调研 Agent 回归原始文档:本地抓取 + 关键词截取的脚本骨架
核对配额这一步,我的原则是:脚本负责取回原文证据,模型只负责归纳和填表。顺序反了,模型就会用记忆里的旧配额把表格填满,看起来完整,实际全是幻觉。
脚本在本地跑,只读公开文档页,不碰任何生产数据库,也不连内网服务。流程是:读候选清单 → 逐个抓取文档页 → 提取含配额/授权关键词的段落 → 落盘成 JSON → 交给模型总结。
候选清单用一个 yaml 维护:
# candidates.yaml - service: 示例天气服务 category: Weather doc_url: https://example-weather.invalid/docs/pricing notes: 目录标注 apiKey + HTTPS Yes + CORS Yes - service: 示例地理编码服务 category: Geocoding doc_url: https://example-geo.invalid/terms notes: 目录标注 No + HTTPS Yes + CORS No,需确认是否仅服务端抓取与截取脚本:
# quota_probe.py —— 本地运行,仅抓取公开文档页 import json import re from pathlib import Path import requests import yaml CANDIDATES = Path("candidates.yaml") OUT = Path("candidates_raw.json") # 覆盖限流、额度、商用、授权四类语义的关键词 KEYWORDS = [ "rate limit", "rate-limit", "ratelimit", "throttle", "quota", "free tier", "free plan", "fair use", "requests per", "calls per", "per month", "per day", "per hour", "commercial use", "commercial", "non-commercial", "terms of service", "terms of use", "acceptable use", "pricing", "attribution", "redistribut", "sublicense", ] HEADERS = {"User-Agent": "quota-probe/0.1 (local research script)"} WINDOW_BEFORE, WINDOW_AFTER = 180, 260 MAX_HITS_PER_PAGE = 40 def strip_html(html: str) -> str: html = re.sub(r"<script[\s\S]*?</script>", " ", html, flags=re.I) html = re.sub(r"<style[\s\S]*?</style>", " ", html, flags=re.I) text = re.sub(r"<[^>]+>", " ", html) return re.sub(r"\s+", " ", text).strip() def extract_hits(text: str): lower = text.lower() hits, seen = [], set() for kw in KEYWORDS: start = lower.find(kw) while start != -1: left = max(0, start - WINDOW_BEFORE) right = start + len(kw) + WINDOW_AFTER snippet = text[left:right].strip() fingerprint = snippet[:80] if fingerprint not in seen: seen.add(fingerprint) hits.append({"keyword": kw, "snippet": snippet}) if len(hits) >= MAX_HITS_PER_PAGE: return hits start = lower.find(kw, start + len(kw)) return hits def probe(entry: dict) -> dict: url = entry["doc_url"] record = { "service": entry["service"], "category": entry.get("category", ""), "doc_url": url, "notes": entry.get("notes", ""), "status": "ok", "hits": [], } try: resp = requests.get(url, headers=HEADERS, timeout=20) resp.raise_for_status() record["hits"] = extract_hits(strip_html(resp.text)) except requests.HTTPError as exc: record["status"] = f"http_error:{exc.response.status_code}" except requests.RequestException as exc: record["status"] = f"request_error:{type(exc).__name__}" return record def main(): entries = yaml.safe_load(CANDIDATES.read_text(encoding="utf-8")) results = [probe(e) for e in entries] OUT.write_text( json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8", ) for r in results: print(f'{r["service"]:<24} {r["status"]:<24} hits={len(r["hits"])}') if __name__ == "__main__": main()跑完得到candidates_raw.json,每条候选带若干原文片段。这里有几个实践细节值得注意:
第一,403 和 429 要单独标记,不要当成"服务已下线"。反爬策略、地区策略、访问频率都会产生这两个状态码,和链接失效是两回事。脚本里把它们留在status字段里,人工复核时再决定是否用浏览器补一次。
第二,关键词命中不等于条款。"commercial" 这个词可能出现在 "non-commercial use only",也可能出现在 "commercial use requires a license"。脚本不做判断,只把上下文窗口切出来,判断交给下一步和人工。
第三,时间戳一定要落盘。配额和价格是随时间漂移的,没有checked_at的对照表,三个月后就是一张废纸。可以在脚本里顺手加datetime.now(timezone.utc).isoformat()。
第四,单页抓取设上限。免费文档站经常没有速率友好设计,一个分类十几条候选连着抓,很容易自己把自己限流。串行 + 每次请求间隔 1–2 秒,比并发更省事。
4. 模型总结接 TaoToken:Key、Base URL 与 Claude Code 的 settings.json
证据抓回来了,接下来才是模型出场的环节:把candidates_raw.json里的原文片段,归纳成第 2 节那张对照表,并逐行标注证据出处。这一步对上下文长度和稳定性有要求,我是走 TaoToken 统一管的。Key 在官网控制台申请,地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=quota_agent_key ,申请完在 API Keys 页面创建,Base URL 统一填 https://taotoken.net/api 。
Claude Code 的配置走settings.json,两个环境变量:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY" } }如果想临时切一次、不想改文件,用环境变量更直接:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="YOUR_API_KEY"放好配置后,先跑一个最小验证,确认链路通:
curl -sS "$ANTHROPIC_BASE_URL/v1/messages" \ -H "x-api-key: $ANTHROPIC_AUTH_TOKEN" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "max_tokens": 64, "messages": [{"role": "user", "content": "reply with the single word: ok"}] }'这里有两个判断点。一是认证头要和工具约定对齐:Claude Code 读ANTHROPIC_AUTH_TOKEN,走x-api-key语义;有些封装库读ANTHROPIC_API_KEY,两者不要混着填,否则会出现"配置看着没错,就是 401"的情况。二是不要为了省事把 Key 写进会提交的文件。settings.json如果会进版本库,就改用环境变量注入,或者用本地未跟踪的.env文件。
请求丢给模型时,提示词要写死输出格式,否则每次拿回来的表格列名都不一样,后面的比对脚本没法用。我在用的模板大概是这样:
你是数据接口配额核对助手。下面给你的是若干服务的官方文档片段。 要求: 1. 只依据给出的片段作答,片段没提到的字段一律填 "未说明"。 2. 不要使用你记忆中的配额数字,不要推测。 3. 输出 Markdown 表格,列固定为: service | free_tier | rate_limit | limit_scope | commercial | data_license | region | evidence 4. evidence 列填写支撑该行结论的片段原句(不超过 40 字),没有就填 "无"。 5. 表格之后,单独列出"存疑项",说明哪些服务需要人工再查。 文档片段: {{snippets}}这里第 4 条是关键。强制模型给出证据原句,相当于给每一行结论加了一个可回查的锚点。核表的人不需要重跑全流程,只要对着doc_url和那一句片段验证即可。模型在片段缺失时被允许填"未说明",比让它硬编一个数字安全得多。
5. Codex 用 config.toml,CC Switch 用三件套切换供应商
上面是 Claude 系工具的接法。如果你同时用 Codex 跑同一批总结任务,配置方式完全不同,不要把ANTHROPIC_*那套变量搬到 Codex 上,两者不是一套协议。
Codex 走config.toml:
# ~/.codex/config.toml model = "gpt-5" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" # 在 https://taotoken.net/api 基础上按 OpenAI 兼容协议追加版本段 base_url = "https://taotoken.net/api/v1" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"对应的环境变量:
export TAOTOKEN_API_KEY="YOUR_API_KEY"注意env_key写的是变量名,不是 Key 本身。Key 落在 shell 环境里,配置文件里只留一个名字,这样config.toml可以放心进版本库。
如果你在多个供应商之间来回切,用 CC Switch 这类切换工具会比手改文件省事。它的核心就是三件套:
- 名称:给这套配置起个可识别的名字,比如
taotoken-prod - Base URL:
https://taotoken.net/api - API Key:
YOUR_API_KEY
三件套填完保存成一条配置,之后切换只是选条目,不会漏改字段。切换工具的价值在这个场景下特别明显:核对配额时用一套配置跑大批量总结,做代码任务时切到另一套,避免两个工作负载互相挤占上下文预算。官网控制台在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=quota_agent_switch ,Key 管理和用量查看都在那里。
有个细节值得提一句:切换工具改的是"当前生效配置",但已经启动的进程不会自动重载。改完配置记得重启对应的 CLI,否则会出现"切了但没生效"的错觉。
6. 价格页摘录与风险备注:三件套产出物长什么样
回到最初的目标。整套流程跑完,应该得到三份可以直接提交给团队评审的产出物。
第一份:配额对照表。就是第 2 节那张表,填满每一列,evidence一列附上模型给出的原文片段,checked_at写上核对日期。评审时重点看三列:rate_limit、commercial、data_license。
第二份:价格页摘录。从每个候选的定价页或条款页,原样摘出三段话:免费层的界定、超量后的计费方式、商用条款的原文。摘录不做改写,保留原句,附上页面地址和抓取时间。它的作用是在三个月后有人问"当时看的到底是哪一版条款"时,能拿出证据。
第三份:风险备注。这是最容易被跳过、但评审时最有价值的一份。格式不固定,按服务逐条列:
## 风险备注 ### 服务 A(示例天气服务) - [配额] 免费层标注"1000 次/月",未说明是否按 IP 计。多实例部署共享出口 IP 时额度可能提前耗尽。 - [商用] 条款中 "commercial use requires prior written consent" 属于需授权类别,上线前需走商务确认。 - [CORS] 目录标注 CORS No,浏览器直连会失败,只能经由服务端转发,需评估额外延迟。 - [稳定性] 文档页最近更新时间为两年前,服务状态未知,建议先小流量灰度。 ### 服务 B(示例地理编码服务) - [授权] 允许调用但禁止再分发结果数据,若产品会把坐标回吐给下游客户,需替换方案。 - [地区] 免费层仅覆盖部分区域,跨区调用可能返回空结果。 - [限流] 5 req/s 的窗口较短,批量任务需要加本地令牌桶,否则偶发 429。风险备注的原则是:只写能从证据里推出来的东西,不写猜测。"文档最近更新在两年前"是事实,"服务可能快下线了"是推测,两者要分开写。前者可以直接进结论,后者只能标成待观察项。
实际评的时候,几个高频风险类型基本逃不出这五类:配额按 IP 共享、免费层不含商用、数据禁止再分发、CORS 限制导致必须服务端代理、条款长期未更新。把这五类做成检查项的固定清单,每核一个服务就过一遍,比自由发挥更不容易漏。
7. 上线前的检查清单与常见报错对照
最后把踩过的坑归拢成一份清单,配合报错对照表用。
上线前检查清单:
- 每个候选是否都有
checked_at,且日期在两周以内? limit_scope是否明确到 IP / Key / 账号?- 商用条款是否读过原文,而不是只看目录里的 "Free" 标注?
- 数据再分发是否单独确认过?很多服务把"调用"和"回吐"分成两条条款。
- CORS 为 No 或 Unknown 的服务,是否已经改为服务端代理调用?
- 是否设置了本地限流器,把上游限制的一半作为自己的预算上限?
- Key 是否只存在于环境变量或未跟踪文件里?
- 是否有降级方案?主要数据源不可用时,退到哪个备选?
常见报错对照:
| 现象 | 大概率原因 | 处理方向 |
|---|---|---|
| 本地 curl 200,线上 429 | 限流按 IP,多实例共享出口 | 查limit_scope,加本地令牌桶或申请独立 Key |
| 浏览器端全部被拦,服务端正常 | CORS 为 No | 改服务端代理,或换掉该候选 |
| 突然大面积 403 | 条款变更、地区策略、UA 被拦 | 用浏览器复核条款页,确认是否仍允许当前用法 |
| 免费层第一周就耗尽 | 额度按账号而非 Key,或存在并发放大 | 统计实际 QPS,重新估算额度消耗 |
| 模型总结表格列名每次都不同 | 提示词没有固定列 | 用第 4 节的模板锁死输出格式 |
| 401 但配置看起来没问题 | 认证变量名和工具约定不一致 | Claude 系看ANTHROPIC_*,Codex 看env_key,别混用 |
这套流程的核心思路其实不复杂:目录负责帮你缩小范围,原文文档负责给出结论,模型负责把结论整理成表。三步分工明确,哪一步出问题都好定位。反过来,如果让模型直接替你"记住"配额数字,表面上省了抓取这一步,实际上省掉的是整个核对过程的可靠性。
如果你的 Agent 也停在"本地能跑、上量就崩"的阶段,可以先从填满那张配额对照表开始。Key 和 Base URL 的配置参考这条路径——先在模型对话里验证一次调用:https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=quota_agent_chat ,确认链路通;需要长期跑批量总结就看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=quota_agent_plan ,把工作负载和计费对上;然后在控制台创建正式 Key:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=quota_agent_apikeys ;Claude Code 侧的完整参数说明在 https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=quota_agent_ccdoc 。Base URL 统一用 https://taotoken.net/api ,Key 位置填YOUR_API_KEY,配完先跑一次最小请求再上批量任务。