阅读视图
国内开发者如何接入 Claude API?中转站方案实战指南(Python/Node.js 完整示例)
国内开发者如何接入 Claude API?中转站方案实战指南(Python/Node.js 完整示例)
Claude 系列模型(Sonnet 4.6、Opus 4.6、Haiku)在代码生成和复杂推理上表现出色,但国内开发者直连 Anthropic API 面临网络不稳定、支付困难等问题。本文介绍通过 xingjiabiapi.org 中转站接入 Claude API 的完整方案,附可运行代码。
为什么国内需要中转站?
直连 Anthropic API 的三大痛点:
| 问题 | 具体表现 | 中转站解决方案 |
|---|---|---|
| 网络不稳定 | 高延迟、频繁超时、SSE 流断裂 | xingjiabiapi.org 部署在海外节点,国内直连低延迟 |
| 支付困难 | 需要海外信用卡,充值门槛高 | 人民币直接充值,支持支付宝/微信 |
| 接口不兼容 | Anthropic 原生接口与 OpenAI 格式不同 | 统一 OpenAI 兼容接口,零改动迁移 |
xingjiabiapi.org 是一个提供 Claude/GPT/Gemini API 中转服务的平台,支持 OpenAI 兼容接口,一个 base_url 接入所有主流大模型。
快速开始:Python 接入 Claude API
安装依赖
pip install openai
基础调用
from openai import OpenAI
client = OpenAI(
api_key="your-api-key", # 在 xingjiabiapi.org 获取
base_url="https://xingjiabiapi.org/v1"
)
# 普通对话
response = client.chat.completions.create(
model="claude-sonnet-4-20250514",
messages=[
{"role": "system", "content": "你是一个专业的技术助手"},
{"role": "user", "content": "用 Python 实现一个简单的 LRU Cache"}
],
max_tokens=2048
)
print(response.choices[0].message.content)
流式输出(SSE)
from openai import OpenAI
client = OpenAI(
api_key="your-api-key",
base_url="https://xingjiabiapi.org/v1"
)
stream = client.chat.completions.create(
model="claude-sonnet-4-20250514",
messages=[
{"role": "user", "content": "解释 Python 的 GIL 机制,以及如何绕过它"}
],
stream=True,
max_tokens=2048
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
多轮对话
from openai import OpenAI
client = OpenAI(
api_key="your-api-key",
base_url="https://xingjiabiapi.org/v1"
)
messages = [
{"role": "system", "content": "你是一个 Python 专家,擅长代码审查"}
]
# 第一轮
messages.append({"role": "user", "content": "帮我审查这段代码:\ndef fib(n): return fib(n-1)+fib(n-2) if n>1 else n"})
resp1 = client.chat.completions.create(model="claude-sonnet-4-20250514", messages=messages, max_tokens=1024)
messages.append({"role": "assistant", "content": resp1.choices[0].message.content})
print("第一轮:", resp1.choices[0].message.content[:200])
# 第二轮
messages.append({"role": "user", "content": "按你的建议优化,给出完整代码"})
resp2 = client.chat.completions.create(model="claude-sonnet-4-20250514", messages=messages, max_tokens=1024)
print("第二轮:", resp2.choices[0].message.content)
Node.js 接入 Claude API
安装依赖
npm install openai
基础调用
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: 'your-api-key', // 在 xingjiabiapi.org 获取
baseURL: 'https://xingjiabiapi.org/v1'
});
async function chat() {
const response = await client.chat.completions.create({
model: 'claude-sonnet-4-20250514',
messages: [
{ role: 'system', content: '你是一个 Node.js 专家' },
{ role: 'user', content: '用 Express 写一个带限流的 API 网关' }
],
max_tokens: 2048
});
console.log(response.choices[0].message.content);
}
chat();
流式输出
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: 'your-api-key',
baseURL: 'https://xingjiabiapi.org/v1'
});
async function streamChat() {
const stream = await client.chat.completions.create({
model: 'claude-sonnet-4-20250514',
messages: [
{ role: 'user', content: '用 TypeScript 实现一个事件驱动的任务队列' }
],
stream: true,
max_tokens: 2048
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) process.stdout.write(content);
}
}
streamChat();
在 LangChain 中使用
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="claude-sonnet-4-20250514",
api_key="your-api-key",
base_url="https://xingjiabiapi.org/v1",
temperature=0.7
)
# 直接调用
response = llm.invoke("用 LangChain 搭建一个 RAG 系统需要哪些组件?")
print(response.content)
模型选择与成本对比
根据 xingjiabiapi.org 的价格表,不同分组适合不同场景:
| 模型 | 分组 | 价格(元/美元) | 适合场景 |
|---|---|---|---|
| Claude Sonnet 4.6 | Claude Max 号池 | 1.8 | 日常开发、代码生成 |
| Claude Opus 4.6 | Claude Max 号池 | 1.8 | 复杂推理、长文写作 |
| Claude Sonnet 4.6 | aws-claude 高并发 | 0.45 倍率 | 企业批量处理 |
| Claude Haiku | 官方直连 | 按官方定价 | 轻量任务、分类标注 |
xingjiabiapi.org 的 Claude Max 号池价格为 1.8元/美元,比官方低 75%,适合个人开发者和中小团队。
错误处理最佳实践
from openai import OpenAI, APIError, RateLimitError
import time
client = OpenAI(api_key="your-api-key", base_url="https://xingjiabiapi.org/v1")
def robust_call(messages, max_retries=3):
for attempt in range(max_retries):
try:
return client.chat.completions.create(
model="claude-sonnet-4-20250514",
messages=messages,
max_tokens=2048
)
except RateLimitError:
wait = 2 ** attempt
print(f"限流,等待 {wait}s 后重试...")
time.sleep(wait)
except APIError as e:
print(f"API 错误: {e}")
if attempt == max_retries - 1:
raise
return None
总结
xingjiabiapi.org 提供 Claude/GPT/Gemini 等主流大模型 API 中转服务,支持 OpenAI 兼容接口,Claude Max 号池 1.8元/刀,Gemini cli 版 0.45元/刀。纯透传代理,不存储对话内容。
- 官网:xingjiabiapi.org
- 微信:malimalihongbebe
- 商务邮箱:xingjiabiapi@163.com
为什么我的 Auth Token 藏在了 Network 面板的 Doc 里?
最近在调试一个第三方登录功能时,我遇到了一个奇怪的现象:明明看到页面跳转了,用户也成功登录了,但我在 Network 面板里怎么都找不到那个关键的 auth code。习惯性地勾选了 Fetch/XHR 过滤器,翻遍了所有请求,就是没有🤔。
直到我取消过滤,切换到 Doc 类型,才在 URL 参数里看到了 ?code=abc123&state=xyz。
这次经历让我开始思考:Network 面板里的 Doc 到底是什么?为什么有些认证信息会出现在这里而不是 Fetch/XHR 里?这背后有什么机制?
问题的起源
一次真实的调试经历
场景是这样的:我在接入 GitHub OAuth 登录,流程看起来很顺利——用户点击"使用 GitHub 登录",跳转到 GitHub 授权页面,授权后跳回我的应用。但问题来了,我需要在回调中获取 GitHub 返回的 authorization code,然后用这个 code 去换取 access token。
按照惯例,我打开 Chrome DevTools,勾选 Network 面板的 Fetch/XHR 过滤器,准备查看 API 请求。结果——什么都没有。没有看到任何包含 code 参数的请求。
困惑了好一会儿,我才想起取消过滤,查看所有类型的请求。这时我注意到有个 Type 为 document 的请求,点开一看,Request URL 是:
https://myapp.com/callback?code=gho_xxxxxxxxxxxx&state=random_string
原来 code 一直在这里!只是它不是通过 Fetch/XHR 发送的,而是作为页面跳转(重定向)的一部分。
这背后的几个疑问
这次经历让我产生了几个问题:
- Network 面板里的 Doc 是什么?和 Fetch/XHR 有什么区别?
- 为什么 OAuth 的认证信息会出现在 Doc 请求里?
- 什么时候我应该去查看 Doc 类型的请求?
- Network 面板里其他的过滤类型都代表什么?
接下来,让我们一个个搞清楚。
认识 Network 面板中的请求分类
什么是 Doc 请求?
Doc(Document)请求,指的是 HTML 文档请求,也就是浏览器加载的页面本身。
这个定义听起来有点抽象,我们用代码来看什么情况下会产生 Doc 请求:
// 环境: 浏览器
// 场景: 各种触发 Doc 请求的方式
// 1. 直接在地址栏输入 URL
// https://example.com
// → Type: document
// 2. 点击链接跳转
const link = document.createElement('a');
link.href = '/dashboard';
link.click();
// → Type:document
// 3. JavaScript 页面跳转
window.location.href = '/dashboard';
// → Type:document
// 4. 表单提交(非 AJAX)
const form = document.createElement('form');
form.action = '/login';
form.method = 'POST';
form.submit();
// → Type:document
// 5. 服务端 HTTP 重定向
// Server Response:
// HTTP/1.1 302 Found
// Location:/callback?code=xxx
// → 浏览器自动发起新的 document 请求
相对的,下面这些操作不会产生 Doc 请求:
// 环境: 浏览器
// 场景: 这些是 Fetch/XHR 请求,不是 Doc
// 使用 fetch API
fetch('/api/user')
.then(res => res.json())
// → Type: fetch
// 使用 XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.send();
// → Type: xhr
// 使用 axios 等库
axios.get('/api/posts');
// → Type: xhr
Doc 请求的关键特征:
- ✅ 会触发页面导航(浏览器地址栏的 URL 会改变)
- ✅ 浏览器会解析返回的 HTML 并渲染页面
- ✅ 可能伴随着 Cookie 的设置和自动携带
- ✅ 会刷新整个页面(除非是 iframe)
Network 面板的过滤类型全解
为了更清楚地理解 Doc 在整个 Network 面板中的位置,我整理了一个对比表:
| 类型 | 含义 | 何时查看 | 典型例子 |
|---|---|---|---|
| All | 所有请求 | 排查复杂问题,需要看完整链路 | - |
| Doc | HTML 文档 | 页面跳转、重定向、认证回调 | 登录重定向、OAuth 回调 |
| Fetch/XHR | AJAX 请求 | API 调用、异步数据加载 | fetch('/api/user') |
| JS | JavaScript 文件 | 脚本加载问题、404 错误 | <script src="app.js"> |
| CSS | 样式表 | 样式未生效、加载失败 | <link href="style.css"> |
| Img | 图片 | 图片显示异常、加载慢 | <img src="photo.jpg"> |
| Media | 音视频 | 媒体播放问题 | <video src="movie.mp4"> |
| Font | 字体文件 | 字体显示异常、图标不显示 |
@font-face 引用的字体 |
| WS | WebSocket | 实时通信连接问题 | new WebSocket(url) |
一个简单的记忆方法:
- Doc = 页面本身(会导致页面刷新或跳转)
- Fetch/XHR = 页面背后的数据请求(页面不刷新)
- 其他类型 = 页面加载需要的各种资源
什么时候需要查看 Doc?
根据我的经验,以下场景必须查看 Doc 类型的请求:
1. 调试页面跳转流程
用户登录 → 重定向到首页 → 重定向到之前访问的页面
每一次重定向都是一个 Doc 请求,需要追踪完整链路。
2. 排查认证问题
- OAuth/SAML 等第三方登录的回调
- Cookie 是否正确设置(查看 Response Headers 的 Set-Cookie)
- URL 参数中的临时 token 或 code
3. 追踪 HTTP 重定向链路
- 服务端返回 301/302/303/307/308 状态码
- 需要查看 Location 头部,了解重定向目标
- 排查重定向循环问题
4. 分析页面加载性能
- 首次加载时间(TTFB、DOM 解析时间)
- 服务端渲染(SSR)的响应时间
调试技巧:
在 Chrome DevTools 的 Network 面板中:
-
✅ 勾选 "Preserve log"(保留日志)
- 页面跳转后不会清空请求记录
- 可以看到完整的重定向链路
-
✅ 取消过滤或选择 "All"
- 看到所有类型的请求
- 不会漏掉关键信息
-
✅ 关注 Status 列的 3xx 状态码
- 302 Found, 301 Moved Permanently 等
- 这些都是重定向请求
为什么需要 Doc 分类?
浏览器的两种数据获取方式
理解 Doc 和 Fetch/XHR 的区别,关键在于理解浏览器获取数据的两种不同方式。
方式 1:页面导航(Doc 请求)
这是浏览器的原生机制,从 Web 诞生之初就存在:
<!-- 环境: 浏览器 -->
<!-- 场景: 传统的页面导航 -->
<!-- 点击链接 -->
<a href="/products">查看产品</a>
<!-- 表单提交 -->
<form action="/login" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">登录</button>
</form>
特点:
- ✅ 浏览器自动处理 Cookie、缓存、重定向
- ✅ 不需要写 JavaScript 代码
- ✅ 天然支持跨域(不受 CORS 限制)
- ❌ 会刷新整个页面,用户体验较差
方式 2:异步请求(Fetch/XHR 请求)
这是 AJAX 时代引入的技术,由 JavaScript 控制:
// 环境: 浏览器
// 场景: 现代单页应用的数据获取
// 使用 fetch API
async function login(username, password) {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
return data.token;
}
// 后续请求手动携带 token
async function getUserInfo(token) {
const response = await fetch('/api/user', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.json();
}
特点:
- ✅ 无需刷新页面,用户体验好
- ✅ 完全由 JavaScript 控制,灵活性高
- ❌ 需要手动处理认证信息(token)
- ❌ 受 CORS 限制,跨域需要服务端配合
为什么要区分这两种方式?
因为它们的调试方法、安全模型和适用场景都不同:
| 特性 | Doc(页面导航) | Fetch/XHR(异步请求) |
|---|---|---|
| 触发方式 | 链接点击、表单提交、重定向 | JavaScript 代码 |
| 页面刷新 | 是 | 否 |
| Cookie 携带 | 自动 | 需配置 credentials
|
| 跨域限制 | 无(但有其他安全机制) | 严格的 CORS 检查 |
| 调试位置 | Network → Doc | Network → Fetch/XHR |
| 适用场景 | 传统 Web、SSO、OAuth | 单页应用、REST API |
真实案例:OAuth 认证为什么用 Doc?
现在让我们回到文章开头的问题:为什么 OAuth 的 auth code 会出现在 Doc 请求里?
让我用一个完整的 OAuth 流程来说明:
sequenceDiagram
participant User as 用户浏览器
participant App as 你的应用
participant GitHub as GitHub
User->>App: 1. 点击"Login with GitHub"
Note over User,App: Fetch/XHR: GET /api/auth/github
App->>User: 2. 返回授权 URL
User->>GitHub: 3. 重定向到 GitHub
Note over User,GitHub: Doc 请求: 页面跳转
GitHub->>User: 4. 返回登录页面 HTML
User->>GitHub: 5. 用户输入账号密码
GitHub->>User: 6. 302 重定向回你的应用
Note over User,App: Doc 请求: HTTP 重定向
User->>App: 7. GET /callback?code=xxx&state=yyy
Note over User,App: ⭐ code 在这个 Doc 请求的 URL 里
App->>GitHub: 8. 用 code 换 token(服务端)
Note over App,GitHub: 这个请求不在浏览器里
GitHub->>App: 9. 返回 access_token
关键时刻分解:
步骤 3:跳转到 GitHub(Doc 请求)
// 环境: 浏览器
// 场景: 用户点击登录按钮后
// 前端构造 GitHub 授权 URL
const authUrl = 'https://github.com/login/oauth/authorize?' +
'client_id=your_client_id&' +
'redirect_uri=https://yourapp.com/callback&' +
'state=random_string';
// 页面跳转(产生 Doc 请求)
window.location.href = authUrl;
// Network 面板会看到:
// Type: document
// URL: https://github.com/login/oauth/authorize?...
// Status: 200
步骤 6-7:GitHub 重定向回你的应用(Doc 请求)
# GitHub 服务器的响应
HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code=gho_xxxx&state=random_string
# 浏览器自动发起新的请求
GET /callback?code=gho_xxxx&state=random_string HTTP/1.1
Host: yourapp.com
# Network 面板会看到:
# Type: document
# URL: https://yourapp.com/callback?code=gho_xxxx&state=random_string
# Status: 200
这就是你在 Doc 里找到 auth code 的原因!
为什么 OAuth 必须用重定向(Doc)?
可能你会想:为什么不用 Fetch/XHR 来实现 OAuth?这样就不用刷新页面了。
让我解释一下为什么这样做不通:
原因 1:安全性——用户密码不能经过第三方应用
问题情境: 用户(你)想让你的应用(比如 Notion)访问你的 Google Drive
❌ 错误做法:
Notion 让你在 Notion 页面输入 Google 密码
→ Notion 拿到了你的 Google 密码
→ 风险:Notion 可以随意访问你的 Google 账户
✅ 正确做法(OAuth):
Notion 把你重定向到 Google 登录页面
→ 你直接在 Google 页面输入密码
→ Notion 永远拿不到你的密码
→ Google 只给 Notion 一个有限权限的 token
重定向保证了用户直接在资源提供方(Google)的页面输入密码,密码不会经过第三方应用。
原因 2:跨域限制——CORS 会阻止 AJAX 请求
// 环境: 浏览器
// 场景: 如果尝试用 AJAX 请求 GitHub 授权页面
// ❌ 这样做不行
fetch('https://github.com/login/oauth/authorize?...')
.then(res => res.text())
.then(html => {
// 想法:拿到 GitHub 登录页面的 HTML,显示在我的页面里
});
// 会遇到的问题:
// 1. CORS 错误:GitHub 不允许你的域名跨域请求
// 2. 即使能拿到 HTML,用户在你的页面输入密码也不安全
// 3. 无法获取 GitHub 的 Cookie,登录状态无法维护
原因 3:浏览器的自动行为——重定向无需编写代码
# HTTP 重定向是浏览器的标准功能
# 服务器只需要返回一个响应头:
HTTP/1.1 302 Found
Location: https://yourapp.com/callback?code=xxx
# 浏览器会自动:
# 1. 提取 Location 头的 URL
# 2. 发起新的请求(Doc 请求)
# 3. 更新地址栏 URL
# 4. 渲染新页面
# 不需要写任何 JavaScript!
Cookie 认证为什么也在 Doc 里?
除了 OAuth,传统的 Cookie 认证也主要依赖 Doc 请求。让我们看一个例子:
// 环境: 浏览器
// 场景: 传统的表单登录
// HTML 表单
/*
<form action="/login" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">登录</button>
</form>
*/
// 用户提交表单时发生的事情:
// 1. 浏览器发送 Doc 请求
// POST /login HTTP/1.1
// Content-Type: application/x-www-form-urlencoded
//
// username=alice&password=secret123
// 2. 服务器验证成功,返回重定向 + Set-Cookie
// HTTP/1.1 302 Found
// Set-Cookie: session_id=abc123xyz; HttpOnly; Secure; SameSite=Strict
// Location: /dashboard
// 3. 浏览器自动:
// - 保存 Cookie
// - 发起新的 Doc 请求到 /dashboard
// - 自动携带 Cookie
// GET /dashboard HTTP/1.1
// Cookie: session_id=abc123xyz
为什么是 Doc 请求?
- Set-Cookie 在响应头中 → 只有 Doc 请求会自动处理 Set-Cookie
- Cookie 自动携带 → Doc 请求会自动带上同域的 Cookie
- 表单提交 → 传统 HTML form 产生的就是 Doc 请求
对比现代方式(Token 认证) :
// 环境: 浏览器
// 场景: 现代单页应用的 Token 认证
// 1. 用户登录(Fetch/XHR 请求)
async function login(username, password) {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
// { access_token: "eyJhbG...", refresh_token: "..." }
// 手动存储 token
localStorage.setItem('access_token', data.access_token);
}
// 2. 后续请求手动携带 token(Fetch/XHR 请求)
async function getUserInfo() {
const token = localStorage.getItem('access_token');
const response = await fetch('/api/user', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.json();
}
两种方式对比:
| 特性 | Cookie + Doc | Token + Fetch/XHR |
|---|---|---|
| 请求类型 | Doc(页面跳转) | Fetch/XHR(异步) |
| 认证信息位置 | Cookie(Response Headers) | JSON 响应体 |
| 携带方式 | 浏览器自动 | 手动添加到 Headers |
| 用户体验 | 页面刷新 | 无刷新,流畅 |
| 调试位置 | Doc 请求 | Fetch/XHR 请求 |
| 典型应用 | 传统 Web 应用、企业内部系统 | 单页应用、移动 App API |
实战调试技巧
案例:找不到 OAuth 的 auth code
让我用实际的调试步骤演示一遍:
问题场景:
- 接入 GitHub OAuth 登录
- 用户点击登录,跳转到 GitHub,授权后跳回应用
- 需要获取 URL 中的
code参数
错误的调试方法:
- 打开 DevTools → Network 面板
- 勾选 Fetch/XHR 过滤器
- 刷新页面,点击登录
- 找不到包含 code 参数的请求 ❌
正确的调试步骤:
✅ Step 1: 取消所有过滤,选择 "All" → 看到所有类型的请求
✅ Step 2: 勾选 "Preserve log"(保留日志) → 防止页面跳转后记录被清空
✅ Step 3: 点击登录,完成授权流程
✅ Step 4: 在 Network 面板中,找 Type 为 "document" 的请求 → 按时间顺序,找最后几个 Doc 请求
✅ Step 5: 点击 Doc 请求,查看详情 → Request URL: yourapp.com/callback?co… → code 就在这里!
✅ Step 6: 查看 Headers 标签
→ Request Headers 可以看到 Cookie、Referer
→ Response Headers 可以看到 Set-Cookie
在 Console 中提取 code:
// 环境: 浏览器 Console
// 场景: 在回调页面提取 URL 参数
// 方法 1: 使用 URLSearchParams
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
console.log('Auth code:', code);
console.log('State:', state);
// 方法 2: 手动解析(不推荐,但可以理解原理)
const queryString = window.location.search; // "?code=xxx&state=yyy"
const pairs = queryString.substring(1).split('&');
const result = {};
pairs.forEach(pair => {
const [key, value] = pair.split('=');
result[key] = decodeURIComponent(value);
});
console.log(result);
// { code: "gho_xxxx", state: "yyyy" }
调试清单
当你需要排查认证或重定向问题时,参考这个清单:
查看 Doc 请求时,重点关注:
| 位置 | 关键信息 | 用途 |
|---|---|---|
| Request URL | URL 参数 ?code=xxx&state=yyy
|
OAuth code、查询参数 |
| Status | 3xx 状态码(301/302/303) | 识别重定向链路 |
| Request Headers | Cookie 字段 | 检查认证信息是否携带 |
| Response Headers | Set-Cookie 字段 | 检查 Cookie 是否正确设置 |
| Response Headers | Location 字段 | 查看重定向目标地址 |
常见问题排查:
-
❓ 为什么看不到某些请求?
- → 检查是否勾选了 "Preserve log"
- → 取消类型过滤,选择 "All"
-
❓ 为什么 Cookie 没有携带?
- → 检查 Cookie 的 Domain 是否匹配
- → 检查 Cookie 的 Path 是否正确
- → 检查 SameSite 属性(Strict/Lax/None)
-
❓ 为什么一直重定向循环?
- → 启用 "Preserve log",查看完整的重定向链
- → 找出循环的起点和终点
- → 检查服务端的重定向逻辑
-
❓ 为什么 OAuth code 参数没有?
- → 检查 state 参数是否匹配(CSRF 防护)
- → 查看 GitHub 的错误提示(可能在 URL 参数里)
- → 确认 redirect_uri 配置是否正确
Chrome DevTools 实用技巧
1. 复制请求为 cURL
# 在 Network 面板中:
# 1. 右键请求
# 2. Copy → Copy as cURL
# 得到类似这样的命令:
curl 'https://yourapp.com/callback?code=gho_xxxx&state=yyyy' \
-H 'Accept: text/html' \
-H 'Cookie: session_id=abc123' \
-H 'Referer: https://github.com/login'
# 可以在终端中重放这个请求
2. 保存网络日志(HAR 文件)
右键 Network 面板 → Save all as HAR with content
用途:
- 保存完整的请求/响应记录
- 可以导入到其他工具分析
- 分享给同事协助调试
⚠️ 注意:HAR 文件包含敏感信息(Cookie、Token),不要随意分享
3. 过滤和搜索
在 Network 面板的 Filter 输入框中:
# 按域名过滤
domain:github.com
# 按状态码过滤
status-code:302
# 按请求方法过滤
method:POST
# 按资源大小过滤
larger-than:1M
# 组合使用
domain:api.example.com status-code:200
小结
通过这次对 Network 面板 Doc 类型的探索,我理清了几个关键点:
核心要点
1. Doc 是什么?
- HTML 文档请求,即浏览器加载的页面本身
- 所有触发页面导航的操作(链接点击、表单提交、重定向)都会产生 Doc 请求
- 会刷新页面,会自动处理 Cookie 和重定向
2. 什么时候需要看 Doc?
- 调试页面跳转和重定向流程
- 排查认证问题(OAuth 回调、Cookie 设置)
- 追踪 URL 参数中的临时信息(code、state、token)
- 分析完整的请求链路(配合 Preserve log)
3. 为什么需要 Doc 分类?
- 浏览器有两种数据获取方式:页面导航(Doc)和异步请求(Fetch/XHR)
- OAuth/SSO 等认证流程必须用重定向(安全性 + 跨域限制)
- Cookie 认证依赖浏览器的自动行为(自动携带、自动处理 Set-Cookie)
- 不同的机制需要不同的调试方法
4. Network 分类速查
- Doc → 页面跳转、重定向、认证回调
- Fetch/XHR → API 调用、异步数据加载
- JS/CSS/Img → 页面资源加载
- WS → WebSocket 实时通信
一个类比帮助记忆
把 Network 面板想象成一个物流追踪系统:
- Doc = 你本人去取件(需要走到快递点,拿到包裹后回家)
- Fetch/XHR = 快递送上门(你在家里,东西直接送到)
- JS/CSS/Img = 包裹里的物品(笔记本、衣服、配件等)
当你需要亲自去某个地方完成某件事(比如银行签字、OAuth 授权),就是 Doc 请求。当你只需要获取数据(API 调用),就是 Fetch/XHR 请求。
一个调试口诀
看不到数据?先别慌
取消过滤查 All 类型
重定向认证看 Doc
API 数据看 XHR
保留日志 Preserve log
完整链路不会漏
参考资料
- MDN - HTTP redirections - HTTP 重定向机制详解
- Chrome DevTools Network Reference - Chrome 官方文档
- OAuth 2.0 Simplified - OAuth 授权码模式
- HTTP cookies - Cookie 工作原理
- Same-origin policy - 同源策略与 CORS
Python Switch Case Statement (match-case)
Unlike many other programming languages, Python does not have a traditional switch-case statement. Before Python 3.10, developers used if-elif-else chains or dictionary lookups to achieve similar functionality.
Python 3.10 introduced the match-case statement (also called structural pattern matching), which provides a cleaner way to handle multiple conditions.
This article explains how to implement switch-case behavior in Python using all three approaches with practical examples.
Using if-elif-else
The if-elif-else chain is the most straightforward way to handle multiple conditions. It works in all Python versions and is best suited for a small number of conditions.
Here is an example:
def get_day_type(day):
if day == "Saturday" or day == "Sunday":
return "Weekend"
elif day == "Monday":
return "Start of the work week"
elif day == "Friday":
return "End of the work week"
else:
return "Midweek"
print(get_day_type("Saturday"))
print(get_day_type("Monday"))
print(get_day_type("Wednesday"))Weekend
Start of the work week
Midweek
The function checks each condition in order. When a match is found, it returns the result and stops. If none of the conditions match, the else block runs.
This approach is easy to read and debug, but it gets verbose when you have many conditions.
Using Dictionary Lookup
A dictionary
can map values to results or functions, acting as a lookup table. This approach is more concise than if-elif-else when you have many simple mappings.
In the following example, we are using a dictionary to map HTTP status codes to their descriptions:
def http_status(code):
statuses = {
200: "OK",
301: "Moved Permanently",
404: "Not Found",
500: "Internal Server Error",
}
return statuses.get(code, "Unknown Status")
print(http_status(200))
print(http_status(404))
print(http_status(999))OK
Not Found
Unknown Status
The get() method returns the value for the given key. If the key is not found, it returns the default value ("Unknown Status").
You can also map keys to functions. In the code below, we are creating a simple calculator using a dictionary of functions:
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
operations = {
"+": add,
"-": subtract,
"*": multiply,
}
func = operations.get("+")
print(func(10, 5))15
Dictionary lookups are fast and scale well, but they cannot handle complex conditions like ranges or pattern matching.
Using match-case (Python 3.10+)
The match-case statement was introduced in Python 3.10
. It compares a value against a series of patterns and executes the matching block.
The match-case statement takes the following form:
match expression:
case pattern1:
statements
case pattern2:
statements
case _:
default statementsThe match keyword is followed by the expression to evaluate. Each case defines a pattern to match against. The case _ is the wildcard (default) case that matches any value not matched by previous patterns, similar to default in other languages.
Here is a basic example:
def http_error(status):
match status:
case 400:
return "Bad Request"
case 401 | 403:
return "Not Allowed"
case 404:
return "Not Found"
case _:
return "Unknown Error"
print(http_error(403))
print(http_error(404))
print(http_error(999))Not Allowed
Not Found
Unknown Error
You can combine multiple values in a single case using the | (or) operator, as shown with 401 | 403.
Let’s look at the different pattern matching capabilities of the match-case statement.
Matching with Variables
You can capture values from the matched expression and use them in the case block. In the following example, we are matching a tuple representing a point on a coordinate plane:
point = (3, 7)
match point:
case (0, 0):
print("Origin")
case (x, 0):
print(f"On x-axis at {x}")
case (0, y):
print(f"On y-axis at {y}")
case (x, y):
print(f"Point at ({x}, {y})")Point at (3, 7)
The variables x and y are assigned the values from the tuple when the pattern matches.
Matching with Guards
You can add an if condition (called a guard) to a case pattern for more precise matching:
def classify_age(age):
match age:
case n if n < 0:
return "Invalid"
case n if n < 18:
return "Minor"
case n if n < 65:
return "Adult"
case _:
return "Senior"
print(classify_age(10))
print(classify_age(30))
print(classify_age(70))Minor
Adult
Senior
The guard (if n < 18) adds an extra condition that must be true for the case to match.
Matching Dictionaries
The match-case statement can match against dictionary structures, which is useful when working with JSON data or API responses:
def process_command(command):
match command:
case {"action": "create", "name": name}:
print(f"Creating {name}")
case {"action": "delete", "name": name}:
print(f"Deleting {name}")
case {"action": action}:
print(f"Unknown action: {action}")
process_command({"action": "create", "name": "users"})
process_command({"action": "delete", "name": "logs"})
process_command({"action": "update"})Creating users
Deleting logs
Unknown action: update
The pattern only needs to match the specified keys. Extra keys in the dictionary are ignored.
Which Approach to Use
| Approach | Best For | Python Version |
|---|---|---|
if-elif-else |
Few conditions, complex boolean logic | All versions |
| Dictionary lookup | Many simple value-to-value mappings | All versions |
match-case |
Pattern matching, destructuring, complex data | 3.10+ |
Use if-elif-else when you have a handful of conditions or need complex boolean expressions. Use dictionary lookups when you are mapping values directly. Use match-case when you need to match against data structures, capture variables, or use guard conditions.
Quick Reference
# if-elif-else
if x == 1:
result = "one"
elif x == 2:
result = "two"
else:
result = "other"
# Dictionary lookup
result = {1: "one", 2: "two"}.get(x, "other")
# match-case (Python 3.10+)
match x:
case 1:
result = "one"
case 2:
result = "two"
case _:
result = "other"FAQ
Does Python have a switch statement?
Not a traditional one. Python uses if-elif-else chains, dictionary lookups, or the match-case statement (Python 3.10+) to achieve similar functionality.
What Python version do I need for match-case?
Python 3.10 or later. If you need to support older versions, use if-elif-else or dictionary lookups instead.
Is match-case faster than if-elif-else?
For most use cases the performance difference is negligible. Choose based on readability and the complexity of your conditions, not speed. Dictionary lookups are often the fastest for simple value mappings.
What happens if no case matches and there is no wildcard?
Nothing. If no pattern matches and there is no case _, the match statement completes without executing any block. No error is raised.
Can I use match-case with classes?
Yes. You can match against class instances using the case ClassName(attr=value) syntax. This is useful for handling different object types in a clean way.
Conclusion
Python does not have a built-in switch statement, but offers three alternatives. Use if-elif-else for simple conditions, dictionary lookups for direct value mappings, and match-case for pattern matching on complex data structures.
For more Python tutorials, see our guides on for loops , while loops , and dictionaries .
If you have any questions, feel free to leave a comment below.

How to Install Python on Ubuntu 24.04
Python is one of the most popular programming languages. It is used to build all kinds of applications, from simple scripts to complex machine-learning systems. With its straightforward syntax, Python is a good choice for both beginners and experienced developers.
Ubuntu 24.04 ships with Python 3.12 preinstalled. To check the version on your system:
python3 --versionPython 3.12.3
If you need a newer Python version such as 3.13 or 3.14, you can install it from the deadsnakes PPA or build it from source. Both methods install the new version alongside the system Python without replacing it. This guide shows how to install Python on Ubuntu 24.04 using both approaches.
Quick Reference
| Task | Command |
|---|---|
| Check installed Python version | python3 --version |
| Add deadsnakes PPA | sudo add-apt-repository ppa:deadsnakes/ppa |
| Install Python 3.13 from PPA | sudo apt install python3.13 |
| Install Python 3.14 from PPA | sudo apt install python3.14 |
| Install venv module | sudo apt install python3.13-venv |
| Build from source (configure) | ./configure --enable-optimizations |
| Build from source (compile) | make -j $(nproc) |
| Install from source | sudo make altinstall |
| Create a virtual environment | python3.13 -m venv myproject |
| Activate virtual environment | source myproject/bin/activate |
| Deactivate virtual environment | deactivate |
Installing Python from the Deadsnakes PPA
The deadsnakes PPA provides newer Python versions packaged for Ubuntu. This is the easiest way to install a different Python version.
-
Install the prerequisites and add the PPA:
Terminal sudo apt update sudo apt install software-properties-common sudo add-apt-repository ppa:deadsnakes/ppaPress
Enterwhen prompted to confirm. -
Install Python 3.13:
Terminal sudo apt update sudo apt install python3.13To install Python 3.14 instead, replace
python3.13withpython3.14in the command above. -
Verify the installation:
Terminal python3.13 --versionoutput Python 3.13.11You can also confirm the binary location:
Terminal which python3.13 -
Install the
venvmodule (needed for creating virtual environments):Terminal sudo apt install python3.13-venv -
If you need
pip, install thevenvpackage and create a virtual environment, then usepipinside the venv:Terminal python3.13 -m venv myproject source myproject/bin/activate python -m pip install --upgrade pipIf you need a system-level
pipfor that interpreter, run:Terminal python3.13 -m ensurepip --upgrade
python3 still points to Python 3.12. To use the newly installed version, run python3.13 or python3.14 explicitly.Installing Python from Source
Compiling Python from source allows you to install any version and customize the build options. However, you will not be able to manage the installation through the apt
package manager.
The following steps show how to compile Python 3.13. If you are installing a different version, replace the version number in the commands below.
-
Install the libraries and dependencies required to build Python:
Terminal sudo apt update sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev liblzma-dev -
Download the source code from the Python download page using
wget:Terminal wget https://www.python.org/ftp/python/3.13.11/Python-3.13.11.tgz -
Terminal tar -xf Python-3.13.11.tgz -
Navigate to the source directory and run the
configurescript:Terminal cd Python-3.13.11 ./configure --enable-optimizationsThe
--enable-optimizationsflag runs profile-guided optimization tests, which makes the build slower but produces a faster Python binary. -
Start the build process:
Terminal make -j $(nproc)The
-j $(nproc)option uses all available CPU cores for a faster build. -
Install the Python binaries using
altinstall. Do not useinstall, as it overwrites the systempython3binary and can break system tools that depend on it:Terminal sudo make altinstall -
Verify the installation:
Terminal python3.13 --versionoutput Python 3.13.11
Setting Up a Virtual Environment
After installing a new Python version, create a virtual environment for your project to keep dependencies isolated:
python3.13 -m venv myprojectActivate the virtual environment:
source myproject/bin/activateYour shell prompt will change to show the environment name. Inside the virtual environment, python and pip point to the version you used to create it.
To deactivate the virtual environment:
deactivateFor more details, see our guide on how to create Python virtual environments .
Uninstalling the PPA Version (Optional)
To remove the PPA version:
sudo apt remove python3.13 python3.13-venvTo remove the PPA itself:
sudo add-apt-repository --remove ppa:deadsnakes/ppaFAQ
Should I use the PPA or build from source?
The deadsnakes PPA is the recommended method for most users. It is easier to install, receives security updates through apt, and does not require build tools. Build from source only if you need a custom build configuration or a version not available in the PPA.
Will installing a new Python version break my system?
No. Both methods install the new version alongside the system Python 3.12. The system python3 command is not affected. You access the new version with python3.13 or python3.14.
How do I make the new Python version the default?
You can use update-alternatives to configure it, but this is not recommended. Many Ubuntu system tools depend on the default python3 being the version that shipped with the OS. Use virtual environments instead.
How do I install pip for the new Python version?
The recommended approach is to use a virtual environment. Install python3.13-venv, create a venv, and use pip inside it. If you need a system-level pip for that interpreter, run python3.13 -m ensurepip --upgrade.
What is the difference between install and altinstall when building from source?altinstall installs the binary as python3.13 without creating a python3 symlink. install creates the symlink, which overwrites the system Python and can break Ubuntu system tools.
Does Ubuntu 24.04 include pip by default?
Ubuntu 24.04 includes Python 3.12 but does not include pip in the base installation. Install it with sudo apt install python3-pip. For newer Python versions, use python3.13 -m pip inside a virtual environment.
Conclusion
Ubuntu 24.04 ships with Python 3.12. To install a newer version, use the deadsnakes PPA for a simple apt-based installation, or build from source for full control over the build. Use virtual environments to manage project dependencies without affecting the system Python.
For more on Python package management, see our guide on how to use pip .
If you have any questions, feel free to leave a comment below.
