有道翻译
数据分析
生成sign
通过抓取数据,有道翻译是请求这个接口 https://dict.youdao.com/webtranslate,请求参数如下所示:

通过观察参数,mysticTime为时间搓,sign为加密算法生成的数据,其他参数不变,所以只需找到sign的生成函数就可以了。
通过全局搜索,在每个出现sign位置打上断点,找到生成sign的函数

sign是有w函数生成,w函数接收两个值,一个是当前的时间搓,另一个经过重复获取,发现是固定值。
w函数内部通过字符串格式化后把值传给A函数,观察A函数,只是把传过来的值进行md5加密。
使用python实现生成sign。
localtime = str(int(time.time() * 1000))
data = "client=fanyideskweb&mysticTime={}&product=webfanyi&key=fsdsogkndfokasodnaso".format(localtime)
sign = hashlib.md5(data.encode(encoding='utf-8')).hexdigest()
设置cookie
生成sign后,可以构造请求发送请求获取数据。
可以观察,请求需要携带cookie,通过清除游览器的cookie刷新界面,再次发送请求,发现cookie是通过请求这个接口 https://dict.youdao.com/login/acc/query/accountinfo,在请求这个接口的请求结果头部设置了cookie。

使用python实现设置cookie。
url = "https://dict.youdao.com/login/acc/query/accountinfo"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
"Referer": "https://fanyi.youdao.com/index.html"
}
response = requests.get(url=url, headers=headers)
headers["Cookie"] = response.headers["Set-Cookie"]
解密请求结果
设置cookie,生成sign后请求数据,获取请求结果,发现请求结果是密文,需要通过解密获取具体的结果信息。通过添加断点以及堆栈信息,一步步查找。

可以观察到,密文o传入到tt["a"].decodeData函数后传回给a的是明文数据。点击tt["a"].decodeData跳转到decodeData的具体函数。

通过观察,o 与 n 参数为固定值,并都是作为参数传值给 j 函数,点击j函数跳转到具体位置,发现j函数是只是进行md5加密后的数据返回。然后把密文和数据传给createDecipheriv函数进行解密得到明文。
如果使用python对数据进行处理的会得到各种报错,因此使用execjs库直接把数据交个js处理。
const crypto=require('crypto')
function getDatas(t){
const o = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl';
const n = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4';
const a = Buffer.alloc(16, crypto.createHash('md5').update(o).digest())
const r = Buffer.alloc(16, crypto.createHash('md5').update(n).digest())
const i = crypto.createDecipheriv("aes-128-cbc", a, r);
let s = i.update(t, "base64", "utf-8");
return s += i.final("utf-8"), s
}
代码封装
根据上述,我们通过对js逆向对有道翻译进行处理得到明文数据,对上述步骤进行封装整理成类。
import time
import json
import hashlib
import requests
import subprocess
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')
import execjs
class YouDaoTranslate():
def __init__(self):
self.js_path = "有道.js"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
"Referer": "https://fanyi.youdao.com/index.html"
}
self.__set_cookie()
self.langDict = {}
self.__langType()
def translate(self,query,from_="auto",to=""):
url = "https://dict.youdao.com/webtranslate"
localtime = str(int(time.time() * 1000))
data = "client=fanyideskweb&mysticTime={}&product=webfanyi&key=fsdsogkndfokasodnaso".format(localtime)
sign = hashlib.md5(data.encode(encoding='utf-8')).hexdigest()
data = {
"i": query,
"from": from_,
"to": to,
"dictResult": "true",
"keyid": "webfanyi",
"sign": sign,
"client": "fanyideskweb",
"product": "webfanyi",
"appVersion": "1.0.0",
"vendor": "web",
"pointParam": "client,mysticTime,product",
"mysticTime": localtime,
"keyfrom": "fanyi.web"
}
response = requests.post(url=url, headers=self.headers, data=data).text
with open(self.js_path, 'r', encoding='utf-8') as po:
signs = execjs.compile(po.read()).call('datas', response)
text = json.loads(signs)
try:
return text['dictResult']['ce']['word']['trs'][0]['#text']
except:
return text['translateResult'][0][0]['tgt']
def __langType(self):
"""语言类型"""
url = "https://api-overmind.youdao.com/openapi/get/luna/dict/luna-front/prod/langType"
response = requests.get(url=url, headers=self.headers)
text = response.json()
common = text["data"]["value"]["textTranslate"]["common"]
specify = text["data"]["value"]["textTranslate"]["specify"]
for data in common + specify:
self.langDict[data["code"]] = data["label"]
def __set_cookie(self):
url = "https://dict.youdao.com/login/acc/query/accountinfo"
response = requests.get(url=url, headers=self.headers)
self.headers["Cookie"] = response.headers["Set-Cookie"]
if __name__ == '__main__':
obj = YouDaoTranslate()
res = obj.translate('你好,世界')
print(res)
该类下还增加了获取语言类型方法,通过translate方法的from_和to参数控制翻译的语言。