有道翻译
数据分析
生成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
}
这里也提供使用python解码的方法,由js可以看出使用的编码是AES,t先经过base64解码后在进行AES解码。
import hashlib
import base64
from Crypto.Cipher import AES
def getDatas(t):
o = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl'
n = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4'
a = hashlib.md5(o.encode()).digest()
r = hashlib.md5(n.encode()).digest()
t = t.replace("_", "/").replace("-", "+")
missing_padding = 4 - len(t) % 4
if missing_padding:
t += '=' * missing_padding
cipher = AES.new(a, AES.MODE_CBC, r)
decrypted = cipher.decrypt(base64.b64decode(t)).decode('utf-8')
index = decrypted.rfind("}")
decrypted = decrypted[:index+1]
return decrypted
代码封装
根据上述,我们通过对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):
path = os.path.dirname(os.path.abspath(__file__))
self.js_path = os.path.join(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.__langType()
self.langMap = {
'zh-CHS':"中文",
"en":"英语",
"ja":"日语",
"ko":"韩语",
"fr":"法语",
"de":"德语",
"ru":"俄语",
"es":"西班牙语",
"pt":"葡萄牙语",
"vi":"越南语",
"id":"印度尼西亚语",
"it":"意大利语",
"nl":"荷兰语",
"th":"泰语",
"ar":'阿拉伯语'
}
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, cookies=self.cookies, data=data).text
# 使用execjs解码
# with open(self.js_path, 'r', encoding='utf-8') as po:
# signs = execjs.compile(po.read()).call('datas', response)
# text = json.loads(signs)
# 使用python解码
signs = self.__decode(response)
text = json.loads(signs)
result_list = []
if text.get("translateResult"):
for i in text['translateResult']:
result_list.append(i[0]["tgt"].strip())
else:
result_list.append("对不起,无法翻译该语种")
res_dict = {
"result":"\n".join(result_list),
"CEDict":[]
}
if text.get('dictResult') and text['dictResult'].get("ce"):
for tr in text['dictResult']['ce']['word']['trs']:
if not tr.get("#tran"):
continue
res_dict["CEDict"].append({"text":tr['#text'],"tran":tr['#tran']})
return res_dict
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()
self.langDict = {}
common = text["data"]["value"]["textTranslate"]["common"]
specify = text["data"]["value"]["textTranslate"]["specify"]
for data in common + specify:
self.langDict[data["code"]] = data["label"]
def __decode(self,t):
o = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl'
n = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4'
a = hashlib.md5(o.encode()).digest()
r = hashlib.md5(n.encode()).digest()
t = t.replace("_", "/").replace("-", "+")
missing_padding = 4 - len(t) % 4
if missing_padding:
t += '=' * missing_padding
cipher = AES.new(a, AES.MODE_CBC, r)
decrypted = cipher.decrypt(base64.b64decode(t)).decode('utf-8')
index = decrypted.rfind("}")
decrypted = decrypted[:index+1]
return decrypted
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
参数控制翻译的语言。