两个参数,两种获取方式
百度翻译 fanyi.baidu.com 的 v2transapi 接口需要 sign 和 token 两个参数,获取方式完全不同:
token:页面里写死的,正则一把抠:token: '([0-9a-f]+)'sign:由待翻译文本实时计算,算法在页面 JS 里,种子是页面里的gtk
这个"gtk 种子 + 逐字符位运算"的算法结构,是前端签名里最经典的设计之一,值得完整拆一遍。
gtk:算法的种子
页面源码里有 window.gtk = '320305.131321201' 之类的定义,两个数字用点分隔。它是 sign 计算的初始状态,会不定期更新——所以工程上 gtk 要和 token 一样启动时从页面提取,绝不写死。
sign 算法完整还原
算法核心是一个"指令驱动的位运算器"函数(混淆后常命名为 n 或 a):
def n(r, ops):
# 按指令串对 r 做位运算。指令格式:每3字符一组 [操作][方向][位数]
for t in range(0, len(ops) - 2, 3):
a = ops[t + 2]
a = ord(a) - 87 if a >= 'a' else int(a) # 'a'=10, 'b'=11... 字母表位移
a = (r >> a) if ops[t + 1] == '+' else (r << a)
r = ((r + a) & 0xFFFFFFFF) if ops[t] == '+' else (r ^ a)
return r
def utf16_codes(text):
# JS charCodeAt 语义:emoji 等代理对字符拆成两个码元
codes = []
for ch in text:
cp = ord(ch)
if cp > 0xFFFF:
codes.extend([0xD800 + ((cp - 0x10000) >> 10),
0xDC00 + ((cp - 0x10000) & 0x3FF)])
else:
codes.append(cp)
return codes
def baidu_sign(text, gtk):
a, b = (int(x) for x in gtk.split('.'))
r = a
for code in utf16_codes(text):
r += code
r = n(r, "+-a^+6") # 第一轮:每个字符后执行
r = n(r, "+-3^+b+-f") # 第二轮:收尾执行
r ^= b
if r < 0:
r = (r & 0x7FFFFFFF) + 0x80000000
r %= 1000000
return f"{r}.{r ^ a}"指令串 "+-a^+6" 的读法:+ - a 一组、^ + 6 一组……每组三字符,分别表示"累加/异或、左移/右移、位数"。这套"操作串当程序执行"的设计很精巧——百度想改防护只需换操作串,算法骨架不变。
完整请求流程
import requests, re, hashlib
class BaiduFanyi:
def __init__(self):
self.s = requests.Session()
self.s.headers['User-Agent'] = 'Mozilla/5.0 ...'
self._refresh()
def _refresh(self):
html = self.s.get('https://fanyi.baidu.com/').text
self.token = re.search(r"token: '([0-9a-f]+)'", html).group(1)
self.gtk = re.search(r"window\.gtk = '([^']+)'", html).group(1)
def translate(self, text, to='en'):
data = {
'from': 'zh', 'to': to, 'query': text,
'transtype': 'realtime', 'simple_means_flag': '3',
'sign': baidu_sign(text, self.gtk), 'token': self.token,
}
r = self.s.post('https://fanyi.baidu.com/v2transapi', data=data)
return r.json()精髓:四个实测坑
- 代理对字符(emoji)是签名对不上的头号原因。Python 的
ord()对 emoji 返回完整码点,JS 的charCodeAt返回两个 UTF-16 码元——不做代理对拆分,含 emoji 的文本 sign 必错。上面的utf16_codes就是标准解法。 - token 和 session 绑定。token 从哪个 session 的页面里抠的,就得用哪个 session 发请求,跨 session 使用返回
{"errno": 997}(token 校验失败)。 - 新版加了 Acs-Token 头。2023 年后部分请求还校验
Acs-Token请求头,由页面里的 acs 安全脚本生成。裸请求遇到 998/999 错误码时,检查是不是缺这个头——它的生成有混淆,建议直接用浏览器自动化补。 - 频率限制按 token 计。单 token 高频翻译会触发
{"errno": 998}或滑块,批量场景要轮换 session(每轮换都重拿 token+gtk)。
总结
- token 页面抠、gtk 页面取、sign 实时算,三者同源同 session
- 算法核心是指令驱动的位运算器,
"+-a^+6"这类操作串是灵魂 - emoji 代理对拆分是 Python 复现的最大坑
- 997=token 问题,998=频率问题,错误码即排查地图