客户端配置¶
x-api-rs 是 SDK,HTTP/性能/可用性相关参数都通过 RquestClientBuilder 暴露给上层使用方。
本文档列出所有可配置项的默认值、风险、何时该改。
设计原则¶
- 安全默认 + 可覆盖:默认值对非幂等写操作(DM/Posts/Like/上传)安全,高级用户可通过 builder 覆盖
- 协议常量不暴露:与 Twitter API 协议绑定的常量(如
MAX_MESSAGE_LENGTH = 10000、GraphQL queryId、Bearer Token)写死在 SDK 内,不可配置 - 传输/性能参数暴露:timeout、retry、proxy、pool size、JA3 指纹等可调
配置项参考¶
tls_cert_verification: bool 🔴¶
是否校验服务端 TLS 证书。对应 wreq ClientBuilder::tls_cert_verification。
| 项目 | 值 |
|---|---|
| 默认 | true(与 wreq 库默认一致,校验证书) |
| 风险等级 | 设为 false 时 🔴 高(接受任意无效证书,存在 MITM) |
仅在必须使用自签/拦截式调试代理时,通过 RquestClientBuilder::tls_cert_verification(false) 显式关闭。
环境快照 network.tls_cert_verification 会导出/还原该选项;旧快照缺字段时按 true 反序列化。
http2_max_retry_count: usize 🔴¶
HTTP/2 protocol-nack 自动重试次数。控制 wreq 在收到 RST_STREAM(REFUSED_STREAM) 或 GOAWAY(NO_ERROR)
时是否自动重发请求。
| 项目 | 值 |
|---|---|
| 本 SDK 默认 | 0(禁用) |
| wreq 默认 retry policy | protocol-nack 最大重试 2 |
| 风险等级 | 🔴 高(影响数据正确性) |
为什么默认禁用:
wreq 默认 retry policy 把 H2 REFUSED_STREAM 视为"请求未到达 server"自动重发(RFC 9113 §8.7-3.2)。
但 Twitter 在限流时会先处理请求再发 RST_STREAM——server 已经收到并执行了请求,
wreq 仍重发,导致同一条 DM/Tweet 被发送 2 次。
200 并发 DM 场景实测:11-19 条消息出现重复(5.5%-9.5% 重复率)。
何时可以改成 >0:
- ✅ 客户端只用于幂等读操作:搜索、获取用户资料、获取 timeline、获取私信列表
- ❌ 绝对不能用于:DM 发送、发帖、点赞、转发、关注、上传——任何写操作
如果同一个客户端实例会混用读写,保留默认 0。
用法:
use x_api_core::common::{CookieAuth, ClientTransaction, RquestClientBuilder};
use std::sync::Arc;
// ✅ 安全默认(推荐)
let client = RquestClientBuilder::new(auth, transaction).build()?;
// ⚠️ 仅在纯读场景下可考虑
let client = RquestClientBuilder::new(auth, transaction)
.http2_max_retry_count(2)
.build()?;
proxy_url: Option<String>¶
代理服务器 URL,支持 HTTP / HTTPS / SOCKS5。
| 项目 | 值 |
|---|---|
| 默认 | None(直连) |
| 风险等级 | 🟢 低 |
已知陷阱:
- Burp Suite 会破坏 JA3 指纹模拟(TLS MITM 终止连接)→ 触发 Twitter 226 反爬
- 大文件 multipart 上传 (>~100KB) 通过 Burp 会失败:
Stream failed to close correctly - 测试场景建议使用 mitmproxy 或直连
用法:
RquestClientBuilder::new(auth, transaction)
.proxy_url("http://127.0.0.1:8080".to_string())
.build()?;
profile: Option<WebProfile>¶
浏览器指纹 profile —— 把 TLS 指纹(JA3/JA4 + HTTP/2 SETTINGS)与 HTTP 头部(User-Agent / Sec-Ch-Ua)绑成一个自洽身份。支持每账号独立配置。
| 项目 | 值 |
|---|---|
| 默认(不传) | WebProfile::Chrome { os: Windows, arch: X64, version: CHROME_MAJOR }(当前为 Chrome 149 Windows x64) |
| 风险等级 | 🟢 低 |
为什么是一个参数而不是 enable_ja3 + fingerprint 两个:真实浏览器的 TLS 指纹和 HTTP 头是同一个身份,拆成两个独立开关会产生「UA 自称 Chrome 140、TLS 握手却是另一版本」这种错配——而 UA/JA3 错配本身就是反爬识别机器人的特征。WebProfile 保证两层永远一致。
默认为何固定 Chrome 基线 Windows x64(而非随机):指纹模拟的目标是混进最大的真实人群,不是变独特。Chrome Windows 是全球 Twitter Web 桌面占比最大的配置,淹没在真人流量里最安全。随机化反而可能制造唯一性或 UA/JA3 错配。
跨账号差异化:要让 A 账号和 B 账号在 JA3 层真正不同,必须跨浏览器家族(Chrome / Safari / Firefox / Edge / OkHttp 各自 TLS 不同);在同一 Chrome 家族内换 OS/版本不改变 JA3。
支持的 profile:
| 家族 | 构造 | TLS 来源 | HTTP 头 |
|---|---|---|---|
| Chrome(默认 149) | WebProfile::Chrome { os, arch, version } |
Emulation::ChromeNNN(与 version 对齐) |
SDK 自建完整 Sec-Ch-Ua-* |
| Safari macOS | WebProfile::SafariMac(v) |
wreq-util Safari emulation | emulation 自带(Safari 不发 Sec-Ch-Ua) |
| Safari iOS | WebProfile::SafariIos(v) |
同上 | 同上 |
| Safari iPad | WebProfile::SafariIpad |
同上 | 同上 |
| Firefox | WebProfile::Firefox(v) |
wreq-util Firefox emulation | 同上 |
| Edge | WebProfile::Edge(v) |
wreq-util Edge emulation | 同上 |
| OkHttp(Android app) | WebProfile::OkHttp(v) |
wreq-util OkHttp emulation | 同上 |
| 禁用 TLS 模拟(调试) | WebProfile::None |
wreq 默认 | 无模拟 |
HTTP/2:始终开启,h2 SETTINGS 帧指纹随 profile 一起模拟(
WebProfile::None除外)。TLS 版本锚定:Chrome 默认版本由
CHROME_MAJOR(当前 149)决定,且必须与wreq-util的Emulation::ChromeNNN同步;Sec-Ch-Ua/User-Agent 由 SDK 按同一版本自建。
Rust 用法:
use x_api_core::web::{Architecture, OsType, WebClient, WebProfile};
// 默认(Chrome 149 Windows x64)
let client = WebClient::create(cookies, None, None).await?;
// 指定 Chrome macOS arm64
let p = WebProfile::Chrome { os: OsType::MacOS, arch: Architecture::Arm64 };
let client = WebClient::create(cookies, None, Some(p)).await?;
// 查看当前身份
let info = client.fingerprint_info();
println!("{} {} {} {}", info.browser, info.version, info.platform, info.user_agent);
完整示例¶
use x_api_core::common::fingerprint::types::{Architecture, OsType};
use x_api_core::web::{WebClient, WebProfile};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cookies = "ct0=xxx; auth_token=yyy; twid=u%3D123".to_string();
// 默认安全配置(Chrome 149 Windows x64,TLS 证书校验开启,H2 重试禁用)
let client = WebClient::create(cookies.clone(), None, None).await?;
// 带代理 + 指定 profile
let p = WebProfile::Chrome { os: OsType::MacOS, arch: Architecture::Arm64 };
let client = WebClient::create(cookies, Some("http://127.0.0.1:8080".into()), Some(p)).await?;
Ok(())
}
需要细粒度控制
http2_max_retry_count等传输参数时,优先使用WebClient::builder()的 transport/profile 配置;若需要更底层控制,再在 core 内部通过web::transport组合WebClient。
Python 用户¶
from x_api_rs.web import Client, WebProfile
# 默认(Chrome 149 Windows x64,对齐当前 wreq-util 最新桌面模板)
client = await Client.create(cookies)
# 带代理
client = await Client.create(cookies, proxy_url="http://127.0.0.1:8080")
# 每账号独立身份——跨家族才有真实 JA3/H2 差异
a = await Client.create(cookies_a, profile=WebProfile.chrome("macos", "arm64"))
b = await Client.create(cookies_b, profile=WebProfile.safari_ios("18.1.1"))
c = await Client.create(cookies_c, profile=WebProfile.okhttp("5"))
# 禁用 TLS 模拟(仅调试)
d = await Client.create(cookies, profile=WebProfile.none())
# 查看实际身份
info = a.fingerprint_info()
print(info.browser, info.version, info.platform)
print(info.user_agent)
WebProfile 工厂方法:
| 方法 | 说明 |
|---|---|
WebProfile.default_latest() |
当前推荐默认画像,等价于 Chrome 149 Windows x64(wreq-util 最新桌面模板) |
WebProfile.chrome(os, arch, version=None) |
Chrome 基线 149;version 可选,仅允许当前库有 TLS 模板的产品版本(120–149,排除 121/122/125)。os: windows/macos/linux;arch: x64/x86_64/arm64/aarch64 |
WebProfile.safari_mac(version) |
Safari macOS。17.5/18/18.2/18.3/18.3.1 |
WebProfile.safari_ios(version) |
Safari iOS。17.4.1/18.1.1 |
WebProfile.safari_ipad() |
Safari iPadOS 18 |
WebProfile.firefox(version) |
Firefox。133/135/136/private136/android135 |
WebProfile.edge(version) |
Edge。131/134 |
WebProfile.okhttp(version) |
OkHttp。5/4.12 |
WebProfile.none() |
禁用 TLS 模拟,仅用于调试 |
WebProfile.from_spec(spec) |
从持久化字符串还原,如 chrome:macos:arm64 |
profile.spec() 返回可持久化的规范字符串,可和环境快照或自管数据库一起保存。
fingerprint_info() 返回 ProfileInfo,字段:browser / version / platform / user_agent。
http2_max_retry_count 当前未在 Python 层暴露——因为 Python 用户场景几乎都涉及写操作,
强制使用安全默认(0)。如需读专用客户端,请联系 issue 反馈。
历史¶
- 2026-05-09:发现底层 HTTP 客户端默认 H2 protocol-nack retry 会导致 DM 重复发送,本 SDK 默认改为 0,并通过
RquestClientBuilder::http2_max_retry_count暴露为可配置(v1.0.15);迁移到 wreq 6 后该配置映射为wreq::retry::Policy。