Claude API 教程:在 VPS 上部署第一个应用
获取 Claude API 密钥,在 Ubuntu 24.04 上妥善保存,用 Python 写一个带流式输出、类型化错误处理和真实成本控制的日志解释工具。
您将构建什么
这是一个在全新 Ubuntu 24.04 VPS 上运行的命令行工具:您把一条错误信息或一段日志通过管道传给它,它返回一段通俗易懂的诊断:journalctl -u nginx -n 50 | explain。它大约六十行 Python 代码,却涵盖了一个真正的 Claude API 应用所需的一切 —— 妥善保存的密钥、一个虚拟环境、SDK 的响应结构、流式输出、类型化的异常链,以及一个 systemd 单元,让它无需您照看即可运行。
我特意挑选了这个项目。大多数“第一个 API 应用”教程让您构建一个再也不会打开的聊天机器人。而一个日志解释工具从第一天起就在服务器上派得上用场,而且它会逼着您把初学者真正容易做错的两件事做对:正确读取响应对象,以及控制花费。API 按 token 计费,除了您自己设定的上限之外没有任何天花板,因此成本控制在这里是一项设计输入,而不是事后补救 —— 当您进阶到在同一台 VPS 上用 tmux 运行 Claude Code时,同样的纪律依然重要。
从 Console 获取 API 密钥
API 访问在 platform.claude.com 上的 Anthropic Console 中管理 —— 先注册,然后在 Settings → API Keys 下创建一个密钥(文档直接链接到 platform.claude.com/settings/keys)。密钥只显示一次,以 sk-ant- 开头,之后无法再次取回 —— 请立即复制,否则就删除并重新签发。
关于费用:截至 2026 年 7 月,API 没有持续性的免费额度。Anthropic 的定价文档说明新用户会获得少量免费额度用于测试;具体金额以您注册时 Console 显示的为准,用完之后必须先为账户充值,请求才能成功。这与 claude.ai 订阅是两码事 —— Pro 或 Max 套餐并不包含 API 额度,而一个 API 密钥也不会给您聊天应用。如果您在订阅和 API 之间权衡,这个取舍本身就是另一个话题:您到底需要哪个 Claude 套餐。
创建密钥时把它限定到单个项目或服务器。当密钥泄露时 —— 时间足够长,总会有那么一次 —— 您会希望能撤销它而不影响您拥有的其他一切。
别把密钥放进 .bashrc
下意识的做法是在 ~/.bashrc 里写 export ANTHROPIC_API_KEY=sk-ant-...。别这么做。有三个各自独立的问题:
- 每个进程都会继承它。 在登录 shell 中导出的环境变量会传播到您启动的一切 —— Web 应用、那个会“热心地”把自身环境倒进错误报告的崩溃上报程序、某人忘了关掉的
phpinfo()页面。密钥的暴露面变成了“这个用户曾经运行过的一切”。 - 手动输入它会留在
~/.bash_history里。 只要手动执行过一次这条 export,您的密钥就永远留在一个明文文件里,并同步进您主目录的每一份备份。 - systemd 需要它的时候它却不在。 服务不会读取您的
.bashrc,所以当您把脚本提升为一个单元时,这套做法恰好会失效 —— 通常表现为凌晨 6 点一个莫名其妙的 401。
在服务器上正确的做法是使用一个权限为 600 的专用环境变量文件,只由需要它的进程加载:
sudo mkdir -p /opt/explain
sudo install -m 600 -o root -g root /dev/null /etc/claude-explain.env
printf 'ANTHROPIC_API_KEY=sk-ant-YOUR-KEY-HERE\n' | sudo tee /etc/claude-explain.env >/dev/null如果您想让密钥不落入编辑器的交换文件,就用 printf 配合 tee,而不是用编辑器;无论哪种方式,都用 ls -l /etc/claude-explain.env 验证它显示为 -rw------- 且属主为 root。交互式 shell 通过一个封装脚本(见下文)在每次调用时获取密钥,systemd 则通过 EnvironmentFile= 获取 —— root 在放弃权限之前先读取该文件,因此服务用户从不需要对它有读取权限。密钥不会出现在代码里、git 里、ps 输出里,也不会出现在 shell 历史里。
在 venv 中安装 SDK
Ubuntu 24.04 自带启用了 PEP 668 强制策略的 Python 3.12,所以对系统解释器直接执行 pip install anthropic 会以 error: externally-managed-environment 失败。这个错误是操作系统按设计正常工作 —— 请使用虚拟环境:
sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropic在服务器上不需要繁琐的激活步骤:直接调用 /opt/explain/venv/bin/python 始终使用该 venv 里的包。
第一次调用,以及正确读取响应
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1000,
messages=[{"role": "user", "content": "Explain what a systemd unit file is in three sentences."}],
)
for block in response.content:
if block.type == "text":
print(block.text)这十二行代码里有两处承载了 API 的大部分心智模型。第一,不带参数的 anthropic.Anthropic() 会从环境中读取密钥 —— 永远不要把密钥作为字符串字面量传进去。第二,response.content 是一个内容块列表,而不是字符串。直接打印它,您会得到初学者经典的输出:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]这不是 bug,而是该对象的 repr。响应可以包含多种块类型(文本、工具调用、思考),所以您要遍历它们,在访问 .text 之前检查 block.type == "text"。第一天就把这个循环接进去,整整一类“它打印出乱码”的困惑就永远不会发生。
请使用精确的模型 ID claude-opus-4-8。当前这一代的 ID 不带日期 —— 请克制住那种(来自肌肉记忆或旧博客文章的)想给它加上日期后缀的冲动;那会产生一个 404,下文会讲到。
真正的工具:explain
这是完整的程序 —— 从标准输入读入,流式输出诊断,并处理错误:
#!/usr/bin/env python3
"""explain: pipe an error or log excerpt in, get a diagnosis out."""
import sys
import anthropic
MODEL = "claude-opus-4-8"
def main() -> int:
text = sys.stdin.read().strip()
if not text:
print("usage: journalctl -u nginx -n 50 | explain", file=sys.stderr)
return 1
client = anthropic.Anthropic()
try:
with client.messages.stream(
model=MODEL,
max_tokens=1500,
system=(
"You are a senior Linux sysadmin. The user pipes you server "
"logs or error output. Name the most likely cause outright, "
"then give the commands to confirm and fix it. Be terse."
),
messages=[{"role": "user", "content": text}],
) as stream:
for chunk in stream.text_stream:
print(chunk, end="", flush=True)
print()
except anthropic.RateLimitError as e:
retry_after = e.response.headers.get("retry-after", "60")
print(f"rate limited; retry in {retry_after}s", file=sys.stderr)
return 2
except anthropic.APIStatusError as e:
print(f"API error {e.status_code}: {e.message}", file=sys.stderr)
return 2
except anthropic.APIConnectionError:
print("network error reaching the API", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())把它保存为 /opt/explain/explain.py,然后添加一个封装脚本,在交互使用时加载密钥:
sudo tee /usr/local/bin/explain >/dev/null <<'EOF'
#!/bin/sh
set -a; . /etc/claude-explain.env; set +a
exec /opt/explain/venv/bin/python /opt/explain/explain.py "$@"
EOF
sudo chmod 755 /usr/local/bin/explain(这个封装脚本需要通过 sudo 运行,或者环境变量文件需要归属于您的管理员用户所在的某个组 —— 请有意识地二选一,而不是把文件放宽到 644。)
为什么用流式。 client.messages.stream 会在 token 到达时就打印出来,而不是在整个生成过程中一直沉默,而且它能绕开长输出时的 HTTP 超时 —— SDK 实际上会因为这个原因拒绝在非流式调用上使用非常大的 max_tokens 值。如果之后您需要拼装好的完整对象,就在 with 块内调用 stream.get_final_message()。
为什么是那个异常顺序。 SDK 抛出类型化的异常,最具体的排在最前:RateLimitError 是 429,并携带一个 retry-after 头,告诉您要等多久;APIStatusError 涵盖其他非 2xx 响应(检查 e.status_code >= 500 判断服务端故障);APIConnectionError 表示请求根本没有得到任何响应。而且在您动手写重试循环之前:SDK 自身已经会重试 429 和 5xx 错误,默认重试两次并采用指数退避(客户端上的 max_retries)。等到您的 except 运行时,重试次数已经用尽 —— 所以在 CLI 里正确的做法是报告并退出,而不是睡眠后再去猛敲。
成本控制
这一节值得单独成节,因为除了您自己配置的之外,API 没有内置的每月上限,而这里的每一个错误都会悄悄累积。
max_tokens 是您单次调用的花费上限。 输出 token 是昂贵的方向 —— 在 Opus 4.8 上是输入价格的五倍 —— 而 max_tokens 是对模型可以产生多少 token 的硬性上限。一个失控的提示词造成的输出花费不可能超过您允许的量。按任务大小来设定它:1500 对一次日志诊断绰绰有余;一个分类任务只需要 100。如果响应在句子中途以 stop_reason: "max_tokens" 停下,说明您设得太紧了 —— 要有意识地调高它,而不是默认设成一个巨大的值。
发送前先计数。 输入也要花钱,而日志往往很大。API 有一个可免费使用的计数端点(它有自己的速率限制,与消息创建的限制是分开的):
count = client.messages.count_tokens(
model="claude-opus-4-8",
messages=[{"role": "user", "content": big_log_text}],
)
print(count.input_tokens)用它来防止不小心把一个 2 GB 的日志通过管道送进这个工具。不要用 tiktoken 来做这件事 —— 那是 OpenAI 的分词器,在典型文本上它会把 Claude 的 token 数量少算大约 15–20%,在代码上少算得更多。
按任务而不是按忠诚度来选模型。 截至 2026 年 7 月,Opus 4.8(claude-opus-4-8)为每百万输入 token 5 美元、每百万输出 token 25 美元;Haiku 4.5(claude-haiku-4-5)为 1/5 美元,带 200K 上下文;Sonnet 5(claude-sonnet-5)居中,为 3/15 美元,并有截至 2026 年 8 月 31 日的入门价 2/10 美元。具体来说:一段 2000 token 的日志摘录配一个 500 token 的回答,在 Opus 上大约花费 0.0225 美元,在 Haiku 上约 0.0045 美元。在您判断输出质量期间先用 Opus,然后把同样的提示词拿到 Haiku 上试 —— 对于高流量、简单的转换任务,它常常在五分之一的价格下与之难分伯仲。在把任何数字写死进预算之前,请在定价页面上核对当前的数值。
任何可以等待的任务都用批处理。 Batches API 以标准价格的 50% 异步处理请求,大多数批处理在一小时内完成。夜间摘要、回填、批量分类 —— 任何没有人在等待结果的任务都应放到这里。
对重复的上下文使用提示缓存。 如果每次调用都重复发送同一个庞大的系统提示词或运行手册,就把它标记为可缓存:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1000,
system=[{
"type": "text",
"text": RUNBOOK_TEXT, # the same 30K tokens on every call
"cache_control": {"type": "ephemeral"},
}],
messages=[{"role": "user", "content": question}],
)
print(response.usage.cache_read_input_tokens) # non-zero from the second call on缓存写入的费用约为输入价格的 1.25 倍,缓存读取约为 0.1 倍,TTL 为 5 分钟 —— 因此窗口期内的第二次调用就已经把第一次的开销赚回来了。有两个坑。被缓存的前缀必须达到某个按模型而定的最小值 —— 在 Opus 上是几千个 token —— 所以一个很短的系统提示词会悄无声息地根本不被缓存。而如果 cache_read_input_tokens 在多次完全相同的调用中始终为零,那说明您前缀里有东西每次请求都在变(时间戳是常见的罪魁祸首)。
记住什么算作输入。 系统提示词、工具定义,以及 —— 在多轮对话中 —— 您每一轮都重新发送的整段历史,全都按输入 token 计费。一个从不裁剪历史的聊天循环,其成本会以二次方增长。在您构建任何对话式应用之前,值得把这套完整的计费方式弄清楚:Claude 的 token 用量和计费到底是怎么算的。
在 systemd 下运行
对环境变量文件这套纪律的回报:一个每天早上汇总前一天错误的定时器。
# /etc/systemd/system/log-digest.service
[Unit]
Description=Daily error-log digest via the Claude API
[Service]
Type=oneshot
User=explain
Group=systemd-journal
EnvironmentFile=/etc/claude-explain.env
ExecStart=/bin/sh -c 'journalctl -p err --since yesterday | /opt/explain/venv/bin/python /opt/explain/explain.py >> /var/log/log-digest.txt'# /etc/systemd/system/log-digest.timer
[Unit]
Description=Run the log digest every morning
[Timer]
OnCalendar=06:15
Persistent=true
[Install]
WantedBy=timers.targetsudo useradd -r -s /usr/sbin/nologin explain
sudo touch /var/log/log-digest.txt && sudo chown explain /var/log/log-digest.txt
sudo systemctl daemon-reload
sudo systemctl enable --now log-digest.timer
sudo systemctl start log-digest.service # test it once, right now注意 EnvironmentFile= 给您带来了什么:systemd 在放弃权限、降为无特权的 explain 用户之前读取这个属主为 root、权限为 600 的文件,所以进程拿到了这个变量,而该用户却读不到密钥文件。systemd-journal 组授予了日志访问权限。用一次手动的 systemctl start 来测试,并查看 journalctl -u log-digest.service —— 不要等到早上 6:15 才发现有个拼写错误。当这套模式发展到超出一条 shell 管道所能承载时,同样的“密钥放在环境变量文件里”的做法可以直接沿用到同一台机器上的由 Claude 驱动的 n8n 工作流。
失败模式,以及您会看到的字符串
一个本来有效的密钥却报 401。 异常信息为:
anthropic.AuthenticationError: Error code: 401 - {'type': 'error', 'error': {'type': 'authentication_error', 'message': 'invalid x-api-key'}, 'request_id': 'req_011CSHoEeqs5C35K2UUqR7Fy'}如果密钥在您的 shell 中有效但服务却报 401,那是服务根本没收到它 —— 记住 systemd 不会读取 .bashrc;检查 EnvironmentFile= 指向的路径是否正确。其他原因:粘进环境变量文件里的引号(ANTHROPIC_API_KEY="sk-ant-..." —— systemd 会把引号剥掉,但如果您引用方式古怪,您 shell 封装脚本里的 . file 会把引号留在值里)、尾随空白,或者一个您上周已经在 Console 里撤销的密钥。
因模型拼写错误导致的 404。 这其中最常见的一种,就是给当前的模型 ID 加上日期后缀:
anthropic.NotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-opus-4-8-20260115'}, 'request_id': 'req_011CSJqymAvNw4bT3qmDdMbA'}当前这一代的 ID 就是照字面写的那样精确 —— claude-opus-4-8、claude-haiku-4-5、claude-sonnet-5。请从模型文档里复制它们,绝不要凭记忆或从旧教程里抄。
429 rate_limit_error。 错误类型字符串是 rate_limit_error,响应携带一个 retry-after 头,给出需要等待的秒数。在您看到这个异常之前,SDK 已经带退避重试了两次,所以持续的 429 意味着您的持续速率确实超出了您所在的档位 —— 把工作分批或分散开,而不是去收紧重试循环。
它打印出的是对象,而不是文本。 输出看起来像 [TextBlock(citations=None, text='...', type='text')]。您打印了 response.content,而没有遍历各个块、从 block.type == "text" 的那些块里读取 .text。上面每一个 SDK 示例都做对了;把那个循环照抄过去。
error: externally-managed-environment。 您对 Ubuntu 24.04 的系统 Python 执行了 pip install。请使用 venv —— 在一台您在乎的服务器上,绝不要用 --break-system-packages。
被截断的回答。 response.stop_reason == "max_tokens" 表示模型在思路中途撞上了您的输出上限。这是按设计正常工作;请有意识地调高上限。
当您的第一个应用跑通后,用 Claude 构建一个 AI 智能体会把同样的这些 API 调用变成一个会使用工具的智能体。
FAQ
试用 Claude API 要花多少钱?
对于这样一个工具来说,真的花不了多少。截至 2026 年 7 月,Opus 4.8 为每百万输入 token 5 美元、每百万输出 token 25 美元,所以一次典型的日志诊断 —— 输入几千 token,输出几百 token —— 大约两美分,在 Haiku 4.5(1/5 美元)上则不到半美分。一整个月每天做摘要的花费比一杯咖啡还少。风险不在于每次调用的价格,而在于无边界的循环和无边界的 max_tokens,这正是本指南把两者都显式设定的原因。
Claude API 有免费额度吗?
截至 2026 年 7 月,没有持续性的免费额度。Anthropic 的定价文档说明新用户会获得少量免费额度来测试 API —— 一次性的试用,具体金额在您注册时的 Console 中显示 —— 用完之后需要为账户充值。如果您的目标是让每次请求的边际成本为零,而不是追求前沿质量,那么另一条路是用 Ollama 自托管一个开源权重模型,用内存而不是 token 来付费。
我该如何在服务器上妥善保管我的 API 密钥?
绝不放进代码,绝不放进 git,绝不从 .bashrc 导出,绝不输入到会保留历史的 shell 里。把它放进一个属主为 root、权限为 600 的文件,按进程加载 —— 交互使用时用封装脚本,systemd 用 EnvironmentFile= —— 并且做到每台服务器或每个项目一个密钥,这样撤销一个泄露的密钥是外科手术,而不是截肢。如果密钥一旦碰到过粘贴网站或某次 git 提交,请立即在 Console 里撤销它;删除那次提交并不能让它“没泄露过”。
我该从哪个 Claude 模型开始?
在评估输出是否足够好、值得在其上构建期间,先从 claude-opus-4-8 开始 —— 您想在满质量下判断这个想法,而在业余量级下成本差异只有几美分。等提示词定型之后,把您真实的输入拿到 claude-haiku-4-5 上重跑一遍;对于摘要、分类和日志分诊,它常常在五分之一的价格下同样出色。凭测量而不是凭默认来决定是否转到 Haiku 或 Sonnet。