news 2026/10/29 18:23:36

Claude API Token成本计算与用量分析:Python脚本实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Claude API Token成本计算与用量分析:Python脚本实现

1. 从一条计费通知说起:为什么我要自己算 Token 账

上周三凌晨,我照例跑完当天的批量文本处理任务,顺手打开控制台看了一眼账单,发现同样的任务量,费用比前一天少了将近四成。第一反应是脚本出 bug 了,检查日志才发现是上游模型服务调整了计费策略。这件事让我意识到一个很现实的问题:把成本控制完全交给平台账单,等于把自己的钱包交给别人管。

这篇文章要聊的就是这件事——Claude API 的 Token 成本计算与用量分析。我会从 Token 的计费原理讲起,带你搞清楚输入 Token、输出 Token、缓存命中这几项到底怎么算钱,然后给出一套可以直接跑的 Python 脚本,帮你统计自己项目里每一次调用的真实开销。不管你是刚接触大模型 API 的新手,还是已经在生产环境跑了几十万次调用的老手,这套方法都能用得上。

适合谁看?三类人:一是刚开始用 Claude API 做个人项目的开发者,想知道钱花在哪;二是团队里负责成本核算的同学,需要一份可复现的用量报表;三是像我这样习惯自己掌控数据的人,不满足于平台给的那张汇总账单。

需要提前说明的是,模型服务的定价策略会不定期调整,本文涉及的具体单价仅作为计算方法的演示,实际计费请以官方最新公告为准。方法本身是通用的,换个模型、换个单价,脚本改一行配置就能继续用。

2. Token 计费的核心逻辑拆解

2.1 Token 到底是什么,为什么按它收费

很多人第一次接触 API 计费时会困惑:为什么不是按字数、按请求次数收费,而是按 Token?这里得先把这个概念说清楚。

Token 是模型处理文本的最小单位,它不是字符,也不是单词。英文里一个常见单词通常是 1 个 Token,长单词会被拆成 2 到 3 个;中文里一个汉字大约对应 1 到 2 个 Token,具体取决于分词器的实现。你可以把它理解成"模型眼里的文字颗粒度"——模型读进去的是 Token 序列,吐出来的也是 Token 序列,算力消耗自然就按 Token 数量来计量。

提示:不同模型家族的分词规则不一样,同样一段中文,在 A 模型里是 800 Token,在 B 模型里可能是 1100 Token。做成本对比时,一定要用目标模型自己的分词器去估算,不能拿字数直接换算。

为什么按 Token 而不是按字数?因为模型的注意力计算复杂度跟序列长度强相关,处理 1000 个 Token 和处理 2000 个 Token,算力开销不是线性翻倍那么简单。按 Token 计费,本质上是在按真实算力消耗收费,这对平台和用户都更公平。

2.2 输入、输出、缓存:三笔账要分开算

Claude API 的计费不是单一价格,而是分成了几个维度,这是很多人算错账的根源。我把它拆成三块:

计费项说明单价特点
输入 Token你发给模型的提示词、上下文、文档内容基准价,通常最低
输出 Token模型生成的回复内容通常是输入价的数倍
缓存写入 Token把长上下文写入缓存供后续复用略高于普通输入
缓存读取 Token命中缓存后读取的内容显著低于普通输入

这里最关键的一点是:输出 Token 的单价普遍比输入贵好几倍。这意味着如果你的任务输出很长,成本大头在输出侧;如果输入是超长文档,那输入侧和缓存策略就成了优化重点。

我见过不少人的账单里,输出费用占了七成以上,原因就是让模型生成了大量冗余内容。控制输出长度,往往比压缩输入更省钱。

2.3 缓存机制:省钱的关键杠杆

缓存(Prompt Caching)是这套计费体系里最值得研究的部分。原理不复杂:如果你有一段很长的固定上下文(比如系统提示词、知识库文档、代码库说明),每次请求都重复发送,那这部分 Token 每次都要按输入价计费,非常浪费。

缓存机制允许你把这段内容"存"起来,第一次写入时付一次缓存写入费,后续请求命中缓存时,这部分只按缓存读取价计费,而缓存读取价通常只有普通输入价的十分之一左右。

注意:缓存有有效期,通常是几分钟到一小时不等,超时后需要重新写入。所以缓存策略适合"短时间内高频重复使用同一段长上下文"的场景,比如连续多轮对话、批量处理同一份文档。如果你的请求间隔很长,缓存可能还没命中就过期了,反而多付了写入费。

算一笔账:假设你有 10000 Token 的固定上下文,普通输入价是每百万 Token 3 美元,缓存读取价是每百万 Token 0.3 美元。不做缓存,100 次请求要花 100 × 10000 ÷ 1000000 × 3 = 3 美元;做了缓存,写入一次 10000 Token 按 3.75 美元每百万算约 0.0375 美元,剩下 99 次读取花 99 × 10000 ÷ 1000000 × 0.3 ≈ 0.297 美元,合计约 0.33 美元。差了将近十倍。这个杠杆,不用白不用。

3. 用量分析脚本的设计思路

3.1 为什么要自己写统计脚本

平台控制台不是有账单吗,为什么还要自己写脚本?三个理由。

第一,账单是滞后的、聚合的。控制台通常按天或按小时汇总,你看到的是总数,不知道哪次调用、哪个功能、哪个用户消耗了多少。做成本优化时,你需要的是细粒度数据。

第二,账单不区分业务维度。一个项目里可能有摘要任务、翻译任务、问答任务,它们的 Token 消耗结构完全不同。只有自己在调用层埋点,才能按业务标签拆分成本。

第三,实时性。等账单出来再发现超支,往往已经晚了。自己统计可以做到每次调用后立即累计,设置阈值告警,把成本控制在事前。

所以我的做法是:在 API 调用封装层加一个轻量的统计模块,每次请求记录 Token 用量和费用,写入本地文件或数据库,随时可以出报表。

3.2 脚本整体架构

脚本不复杂,核心就三块:

  • 计费配置模块:把各模型的单价写成配置字典,方便随时更新。
  • 用量记录模块:每次调用后,从 API 返回的 usage 字段里提取 Token 数,按配置算出费用,追加写入记录。
  • 报表分析模块:读取记录文件,按时间、按任务类型、按模型聚合,输出汇总表和趋势。

我特意把计费配置独立出来,是因为单价会变。把单价硬编码在计算逻辑里,改价时就得翻代码;独立成配置,改一个数字就行。这是踩过坑之后的经验——之前有一次平台调价,我花半小时才找到散落在三个文件里的单价常量。

3.3 关键设计决策与取舍

有几个设计点值得说明,因为它们直接影响脚本好不好用。

用 JSON Lines 而不是 CSV 存记录。JSON Lines 每行一个独立 JSON 对象,追加写入不会破坏已有数据,字段可以灵活增减,读取时逐行解析也不占内存。CSV 虽然 Excel 能直接打开,但字段一变就要改表头,追加时还容易出格式问题。我选 JSON Lines,报表阶段再转成需要的格式。

费用计算保留高精度。Token 单价很小,比如每百万 Token 3 美元,单次调用可能只花 0.0001 美元。如果用浮点数累加,误差会累积。我用 Python 的decimal.Decimal做费用计算,避免精度问题。这个细节很多人忽略,但做成本核算时,几分钱的误差累积起来也够呛。

记录原始 usage 字段。除了算出的费用,我把 API 返回的原始 usage 对象也存下来。这样万一计费规则理解错了,可以回溯原始数据重新计算,不用重新跑一遍任务。

4. 可运行脚本的完整实现

4.1 环境准备与依赖

脚本只依赖 Python 标准库,不需要额外装包,这是刻意的选择——统计脚本应该轻量、好部署,不该因为依赖问题跑不起来。Python 3.8 以上都能用。

如果你还没装 Python,去官网下载安装包,安装时记得勾选"Add Python to PATH"。装完在命令行敲python --version能看到版本号就说明好了。这一步是很多新手卡住的地方,Windows 上如果提示"不是内部或外部命令",基本就是 PATH 没配好,重新装一遍勾上那个选项即可。

4.2 计费配置模块

先定义计费配置。单价用"每百万 Token 多少美元"表示,这是行业惯例,也方便阅读。

# pricing_config.py # 单价单位:美元 / 每百万 Token # 注意:以下数值仅作演示,实际请以官方最新公告为准 PRICING = { "claude-3-5-sonnet": { "input": 3.00, "output": 15.00, "cache_write": 3.75, "cache_read": 0.30, }, "claude-3-5-haiku": { "input": 0.80, "output": 4.00, "cache_write": 1.00, "cache_read": 0.08, }, "claude-3-opus": { "input": 15.00, "output": 75.00, "cache_write": 18.75, "cache_read": 1.50, }, } def get_price(model: str, token_type: str) -> float: """获取指定模型指定类型的单价,找不到时抛错而不是静默返回 0""" if model not in PRICING: raise KeyError(f"未知模型: {model},请在 PRICING 中补充配置") if token_type not in PRICING[model]: raise KeyError(f"模型 {model} 不支持计费类型: {token_type}") return PRICING[model][token_type]

这里有个细节:get_price遇到未知模型直接抛错,而不是返回 0。为什么?因为静默返回 0 会让费用统计悄悄失真,你以为是免费的,其实只是没配价格。宁可报错让你发现,也不要算出一份错误的报表。

4.3 用量记录与费用计算

接下来是核心的记录模块。它接收一次调用的 usage 数据,算出费用,追加写入文件。

# usage_tracker.py import json import os from datetime import datetime, timezone from decimal import Decimal from pricing_config import get_price RECORD_FILE = "usage_records.jsonl" def calculate_cost(model: str, usage: dict) -> Decimal: """ 根据 usage 计算单次调用费用。 usage 字段命名参考常见 API 返回结构,实际以你的接口为准。 """ cost = Decimal("0") # 普通输入 Token input_tokens = usage.get("input_tokens", 0) cost += Decimal(str(input_tokens)) / Decimal("1000000") * Decimal(str(get_price(model, "input"))) # 输出 Token output_tokens = usage.get("output_tokens", 0) cost += Decimal(str(output_tokens)) / Decimal("1000000") * Decimal(str(get_price(model, "output"))) # 缓存写入 cache_write = usage.get("cache_creation_input_tokens", 0) cost += Decimal(str(cache_write)) / Decimal("1000000") * Decimal(str(get_price(model, "cache_write"))) # 缓存读取 cache_read = usage.get("cache_read_input_tokens", 0) cost += Decimal(str(cache_read)) / Decimal("1000000") * Decimal(str(get_price(model, "cache_read"))) return cost def record_usage(model: str, usage: dict, task_tag: str = "default"): """记录一次调用,追加写入 JSON Lines 文件""" cost = calculate_cost(model, usage) record = { "timestamp": datetime.now(timezone.utc).isoformat(), "model": model, "task_tag": task_tag, "usage": usage, "cost_usd": str(cost), # Decimal 转字符串存储,避免精度丢失 } with open(RECORD_FILE, "a", encoding="utf-8") as f: f.write(json.dumps(record, ensure_ascii=False) + "\n") return cost

几个关键点解释一下。usage里的字段名我用了常见命名,比如input_tokens、output_tokens、cache_creation_input_tokens、cache_read_input_tokens。不同接口的字段名可能略有差异,你对接时按实际返回结构调整即可,逻辑是一样的。

费用用Decimal计算,最后转成字符串存储。为什么不直接存浮点数?因为 JSON 里的浮点数在解析时可能丢精度,存字符串最稳妥,报表阶段再转回Decimal累加。

task_tag参数是给业务打标签用的。你可以在调用时传入"summary"、"translate"、"qa"之类的标签,后面就能按标签拆分成本。

4.4 报表生成与聚合分析

记录有了,接下来出报表。这个模块读取 JSON Lines 文件,按不同维度聚合。

# report.py import json from collections import defaultdict from decimal import Decimal from datetime import datetime RECORD_FILE = "usage_records.jsonl" def load_records(): records = [] if not os.path.exists(RECORD_FILE): return records with open(RECORD_FILE, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line: records.append(json.loads(line)) return records def summarize_by(records, key_func): """通用聚合:按 key_func 分组,累加费用和各类 Token""" summary = defaultdict(lambda: { "calls": 0, "cost": Decimal("0"), "input_tokens": 0, "output_tokens": 0, "cache_read_tokens": 0, "cache_write_tokens": 0, }) for r in records: k = key_func(r) s = summary[k] s["calls"] += 1 s["cost"] += Decimal(r["cost_usd"]) u = r["usage"] s["input_tokens"] += u.get("input_tokens", 0) s["output_tokens"] += u.get("output_tokens", 0) s["cache_read_tokens"] += u.get("cache_read_input_tokens", 0) s["cache_write_tokens"] += u.get("cache_creation_input_tokens", 0) return summary def print_report(records): if not records: print("暂无记录") return total_cost = sum(Decimal(r["cost_usd"]) for r in records) print(f"总调用次数: {len(records)}") print(f"总费用: ${total_cost:.6f}") print() print("=== 按模型拆分 ===") by_model = summarize_by(records, lambda r: r["model"]) for model, s in sorted(by_model.items(), key=lambda x: -x[1]["cost"]): print(f"{model}: {s['calls']} 次, ${s['cost']:.6f}, " f"输入 {s['input_tokens']} / 输出 {s['output_tokens']} / " f"缓存读 {s['cache_read_tokens']} / 缓存写 {s['cache_write_tokens']}") print() print("=== 按任务标签拆分 ===") by_tag = summarize_by(records, lambda r: r.get("task_tag", "default")) for tag, s in sorted(by_tag.items(), key=lambda x: -x[1]["cost"]): print(f"{tag}: {s['calls']} 次, ${s['cost']:.6f}") print() print("=== 按日期拆分 ===") by_date = summarize_by(records, lambda r: r["timestamp"][:10]) for date, s in sorted(by_date.items()): print(f"{date}: {s['calls']} 次, ${s['cost']:.6f}")

这个报表输出三个维度:按模型、按任务标签、按日期。按模型看,能发现是不是某个贵模型用多了;按标签看,能定位哪个业务最烧钱;按日期看,能观察成本趋势,及时发现异常增长。

4.5 接入实际调用的示例

把上面几块拼起来,看一个完整调用示例。这里用伪代码展示接入方式,实际对接时替换成你的 API 客户端即可。

# main.py from usage_tracker import record_usage from report import load_records, print_report def call_model(client, model: str, messages: list, task_tag: str): """封装一次模型调用,自动记录用量""" response = client.messages.create( model=model, messages=messages, max_tokens=1024, ) # 从响应中提取 usage 并记录 usage = { "input_tokens": response.usage.input_tokens, "output_tokens": response.usage.output_tokens, "cache_creation_input_tokens": getattr(response.usage, "cache_creation_input_tokens", 0), "cache_read_input_tokens": getattr(response.usage, "cache_read_input_tokens", 0), } cost = record_usage(model, usage, task_tag=task_tag) print(f"本次调用费用: ${cost:.6f}") return response if __name__ == "__main__": # 跑完任务后出报表 records = load_records() print_report(records)

getattr那两行是为了兼容不支持缓存的接口——如果响应里没有缓存字段,就默认 0,不会报错。这种防御性写法在对接多个模型时特别有用,因为不同模型的 usage 结构不完全一致。

5. 成本优化的实操经验

5.1 从报表里能看出什么

脚本跑起来之后,报表会告诉你很多反直觉的事实。我拿自己的项目举例。

第一次出报表时,我发现"文档摘要"任务的成本占了总成本的 65%,远超预期。细看数据,问题出在输出侧:摘要任务的平均输出 Token 是输入的 1.5 倍。按理说摘要应该比原文短,为什么输出这么长?检查提示词才发现,我写的指令是"请总结以下内容",模型倾向于生成结构完整、面面俱到的摘要,结果比原文还啰嗦。

改成"用不超过 100 字概括核心结论"之后,输出 Token 直接降了六成,成本跟着降。这就是报表的价值——它把模糊的"感觉贵"变成了精确的"哪里贵、贵多少"。

5.2 三个立竿见影的省钱手段

根据我的实操,按见效速度排序,三个手段最值得先做。

第一,压缩输出。在提示词里明确限制输出长度,比如"用三句话回答"、"只输出 JSON,不要解释"。输出 Token 单价高,压输出立竿见影。我一般会在系统提示词里加一句"回答尽量简洁,避免重复用户的问题",这一句能省不少。

第二,用好缓存。如果你的任务有固定的长上下文,务必启用缓存。判断标准很简单:同一段长文本在短时间内被重复发送超过 2 次,缓存就划算。我有个知识库问答项目,把 8000 Token 的知识库说明做了缓存,单次成本从 0.024 美元降到 0.003 美元。

第三,分级用模型。不是所有任务都需要最强的模型。分类、抽取、格式转换这类任务,用便宜的小模型完全够用;只有需要复杂推理的任务才上大模型。我在脚本里加了任务标签,就是为了方便做这种分级——报表一看,哪个标签用了贵模型但效果没差别,就换掉。

5.3 容易被忽略的隐性成本

有几个成本项,账单上不一定单独列,但实实在在花钱。

重试成本。网络抖动导致的失败重试,每次重试都是一次完整计费。如果你的重试逻辑没做好幂等,可能同一份输入被计费好几次。我的做法是在记录里标记重试次数,报表里单独看这块占比,超过 5% 就要查网络或限流问题。

超长上下文。有些接口对超长输入有特殊计费,或者会触发截断。如果你的输入经常接近上下文上限,要留意是不是有冗余内容可以裁掉。我见过有人把整个日志文件塞进提示词,其实只需要相关的那几行。

测试流量。开发调试阶段的调用也是要花钱的。建议给测试流量单独打标签,和生产流量分开统计,这样能清楚知道"为了开发这个功能,我花了多少调试成本"。

6. 常见问题与排查技巧

6.1 费用对不上账单怎么办

这是最常被问到的问题。自己算的费用和平台账单有出入,先别慌,按这个顺序排查。

现象可能原因排查方法
自算偏低单价配置过期核对官方最新单价
自算偏低漏记了某类 Token检查 usage 字段是否完整提取
自算偏高重复记录检查是否有重试导致的重复写入
差异很小精度或四舍五入属正常,关注大额差异
差异很大计费口径不同确认是否含缓存、是否含失败请求

我的经验是,差异在 1% 以内属于正常,可能是精度或统计口径的细微差别。超过 5% 就要认真查,通常是单价配置过期或者漏记了某类 Token。有一次我漏了缓存写入 Token 的统计,导致自算比账单低了 8%,补上之后就对齐了。

6.2 脚本运行报错的典型场景

报错一:KeyError: 未知模型。说明你用的模型没在PRICING里配置。解决办法是补上配置,别图省事改成默认值,否则费用会算错。

报错二:usage字段取不到。不同接口返回结构不同,有的把 usage 放在response.usage,有的放在response.meta.usage。打印一下完整响应结构,找到正确路径。

报错三:JSON 解析失败。通常是记录文件被并发写入破坏了。如果多进程同时写,需要加文件锁,或者改成每个进程写独立文件,报表阶段再合并。

报错四:Decimal转换异常。检查传入的是不是字符串或数字,Decimal不接受浮点数直接构造(会有精度警告),统一先转字符串再转Decimal。

6.3 让统计更准的几个细节

最后分享几个让统计更准的实操细节,都是踩坑换来的。

第一,记录时间用 UTC。本地时间有时区、有夏令时,跨时区协作时容易乱。统一用 UTC 存储,展示时再转本地时间。

第二,费用字段存字符串。前面说过,JSON 浮点数会丢精度,存字符串最稳。

第三,定期归档。记录文件会越来越大,建议按月归档,老数据压缩存起来,报表只读当月数据,速度快很多。

第四,加个阈值告警。在记录模块里判断,如果单日累计费用超过设定值,打印醒目提示或发通知。这个功能救过我好几次——有次脚本死循环疯狂调用,告警及时触发,避免了更大的损失。

提示:统计脚本本身不产生费用,但它的价值在于让你对费用有掌控感。建议从项目第一天就接入,别等到账单吓人一跳才想起来做统计。

这套方法我在几个项目里都跑过,从个人小工具到日调用量几万次的服务,逻辑是一样的。核心就一句话:把成本数据握在自己手里,你才有优化的依据。脚本不长,但每一行都是为了解决一个具体问题而写的,你可以直接拿去改改用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 18:23:23

VCS验证流程三级架构与-debug_access调试光谱实战指南

简介:本资源为Synopsys官方发布的VCS用户指南(S-2021.09版),面向IC设计工程师、验证工程师及EDA工具使用者,系统解决VCS(Verification Continuum)仿真验证环境的部署、配置、运行与排错问题。手…

作者头像 李华
网站建设 2026/10/29 18:22:46

AI故障诊断Agent技术解析与工业应用实践

1. 项目概述:为什么需要AI故障诊断Agent?在工业生产和设备运维领域,故障诊断一直是个耗时耗力的工作。传统方式依赖工程师的经验积累和规则库匹配,面对复杂系统时响应速度慢、误判率高。我去年参与的一个风电项目就遇到过这种情况…

作者头像 李华
网站建设 2026/10/29 18:22:17

WinRAR SFX自解压打包技术详解与应用实践

1. WinRAR SFX自解压打包技术解析WinRAR的SFX(Self-Extracting Executable)自解压功能是Windows环境下最经典的文件打包方案之一。与常规压缩包不同,SFX模块能将压缩内容与解压程序结合生成独立的.exe可执行文件,这种技术自WinRAR…

作者头像 李华
网站建设 2026/10/29 18:21:47

数据库SQL语句优化实战:从原理到应用

1. 数据库语句优化概述数据库语句优化是每个后端开发者必须掌握的核心技能。记得我刚入行时,一个简单的查询语句就能让整个生产环境瘫痪,经过多年实战,我总结出一套行之有效的优化方法论。数据库查询就像城市交通,不合理的SQL就像…

作者头像 李华
网站建设 2026/10/29 18:21:32

WorkBuddy与QClaw工作效率工具深度对比

1. 项目概述WorkBuddy和QClaw都是当前市场上颇受关注的工作效率工具,它们针对不同的用户场景提供了各具特色的功能解决方案。作为一位长期关注生产力工具的技术博主,我最近对这两款工具进行了深度体验和横向对比,希望能帮助大家在选择时做出更…

作者头像 李华
网站建设 2026/10/29 18:20:50

impeccable:面向代码-文档-插件一致性的轻量级交付校验CLI

1. 项目概述:一个叫“impeccable”的 CLI 工具到底是什么?最近在多个前端工程群、CLI 工具讨论区和 DevOps 实操频道里,频繁看到这个词——impeccable。它不像 npm、npx 那样耳熟能详,也不像 create-react-app 或 vite 那样自带官…

作者头像 李华