【Code With SOLO】用 SOLO 从零构建豆包网页版逆向 API 网关——让免费大模型接入 OpenAI 生态
1. 摘要
用 TRAE SOLO 从零构建了 doubao2API——一个豆包网页版逆向 API 网关,基于 Playwright + Chromium 的 BrowserOnly 架构,巧妙利用字节前端 JS 拦截器自动注入反爬签名(a_bogus/msToken),无需手动实现反爬逻辑。最终产出一个完全兼容 OpenAI API 的网关服务,支持流式/非流式对话、文生图、多账号负载均衡和 Docker 一键部署。
2. 背景
我是一名后端开发者,日常需要将各类大模型接入业务系统。豆包(doubao.com)提供了免费的对话能力,但只有网页版,没有开放 API。如果手动逆向豆包的接口,最大的挑战是字节跳动的 a_bogus/msToken 反爬签名——这套签名算法深度混淆、频繁更新,纯 Python 实现维护成本极高。
我希望能找到一种"绕过签名实现"的方式,把豆包的能力包装成 OpenAI 兼容 API,这样任何支持 OpenAI SDK 的工具/框架都能直接对接。
3. 实践过程
任务拆解
整个项目我拆成了以下几个核心模块:
- 浏览器引擎——用 Playwright 启动 Chromium,在浏览器内执行 fetch,利用字节 JS 自动注入签名
- 账号池——管理多个豆包 sessionid,实现轮询、限流冷却、失效标记
- 会话管理——构建豆包特有的请求体(content_block 嵌套结构、local_conversation_id 等)
- SSE 解析——解析豆包 7 种 SSE 事件类型,提取文本/图片/错误
- OpenAI 兼容层——将豆包响应转换为 OpenAI 格式,支持流式 chunk 输出
- 管理后台——账号增删、API Key 管理、状态查看
关键设计与 SOLO 的协作
核心洞察:不逆向签名,逆向浏览器环境
这是整个项目最关键的决策。我发现豆包前端的 window.fetch 已经被字节 JS 劫持——在浏览器内调用 fetch() 时,a_bogus 和 msToken 会被自动注入到 URL query 参数中。因此只需在 Playwright 打开的浏览器页面中执行 fetch,签名问题就自动解决了。
SOLO 在这个过程中帮我完成了:
- 浏览器引擎的 Cookie 注入策略设计:先注入 Cookie 再导航页面(而非先加载页面再注入 Cookie),确保字节 JS 拦截器以登录态初始化,避免游客态 → 登录态切换的不可靠性
- SSE 解析器的完整实现:豆包的 SSE 协议比 OpenAI 复杂得多,包含 SSE_HEARTBEAT、SSE_ACK、FULL_MSG_NOTIFY、STREAM_MSG_NOTIFY、CHUNK_DELTA、STREAM_CHUNK、SSE_REPLY_END 等 7 种事件类型,需要从 content_block 嵌套结构中提取文本(block_type=10000)和图片(block_type=2074)
- 请求体的精确构造:豆包用 bot_id 标识模型而非 model 字段,请求体包含 client_meta、messages、option、ext 四层嵌套,option 中有 need_create_conversation 等关键标志位
关键 Prompt 示例
构建一个 BrowserEngine 类,核心原理是:
1. 豆包的 a_bogus/msToken 由字节 JS 拦截器在浏览器内自动注入
2. 必须在浏览器环境中执行 fetch,字节 JS 会 hook window.fetch
3. 使用 Playwright Chromium
4. 先注入 Cookie → 再导航页面,确保以登录态初始化
5. 页面池设计,每个 page 绑定独立 BrowserContext 实现 Cookie 隔离
踩过的坑
- Cookie 注入时序问题:最初先导航页面再注入 Cookie,导致字节 JS 拦截器在游客态初始化,fetch 请求缺少签名参数。改为"先注入 Cookie 再导航"后解决。
- SSE 事件类型发现:豆包的 SSE 不是简单的
data: {text}格式,而是包含多种事件类型,每种都有不同的数据结构。需要逐个 F12 抓包分析,特别是 STREAM_CHUNK 中的 patch_op/patch_value 嵌套结构。 - conversation_id 为 0 的问题:如果请求体中缺少 local_conversation_id,服务端会返回 conversation_id=“0” 表示会话创建失败。需要在 client_meta 中正确设置 local_conversation_id 和 need_create_conversation 标志。
4. 成果展示
项目代码:doubao2API — 完整的 Python 项目,约 2000 行代码
架构图:
┌──────────────┐ ┌──────────────────────────────────────────┐
│ 客户端 │ │ doubao2API (FastAPI, 端口 7861) │
│ (curl/SDK) │────→│ │
│ │←────│ /v1/chat/completions (OpenAI 兼容) │
└──────────────┘ │ ┌─ DoubaoClient ───────────────────┐ │
│ │ AccountPool → BrowserEngine │ │
│ │ SessionStore Playwright+Chromium│ │
│ │ build_payload JS fetch(浏览器内)│ │
│ │ 字节JS自动注入 │ │
│ │ a_bogus/msToken │ │
│ │ DoubaoSSEParser │ │
│ └─────────────────────────────────-┘ │
└──────────────────────────────────────────┘
使用示例(Python OpenAI SDK 直接对接):
from openai import OpenAI
client = OpenAI(api_key="admin", base_url="http://127.0.0.1:7861/v1")
# 流式对话
stream = client.chat.completions.create(
model="doubao",
messages=[{"role": "user", "content": "你好"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
Docker 一键部署:
docker compose up -d --build
项目结构:
doubao2API/
├── backend/
│ ├── main.py # FastAPI 入口
│ ├── api/v1_chat.py # OpenAI 兼容接口
│ ├── api/admin.py # 管理后台
│ ├── core/browser_engine.py # Playwright + Chromium 引擎
│ ├── core/account_pool.py # 多账号负载均衡
│ ├── core/config.py # 配置 + 模型映射
│ ├── services/doubao_client.py # 核心客户端
│ ├── services/sse_parser.py # 7种SSE事件解析
│ └── services/session_store.py # 会话状态管理
├── scripts/md_optimizer.py # 批量Markdown翻译工具(实际应用)
├── Dockerfile # 一键容器化
└── docker-compose.yml
5. 效果与总结
提效效果
- 传统方式:纯 Python 逆向 a_bogus 签名,预计 2-3 天开发 + 持续维护(签名算法频繁更新)
- SOLO + BrowserOnly 方式:利用 SOLO 从零构建完整项目,核心开发约 2 小时,且零签名维护成本——字节更新 JS 拦截器不影响我们
SOLO 在流程中的角色
- 架构设计:帮我确定了 BrowserOnly 的核心策略——不在 Python 层逆向签名,而是利用浏览器环境让字节自己的 JS 完成签名
- 模块实现:从浏览器引擎到 SSE 解析器,SOLO 帮我逐模块实现了约 2000 行代码
- Debug 协作:Cookie 注入时序问题、conversation_id 为 0 等关键 bug,通过 SOLO 分析日志快速定位
可复用的方法
- “借用浏览器环境"而非"逆向浏览器逻辑”——这个思路适用于任何有前端 JS 拦截器/签名的网站,不仅是豆包,类似的网页版 AI(如通义千问 qwen2API)也可以用同一套架构
- BrowserOnly 架构模式:Playwright + 页面池 + Cookie 隔离 + 先注入后导航,这套模式可以作为通用模板
- OpenAI 兼容层设计:模型名映射(BOT_MAP)、流式 SSE 转换、文生图意图检测,这些组件在其他逆向网关中也可复用
项目地址:doubao2API