有道翻译——JS解密

爬虫兴起的同时,反爬虫手段也在不断更新,今天以有道翻译http://fanyi.youdao.com/为例,介绍破解JavaScript加密的反爬虫基本流程。

分析网页

我们进入网站,随便输入一个内容(比如spider),会在network的XHR下发现一个translate开头的动态加载文件,查看一下它的标头:

有道翻译——JS解密

这是一个POST请求,URL为http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule。再看一下响应:

有道翻译——JS解密

可以惊喜的发现这个请求的响应恰好就是我们要的东西,那直接获取这个URL的响应内容解析一下不就行了?不妨先试一试:

import requests


url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"

headers = {
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
    'Connection': 'keep-alive',
    'Content-Length': '240',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'Cookie': 'OUTFOX_SEARCH_USER_ID=-1655056227@10.108.160.102; JSESSIONID=aaaLbl1vQGP7LaMN6GeFx; OUTFOX_SEARCH_USER_ID_NCOO=516404572.3890775; ___rl__test__cookies=1613910186738',
    'Host': 'fanyi.youdao.com',
    'Origin': 'http://fanyi.youdao.com',
    'Referer': 'http://fanyi.youdao.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.68',
    'X-Requested-With': 'XMLHttpRequest'
}

data = {
    'i': 'spider',
    'from': 'AUTO',
    'to': 'AUTO',
    'smartresult': 'dict',
    'client': 'fanyideskweb',
    'salt': '16139101867430',
    'sign': 'b5b2bfdc381df9c9b104a03f2244bdaf',
    'lts': '1613910186743',
    'bv': 'a68be4fa7a0372e7ae83662b495a6f4c',
    'doctype': 'json',
    'version': '2.1',
    'keyfrom': 'fanyi.web',
    'action': 'FY_BY_REALTlME'
}

req = requests.post(url,headers=headers,data=data)

print(req.text)

查看一下运行结果:

有道翻译——JS解密

嗯哼,这不是已经获取到了我们需要的结果了吗!!!难道今天就要到此为止了?

想一想我们要写这样一个脚本的初衷是什么?当然是为了想输入什么内容就能得到想要的信息,而不是仅仅获取一个spider的含义。不妨将data里面的'i': 'spider'换成'i': 'translate',,查看一下translate是什么意思。

import requests


url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"

headers = {
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
    'Connection': 'keep-alive',
    'Content-Length': '240',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'Cookie': 'OUTFOX_SEARCH_USER_ID=-1655056227@10.108.160.102; JSESSIONID=aaaLbl1vQGP7LaMN6GeFx; OUTFOX_SEARCH_USER_ID_NCOO=516404572.3890775; ___rl__test__cookies=1613910186738',
    'Host': 'fanyi.youdao.com',
    'Origin': 'http://fanyi.youdao.com',
    'Referer': 'http://fanyi.youdao.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.68',
    'X-Requested-With': 'XMLHttpRequest'
}

data = {
    'i': 'translate',
    'from': 'AUTO',
    'to': 'AUTO',
    'smartresult': 'dict',
    'client': 'fanyideskweb',
    'salt': '16139101867430',
    'sign': 'b5b2bfdc381df9c9b104a03f2244bdaf',
    'lts': '1613910186743',
    'bv': 'a68be4fa7a0372e7ae83662b495a6f4c',
    'doctype': 'json',
    'version': '2.1',
    'keyfrom': 'fanyi.web',
    'action': 'FY_BY_REALTlME'
}

req = requests.post(url,headers=headers,data=data)

print(req.text)

查看一下运行结果:

有道翻译——JS解密

emmm,刚才还是好好的,现在为什么就返回不到信息了呢?事实上,有道翻译是存在JS加密的,这是一个比较高级的反爬虫手段。其中的奥妙就在我们“看不懂”的那些参数里面,什么salt,sign,lts等等。所以下面要做的就是弄懂那些看不懂的数据。

JS加密破解

JS加密,顾名思义就是通过JavaScript脚本来实现加密。既然是JS加密,那必然存在js文件啊,我们可以在网页源代码里面找一找。

有道翻译——JS解密

网页源代码里面有三个js文件。我们猜测一下我们需要的js文件是第二个。访问http://shared.ydstatic.com/api/fanyi-web/assets/index.min.js看一看是不是真的是这个文件。

有道翻译——JS解密

好吧,这个页面被“丑化”过了,我们百度使用JS美化将它美化一下:

有道翻译——JS解密

现在的代码还是不太方便查看,我们不妨将它复制到Sublime Text中,点击右下角的plain text,选择JavaScript,这样代码就高亮了。

有道翻译——JS解密

好了,我们可以通过Ctrl+F发现salt,sign,lts这几个字符串都不在这个文件里面,所以这个文件大概率不是所需文件。

再查看一下第三个,使用同样的方法,真的在这个js文件里面找到了这几个字符串。所以这个文件正是我们所需要的,现在开始分析这个文件。

我们的出发点是破解salt,sign等几个键的含义,关注点也必然要放在js文件里面的这几个字符串上。不断通过Ctrl+F查找salt,会发现下面的代码:

有道翻译——JS解密

这个data的格式不就是我们POST请求是传入data的格式吗?我们分析一下这几个键值对的含义。

猜测含义
i 我们输入的待翻译内容
client 前面已得知是’fanyideskweb’
salt 未知
sign 未知
ts(lts) 未知
bv 未知
from 可以自行设置,默认为AUTO自动识别
to 可以自行设置,默认为AUTO自动识别
tgt 未知,但是请求时貌似不需要这个参数

因此,可以认为需要破解的参数就是salt,sign,ts,bv这四个,可能外加一个tgt,也正是这几个参数导致了爬虫的不成功。继续分析,会发现下面的代码:

有道翻译——JS解密

salt就是时间戳加上一个0~10之间的随机整数,ts是字符串格式的时间戳。但是现在不知道这个e是什么,所以无法确定sign。

继续分析bv和sign,会发现下面的代码:

有道翻译——JS解密

也就是说我们最终关注的内容将是generateSaltSign(n)函数,或者说是这个r。

这个r我们在上面的分析中看到过,在前一个图片中就有,当时我们不知道e是什么,现在对比一下我们得知了e就是输入的待翻译的内容,由此可以知道sign=md5("fanyideskweb" + 待翻译的内容 + salt的内容 + "Tbh5E8=q6U3EXe+&L[4c@")

最后只剩下bv了,bv=n.md5(navigator.appVersion),我们在控制台里面输入navigator.appVersio会发现其实它是我们所用浏览器的版本号。

有道翻译——JS解密

总结一下四个参数的内容:

参数 内容
salt 时间戳加上一个0~10之间的随机整数
ts(lts) 字符串格式的时间戳
sign md5(“fanyideskweb” + 待翻译的内容 + salt的内容 + “Tbh5E8=q6U3EXe+&L[4c@”)
bv md5(所用浏览器的版本号),这是固定的

对应的python代码:

import hashlib
import random
import time

i = input('请输入要翻译的内容:')
salt = str(time.time()*1000)   # JS中的时间戳是毫秒级的,time.time()的时间戳是秒级的
lts = salt + str(random.randint(0,10))
# hashlib.md5()里面传入的内容必须是bytes格式,所以默认为unicode格式的字符串要做一个转换
temp1 = "fanyideskweb" + i + salt + "Tbh5E8=q6U3EXe+&L[4c@"
sign = hashlib.md5(temp1.encode('utf-8')).hexdigest()
temp2 = "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.68"
bv = hashlib.md5(temp2.encode('utf-8')).hexdigest()

我们再运行下列代码试试:

import requests
import hashlib
import random
import time



url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"

headers = {
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
    'Connection': 'keep-alive',
    'Content-Length': '240',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'Cookie': 'OUTFOX_SEARCH_USER_ID=-1655056227@10.108.160.102; JSESSIONID=aaaLbl1vQGP7LaMN6GeFx; OUTFOX_SEARCH_USER_ID_NCOO=516404572.3890775; ___rl__test__cookies=1613910186738',
    'Host': 'fanyi.youdao.com',
    'Origin': 'http://fanyi.youdao.com',
    'Referer': 'http://fanyi.youdao.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.68',
    'X-Requested-With': 'XMLHttpRequest'
}

i = input('请输入要翻译的内容:')
salt = str(time.time()*1000)   # JS中的时间戳是毫秒级的,time.time()的时间戳是秒级的
lts = salt + str(random.randint(0,10))
# hashlib.md5()里面传入的内容必须是bytes格式,所以默认为unicode格式的字符串要做一个转换
temp1 = "fanyideskweb" + i + salt + "Tbh5E8=q6U3EXe+&L[4c@"
sign = hashlib.md5(temp1.encode('utf-8')).hexdigest()
temp2 = "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.68"
bv = hashlib.md5(temp2.encode('utf-8')).hexdigest()

data = {
    'i': i,
    'from': 'AUTO',
    'to': 'AUTO',
    'smartresult': 'dict',
    'client': 'fanyideskweb',
    'salt': salt,
    'sign': sign,
    'lts': lts,
    'bv': bv,
    'doctype': 'json',
    'version': '2.1',
    'keyfrom': 'fanyi.web',
    'action': 'FY_BY_REALTlME'
}

req = requests.post(url,headers=headers,data=data)

print(req.text)

运行结果:

有道翻译——JS解密

我们已经实现了JS破解,剩下的内容就是解析响应的内容了,这里就不再赘述,直接上全部代码:

#-*- coding = utf-8 -*-
# @Time: 2021/2/19 11:01 
# @Auther: 卫佳
# @File: 测试.py
# @Software: PyCharm

import requests
import hashlib
import random
import time



url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"

headers = {
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
    'Connection': 'keep-alive',
    'Content-Length': '240',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'Cookie': 'OUTFOX_SEARCH_USER_ID=-1655056227@10.108.160.102; JSESSIONID=aaaLbl1vQGP7LaMN6GeFx; OUTFOX_SEARCH_USER_ID_NCOO=516404572.3890775; ___rl__test__cookies=1613910186738',
    'Host': 'fanyi.youdao.com',
    'Origin': 'http://fanyi.youdao.com',
    'Referer': 'http://fanyi.youdao.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.68',
    'X-Requested-With': 'XMLHttpRequest'
}

i = input('请输入要翻译的内容:')
salt = str(time.time()*1000)   # JS中的时间戳是毫秒级的,time.time()的时间戳是秒级的
lts = salt + str(random.randint(0,10))
# hashlib.md5()里面传入的内容必须是bytes格式,所以默认为unicode格式的字符串要做一个转换
temp1 = "fanyideskweb" + i + salt + "Tbh5E8=q6U3EXe+&L[4c@"
sign = hashlib.md5(temp1.encode('utf-8')).hexdigest()
temp2 = "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.68"
bv = hashlib.md5(temp2.encode('utf-8')).hexdigest()

data = {
    'i': i,
    'from': 'zh-CHS',    # 设置成中文
    'to': 'en',        # 设置成英文
    'smartresult': 'dict',
    'client': 'fanyideskweb',
    'salt': salt,
    'sign': sign,
    'lts': lts,
    'bv': bv,
    'doctype': 'json',
    'version': '2.1',
    'keyfrom': 'fanyi.web',
    'action': 'FY_BY_REALTlME'
}

req = requests.post(url,headers=headers,data=data)
resp = req.json()
if "translateResult" in resp:
    translateResult = resp["translateResult"][0][0]["tgt"]
    print('translateResult = %s'%translateResult)
if "smartResult" in resp:
    print('smartResult = ', end='')
    smartResult = resp["smartResult"]["entries"]
    for i in smartResult:
        if i:
            r = i.strip('\n').strip('\r')
            print(r,end='\t')

测试几个结果:

有道翻译——JS解密
有道翻译——JS解密
有道翻译——JS解密

祝愿看到这里的人变得越来越强,再会~~~

上一篇:OpenCV 截取图像中某一区域的方法


下一篇:CCIE路由实验(2) -- BGP选路原则