Claude API 教學:在 Ubuntu VPS 部署 Python 應用程式
本教學教您在 Ubuntu 24.04 VPS 上使用 Python 建立 Claude API 工具。內容涵蓋安全儲存 API key、使用 virtualenv、實作 streaming 串流傳輸、處理型別化異常,並包含控制 Token 成本與設定 systemd 的完整流程。
專案目標
在全新的 Ubuntu 24.04 VPS 上建立一個命令列工具。您可以將錯誤訊息或日誌片段透過 pipe 傳入,並獲得簡單易懂的診斷結果:journalctl -u nginx -n 50 | explain。此專案僅需約 60 行 Python 程式碼,但涵蓋了開發 Claude API 應用程式所需的所有核心要素:正確儲存的 API key、virtualenv、SDK 回傳結構、串流傳輸 (streaming)、型別化異常鏈 (typed exception chain),以及用於背景執行的 systemd unit。
我刻意選擇這個專案。大多數「第一個 API 應用程式」的教學會教你建立一個永遠不會再開啟的聊天機器人。而日誌解釋工具從部署第一天起就能在伺服器上發揮作用,並能讓你練習兩項初學者最常出錯的技能:正確解析回傳物件,以及控制成本。API 的費用依 token 計費,除非您自行設定,否則沒有上限,因此成本控制是此專案的核心設計需求,而非事後才考慮的事項——這與您未來進階到 在同一個 VPS 的 tmux 中執行 Claude Code 時所需的嚴謹度是一致的。
從 Console 取得 API key
API 存取權限需於 Anthropic Console (platform.claude.com) 管理。請先註冊,並於 Settings → API Keys 建立金鑰(文件連結直接指向 platform.claude.com/settings/keys)。金鑰僅會顯示一次,且以 sk-ant- 開頭,無法再次檢視。請立即複製金鑰,否則必須刪除並重新核發。
關於費用:截至 2026 年 7 月,API 並無持續性的免費層級。根據 Anthropic 的定價文件,新用戶會獲得少量免費額度供測試;確切金額依註冊時 Console 顯示為準。額度用罄後,必須為帳戶儲值才能成功發送請求。這與 claude.ai 訂閱是分開的:Pro 或 Max 方案不包含 API 額度,而 API key 也無法使用聊天應用程式。若您正在比較訂閱與 API 的差異,請參考:您實際需要的 Claude 方案。
建立金鑰時請限制其作用範圍於單一 project 或 server。當金鑰外洩時(長期來看,外洩機率必然存在),您需要能夠在不影響其他服務的情況下將其撤銷。
避免將金鑰寫入 .bashrc
在 ~/.bashrc 中使用 export ANTHROPIC_API_KEY=sk-ant-... 是一種反向操作,請勿這樣做。這會導致三個獨立問題:
- 所有程序都會繼承該變數。 在登入 shell 中導出的環境變數會傳播至所有啟動的程序——包含 Web app、會將環境變數寫入錯誤報告的 crash reporter,以及某人開啟的
phpinfo()頁面。金鑰的暴露範圍會擴大至「此使用者執行過的任何程序」。 - 輸入指令會將金鑰留在
~/.bash_history。 若手動執行 export 指令,金鑰會永久以明文形式存在於檔案中,並同步到 home directory 的所有備份中。 - systemd 需要時無法讀取。 Service 無法讀取您的
.bashrc,因此當您將指令碼轉換為 unit 時,該模式會失效——通常會在凌晨 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若要避免金鑰出現在編輯器的 swap 檔案中,請使用 printf 搭配 tee,而非使用編輯器。無論使用哪種方式,請務必透過 ls -l /etc/claude-explain.env 驗證檔案內容為 -rw------- 且擁有者為 root。互動式 shell 可透過 wrapper(如下所示)在每次執行時取得金鑰,而 systemd 則透過 EnvironmentFile= 取得——root 會在降權前讀取檔案,因此 service 使用者永遠不需要該檔案的讀取權限。金鑰不會出現在程式碼、git、ps 輸出或 shell history 中。
在 venv 中安裝 SDK
Ubuntu 24.04 內建 Python 3.12 並強制執行 PEP 668,因此直接對系統直譯器執行 pip install anthropic 會導致 error: externally-managed-environment 錯誤。此錯誤表示作業系統運作正常,請使用 virtualenv:
sudo apt update && sudo apt install -y python3-venv
sudo python3 -m venv /opt/explain/venv
sudo /opt/explain/venv/bin/pip install anthropic在伺服器上不需要執行 activation 程序:直接呼叫 /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)這 12 行程式碼中有兩點是理解此 API 核心邏輯的關鍵。首先,不帶參數的 anthropic.Anthropic() 會從環境變數讀取 key,請勿將其作為字串常數傳遞。其次,response.content 是內容區塊列表 (list of content blocks),而非字串。直接列印該物件會得到常見的新手錯誤輸出:
[TextBlock(citations=None, text='A systemd unit file is...', type='text')]這並非錯誤,而是該物件的 repr。回應可能包含多種區塊類型(文字、工具呼叫、思考過程),因此在存取 .text 之前,必須先進行迭代並檢查 block.type == "text"。從一開始就建立此迴圈邏輯,即可避免「輸出亂碼」這類問題。
請使用正確的 model ID claude-opus-4-8。現行世代的 ID 不含日期資訊;請勿依照舊有的習慣或部落格文章在 ID 後方加上日期後綴,否則會導致 404 錯誤(詳見下文)。
The actual tool: explain
Here is the full program — stdin in, streamed diagnosis out, errors handled:
#!/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())Save it as /opt/explain/explain.py, then add a wrapper that loads the key for interactive use:
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(The wrapper needs to run via sudo or the env file needs a group your admin user belongs to — pick one deliberately rather than loosening the file to 644.)
Why streaming. client.messages.stream prints tokens as they arrive instead of sitting silent for the full generation, and it sidesteps HTTP timeouts on long outputs — the SDK will actually refuse very large max_tokens values on non-streaming calls for exactly that reason. If you need the assembled object afterwards, call stream.get_final_message() inside the with block.
Why that exception order. The SDK raises typed exceptions, most-specific first: RateLimitError is a 429 and carries a retry-after header telling you how long to wait; APIStatusError covers other non-2xx responses (check e.status_code >= 500 for server-side trouble); APIConnectionError means the request never got a response at all. And before you build a retry loop: the SDK already retries 429s and 5xx errors itself, twice by default with exponential backoff (max_retries on the client). By the time your except runs, the retries are spent — so the right move in a CLI is to report and exit, not to sleep and hammer.
Cost control
此部分應獨立說明,因為除了您設定的限制外,API 並無內建每月上限,且任何錯誤都會在無聲中累積。
max_tokens 是您的單次呼叫支出上限。 Output tokens 的成本較高 —— 在 Opus 4.8 上,其價格是 input 的 5 倍 —— 而 max_tokens 是模型可產生的 token 數量硬上限。失控的 prompt 不會產生超過您允許的 output 成本。請根據任務調整大小:日誌診斷設定 1,500 綽綽有餘;分類任務則需要 100。如果回應因 stop_reason: "max_tokens" 而在句子中斷,表示您設定過小 —— 請有意識地調高,而非直接預設為極大值。
發送前先計算。 Input 也會產生費用,且日誌通常體積龐大。API 提供一個免費的計數 endpoint(其 rate limits 與訊息建立是分開計算的):
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 的 tokenizer,對於一般文本,它對 Claude token 的計算會少估約 15–20%,在程式碼上的誤差更大。
依任務選擇模型,而非依品牌忠誠度。 截至 2026 年 7 月,Opus 4.8 (claude-opus-4-8) 的費用為每百萬 input tokens $5 與每百萬 output $25;Haiku 4.5 (claude-haiku-4-5) 為 $1/$5 且具備 200K context;Sonnet 5 (claude-sonnet-5) 介於兩者之間,為 $3/$15,且在 2026 年 8 月 31 日前提供 $2/$10 的優惠價格。具體而言:一個包含 2,000-token 日誌摘錄與 500-token 回答的任務,在 Opus 上的成本約為 $0.0225,在 Haiku 上則為 $0.0045。在評估輸出品質時請先從 Opus 開始,接著嘗試將相同的 prompt 應用於 Haiku —— 對於高量且簡單的轉換任務,兩者結果通常無異,但 Haiku 的價格僅需五分之一。在將這些數據寫入預算之前,請至 pricing page 確認最新數據。
可延遲的任務請使用 Batches。 Batches API 以標準價格的 50% 非同步處理請求,大多數 batch 會在 1 小時內完成。夜間摘要、資料回填、大量分類 —— 任何不需要人類即時等待的任務都適用。
重複 context 請使用 Prompt caching。 如果每次呼叫都會重新發送相同的龐大 system prompt 或 runbook,請將其標記為可快取:
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在 5 分鐘的 TTL 下,快取寫入的成本約為 input 價格的 1.25x,快取讀取的成本約為 0.1x —— 因此在該時間窗口內的第二次呼叫,其成本已包含在第一次呼叫中。兩點注意事項:快取前綴必須超過模型規定的最小值 —— 例如 Opus 需要數千個 tokens —— 因此過短的 system prompt 將無法進行快取。此外,如果相同呼叫的 cache_read_input_tokens 始終為零,表示您的前綴在每次請求時都在變動(時間戳記通常是主因)。
請記住哪些屬於 input。 System prompts、tool definitions,以及在多輪對話中,您每輪重新發送的完整歷史紀錄,都會被計為 input tokens。若聊天迴圈從不修剪歷史紀錄,其成本會呈平方級增長。在構建任何對話功能之前,建議先理解完整的計費邏輯:Claude token 使用量與計費的實際計算方式。
使用 systemd 執行
使用 environment-file 的優點:每天早上透過 timer 彙整昨天的錯誤。
# /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 擁有且權限為 mode-600 的檔案。因此,程序可以取得變數,但該用戶無法讀取金鑰檔案。systemd-journal 群組則授予日誌存取權限。請使用手動 systemctl start 進行測試並讀取 journalctl -u log-digest.service —— 不要等到 06:15 才發現打錯字。當此模式的複雜度超過 shell pipeline 時,同樣的「將金鑰寫入 environment-file」方法,可以直接應用於同一台機器上的 Claude 驅動的 n8n 工作流。
Failure modes, with the strings you will see
401 on a working key. 錯誤訊息如下:
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= 是否指向正確路徑。其他原因包括:env 檔案中貼入引號(ANTHROPIC_API_KEY="sk-ant-..." — systemd 會移除引號,但若您的 shell wrapper 使用 . file 且引號方式不當,則會保留引號)、結尾空白,或是您上週已在 Console 中撤銷該金鑰。
404 from a model typo. 最常見的情況是在目前的 model 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。請從 models 文件的內容中複製,切勿憑記憶或參考舊版教學。
429 rate_limit_error. 錯誤類型字串為 rate_limit_error,且回應包含一個帶有等待秒數的 retry-after header。在您看到此異常前,SDK 已嘗試過兩次帶有 backoff 機制的重試,因此持續出現 429 代表您的持續速率確實超過了您的層級 — 請將工作批次處理或分散執行,不要縮短 retry loop 的間隔。
It prints the object, not the text. 輸出內容看起來像 [TextBlock(citations=None, text='...', type='text')]。您印出了 response.content,而不是透過迭代 blocks 並從 block.type == "text" 的內容中讀取 .text。上述每個 SDK 範例皆正確處理;請直接複製該迴圈。
error: externally-managed-environment. 您在 Ubuntu 24.04 的 system Python 上執行了 pip install。請使用 venv — 切勿在重要的伺服器上使用 --break-system-packages。
Truncated answers. response.stop_reason == "max_tokens" 代表模型在思考中途達到了輸出上限。這是正常設計;請手動調高上限。
當您的第一個應用程式運作後,building an AI agent with Claude 能將這些 API 呼叫轉換為使用 tools 的 agent。
FAQ
嘗試 Claude API 的成本是多少?
對於此類工具而言,成本極低。截至 2026 年 7 月,Opus 4.8 的費用為每百萬 input tokens $5,每百萬 output tokens $25。典型的日誌診斷(約數千 tokens 輸入,數百 tokens 輸出)成本約為 2 美分;若使用 Haiku 4.5 ($1/$5),成本則低於 0.5 美分。每月執行每日摘要的成本比一杯咖啡還低。風險不在於單次呼叫的價格,而在於無限制的迴圈與無限制的 max_tokens,因此本指南會明確設定這兩項參數。
Claude API 有免費方案嗎?
截至 2026 年 7 月,並無持續性的免費方案。根據 Anthropic 的定價文件,新用戶在註冊後會在 Console 中看到一筆一次性的試用額度,用於測試 API,之後需自行儲值。若您的目標是追求零邊際成本而非頂尖品質,替代方案是 使用 Ollama 自架 open-weight model,並以 RAM 代替 token 支付成本。
如何在伺服器上保護我的 API key?
切勿將其寫入程式碼、存入 git、從 .bashrc 匯出,或輸入至會保留紀錄的 shell 中。請將其存放在 root 擁有的檔案中,並設定 600 權限。請針對個別程序進行載入:互動式使用請使用 wrapper script,系統服務則使用 EnvironmentFile=。請為每個伺服器或專案配置獨立的 key,以便在金鑰外洩時能精準撤銷,而非直接切斷整個系統。若 key 曾出現在貼文網站或 git commit 中,請立即在 Console 撤銷該金鑰;刪除 commit 並不能消除外洩的風險。
我應該從哪款 Claude model 開始使用?
在評估輸出品質是否足以進行開發時,請先從 claude-opus-4-8 開始。您需要以最高品質來評估方案可行性,且在個人使用規模下,成本差異僅為幾美分。待 prompt 確定後,請將實際輸入內容在 claude-haiku-4-5 上重新執行;對於摘要、分類與日誌篩選,其表現通常與高階模型相當,但成本僅需五分之一。請根據實際量測結果決定是否切換至 Haiku 或 Sonnet,而非預設切換。