使用Reqable和算法助手简单逆向有道翻译官查词接口(上)

准备环境

有 Root 环境的安卓手机或模拟器一台,需要安装 LSPosed 框架。

安装算法助手并授予 Root 权限,并在 LSPosed 中勾选需要捕获的 APP。

在算法助手中勾选需要捕获的 APP 和需要分析的算法。

以上我们的算法助手就设置完了,下面开始设置 Reqable。

根据 Reqable 的提示我们扫码把手机和电脑设置协同,使用 Magisk 模块安装证书后根据提示重启。

打开 Reqable 并连接上电脑,再打开有道翻译官进行查词,这里以单词 dog 为例。

分析 HTTP 请求

右键这个请求可以复制 curl 命令,这里我们把这个 curl 命令交给 Deepseek 并让他帮我们拆解出全部参数。

import requests
import time

url = "https://fanyiguan-api.youdao.com/search_s"

params = {
    "q": "dog",
    "from": "en",
    "to": "zh-CHS",
    "userLabel": "LEARNER, HIGHSCHOOL, IELTS",
    "lecheck": "true",
    "dev_name": "Redmi Note 8 Pro",
    "product": "fanyiguan",
    "appVersion": "4.2.2",
    "keyfrom": "fanyi.4.2.2.android",
    "mid": "11",
    "screen": "1080x2340",
    "network": "wifi",
    "abtest": "9",
    "yduuid": "5d41d9c2ca51d193",
    "vendor": "vivo",
    "client": "android",
    "imei": "CQljODMzNGE3ZTMzYjM4NmMyCXVua25vd24=",
    "model": "Mi_10",
    "oaid": "5d41d9c2ca51d193",
    "pointParam": "userLabel,dev_name,product,appVersion,keyfrom,mid,screen,keyid,mysticTime,network,q,lecheck,abtest,yduuid,vendor,client,imei,from,model,to,oaid",
    "sign": "b61237da3b7e82f1f15c804188ac9644", # 签名校验
    "mysticTime": "1756639283092",  # 使用当前时间戳
    "keyid": "fyg_trans_key"
}

headers = {
    "User-Agent": "okhttp/5.0.0-alpha.14",
    "Connection": "Keep-Alive",
    "Accept-Encoding": "gzip",
    "Content-Type": "application/x-www-form-urlencoded; charset=utf-8"
}

data = {
    "dicts": '{"count":69,"dicts":[["ct"],["tc"],["ec"],["ce"],["newjc"],["newcj"],["yd_cj"],["yd_jc"],["newcj_sents"],["jtj"],["ck"],["kc"],["cf"],["fc"],["multle"],["longman"],["newcenturyfc"],["collins"],["ec21"],["exam_dict"],["ee"],["hh"],["newhh"],["ce_new"],["special"],["web_trans"],["pic_dict"],["word_video"],["rel_word"],["phrs"],["syno"],["wordform"],["ywAncientWord"],["ywBasic"],["ywRelatedWords"],["ywWordNet"],["ywIdiom"],["ywSynAndAnt"],["blng_sents_part"],["auth_sents_part"],["media_sents_part"],["baike"],["etym"],["wikipedia_digest"],["typos"],["web_search"],["fanyi"],["tcb"],["ctc"],["ugc"],["oxfordAdvance"],["huge_ec"],["huge_ce"],["newcenturyjc"],["oxford"],["webster"],["special_economy"],["special_medicine"],["special_elec_commu_auto_control"],["special_mechanical"],["special_energy"],["special_biology"],["special_traffic"],["special_computer"],["special_other"],["video_sents"],["longchao-ck"],["longchao-kc"],["individual"]]}'
}

response = requests.post(
    url=url,
    params=params,
    headers=headers,
    data=data
)

print(f"Status Code: {response.status_code}")
print(f"Response: {response.text}")

请求头和请求体中没有看出什么特别的,所以我们详细分析 query strings 也就是 URL parameters 的部分。

这里的 yduuidoaid 一模一样,推测是用户id标识。

sign 推测是反爬校验的参数,更改q之后重新发送请求果然无法获取到数据。

{
  "code": 403,
  "msg": "Permission Denied"
}

这时我们打开算法助手,找到有道翻译官的日志搜索b61237da3b7e82f1f15c804188ac9644这个值。

abtest=9&appVersion=4.2.2&client=android&dev_name=Redmi Note 8 Pro&from=en&imei=CQljODMzNGE3ZTMzYjM4NmMyCXVua25vd24%3D&keyfrom=fanyi.4.2.2.android&keyid=fyg_trans_key&lecheck=true&mid=11&model=Mi_10&mysticTime=1756639283092&network=wifi&oaid=5d41d9c2ca51d193&product=fanyiguan&q=dog&screen=1080x2340&to=zh-CHS&userLabel=LEARNER, HIGHSCHOOL, IELTS&vendor=vivo&yduuid=5d41d9c2ca51d193&key=gj6TNi1SC5igmZlVSFgyX1lxSrtAQlcr

可以看出b61237da3b7e82f1f15c804188ac9644是一个 MD5 算法摘要后的值,上面这一段就是摘要前的值。很明显其中大部分都出现在请求的 URL 查询参数中,除了一个key

按照上面的方法,重新发送请求后查看算法助手的日志发现这个用来组成字符串的key参数是固定的,我推测服务端应该也存储有对应的imei或者yduuid和相对应的这个key来生成同样的 MD5 摘要进行校验,所以保留这个key用来生成我们自己的sign

实现代码

import requests
import time
import hashlib
import ssl
import urllib3
import json
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class YouDaoTranslator:
    def __init__(self):
        self.secret_key = 'gj6TNi1SC5igmZlVSFgyX1lxSrtAQlcr'
        self.base_params = {
            'abtest': '9',
            'appVersion': '4.2.2',
            'client': 'android',
            'dev_name': 'Redmi Note 8 Pro',
            'imei': 'CQljODMzNGE3ZTMzYjM4NmMyCXVua25vd24%3D',
            'keyfrom': 'fanyi.4.2.2.android',
            'keyid': 'fyg_trans_key',
            'lecheck': 'true',
            'mid': '11',
            'model': 'Mi_10',
            'network': 'wifi',
            'oaid': '5d41d9c2ca51d193',
            'product': 'fanyiguan',
            'screen': '1080x2340',
            'vendor': 'vivo',
            'yduuid': '5d41d9c2ca51d193'
        }
        self.param_order = [
            'abtest', 'appVersion', 'client', 'dev_name', 'from', 'imei',
            'keyfrom', 'keyid', 'lecheck', 'mid', 'model', 'mysticTime',
            'network', 'oaid', 'product', 'q', 'screen', 'to', 'vendor', 'yduuid'
        ]
    
    def generate_sign(self, q_text, from_lang, to_lang):
        print("\n===== generate_sign 函数开始 =====")
        print(f"输入参数: 文本='{q_text}', 源语言='{from_lang}', 目标语言='{to_lang}'")
        
        params = self.base_params.copy()
        params.update({
            'q': q_text,
            'from': from_lang,
            'to': to_lang,
            'mysticTime': str(int(time.time() * 1000))
        })
        print(f"添加翻译参数后的完整参数: {json.dumps(params, ensure_ascii=False, indent=2)}")
        print(f"当前时间戳(mysticTime): {params['mysticTime']}")
        
        sign_parts = []
        for key in self.param_order:
            if key in params:
                sign_parts.append(f"{key}={params[key]}")
                print(f"添加参数到签名: {key}={params[key]}")
        
        sign_str = '&'.join(sign_parts) + f'&key={self.secret_key}'
        print(f"签名字符串(sign_str): {sign_str}")
        
        sign = hashlib.md5(sign_str.encode('utf-8')).hexdigest()
        print(f"MD5签名结果(sign): {sign}")
        print("===== generate_sign 函数结束 =====\n")
        
        return params, sign
    
    def translate(self, text, from_lang='en', to_lang='zh-CHS'):
        params, sign = self.generate_sign(text, from_lang, to_lang)
        
        url = 'https://fanyiguan-api.youdao.com/search_s'
        
        request_params = params.copy()
        request_params['sign'] = sign
        request_params['mysticTime'] = params['mysticTime']
        request_params['pointParam'] = 'dev_name,product,appVersion,keyfrom,mid,screen,keyid,mysticTime,network,q,lecheck,abtest,yduuid,vendor,client,imei,from,model,to,oaid'
        
        headers = {
            'User-Agent': 'okhttp/5.0.0-alpha.14',
            'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
        }
        
        data = {
            'dicts': '{"count":69,"dicts":[["ct"],["tc"],["ec"],["ce"],["newjc"],["newcj"],["yd_cj"],["yd_jc"],["newcj_sents"],["jtj"],["ck"],["kc"],["cf"],["fc"],["multle"],["longman"],["newcenturyfc"],["collins"],["ec21"],["exam_dict"],["ee"],["hh"],["newhh"],["ce_new"],["special"],["web_trans"],["pic_dict"],["word_video"],["rel_word"],["phrs"],["syno"],["wordform"],["ywAncientWord"],["ywBasic"],["ywRelatedWords"],["ywWordNet"],["ywIdiom"],["ywSynAndAnt"],["blng_sents_part"],["auth_sents_part"],["media_sents_part"],["baike"],["etym"],["wikipedia_digest"],["typos"],["web_search"],["fanyi"],["tcb"],["ctc"],["ugc"],["oxfordAdvance"],["huge_ec"],["huge_ce"],["newcenturyjc"],["oxford"],["webster"],["special_economy"],["special_medicine"],["special_elec_commu_auto_control"],["special_mechanical"],["special_energy"],["special_biology"],["special_traffic"],["special_computer"],["special_other"],["video_sents"],["longchao-ck"],["longchao-kc"],["individual"]]}'
        }
        
        # 添加 verify=False 来禁用SSL验证
        response = requests.post(
            url=url,
            params=request_params,
            headers=headers,
            data=data,
            verify=False  # 禁用SSL验证
        )
        
        return response.json()

if __name__ == "__main__":
    translator = YouDaoTranslator()
    try:
        print("开始翻译单词 'dog'...")
        result = translator.translate("dog")
        # 将翻译结果保存到json文件
        with open('translation_result.json', 'w', encoding='utf-8') as f:
            json.dump(result, f, ensure_ascii=False, indent=4)
        print("翻译结果已保存到 translation_result.json")
    except Exception as e:
        print("错误:", e)

这里我们查看保存的 json 文件后发现仍有几个数据是加密的,比如"webster"对象中的"encryptedData""oxfordAdvance"中的"encryptedData"
下篇中会继续使用算法助手解密韦氏词典和牛津高阶词典数据。