动态网页爬虫一般可分为两种:Selenium爬取和接口爬取。两种方式各有优缺点:前者虽然可以很好地处理网页异步加载问题,但面对大型爬虫任务时,效率还是比较低的;后者虽然爬取速度较快,但请求参数很可能是动态变化的,这时就需要利用一些前端的知识,重新构造参数,整个过程通常称为JS逆向。
今天,我们就来分享一个JS逆向的基础案例——有道翻译参数破解
一、分析Post请求参数
爬取网址为有道翻译,利用谷歌浏览器抓包后,找到翻译响应如下:
注意:响应返回的是post请求,不是以往我们接触的get请求。此时,直接将URL复制到浏览器去访问,是无法获取数据的,如下图所示:
此时,我们可以通过requests
模块中post
方法来传入data
参数(通常是以字典形式),以此来正常访问页面。
requests.post(url,headers=header,data=from_data #data为需要传入的参数)
那需要传哪些参数呢?我们可以在Headers
下的From data
中找到,如下图所示:
通过比对不同翻译的响应,可知动态参数为i、salt、sign、lts
,其中参数i
是我们需要翻译的词。因此,我们可以初步构建from_data字典如下:
from_data={
'i': word,
'from': 'AUTO',
'to': 'AUTO',
'smartresult': 'dict',
'client': 'fanyideskweb',
'salt':salt,
'sign':sign,
'lts':lts,
'bv':'4b1009b506fa4405f21e207abc4459fd',
'doctype': 'json',
'version': '2.1',
'keyfrom': 'fanyi.web',
'action': 'FY_BY_CLICKBUTTION',
}
接下来就是寻找salt、sign、lts
是如何构成的,然后传入上述字典即可。
二、分析参数来源
首先,我们打开搜索栏,尝试搜索salt参数(当然其他两个也是可以的),基本步骤如下
点击进入JS文件后,可按如下方式均可美化代码,便于后续分析代码。
接下来,按照同样的方法,对参数salt
进行搜索,结果共12个。挨个慢慢找哪些是能构成salt
参数的,最终结果如下:
关于navigator.appVersion
、(new Date).getTime()
等这些参数的含义,其实是与javascript相关的语句,通过百度即可了解。不过我们也可以通过console控制台初步了解。点击console控制台,输入console.log(navigator.appVersion)
即可得到其结果,如下图所示:
从上述结果,我们可知道navigator.appVersion
与用户代理有关(不知道也没事,反正是个常量),(new Date).getTime()
与时间戳非常接近,通过比较时间戳后可知它是当前时间戳乘以1000取整构成的,parseInt(10*Math.random(),10)
是生成0-10的随机数。当然,对于sign
值还有个e
,我们还不了解,这里可以打断点进行分析,如下图所示(断点处不一定非要8376行,在其之后也可以)
打完断点,点击翻译,即可得到各个参数返回的值。这里,我们整理下各参数值的含义
参数 | 含义 |
---|---|
e | 需要翻译的词 |
t | 对navigator.appVersion 进行md5加密 |
r | 当前时间戳*1000取整 |
i | r值+0-10的随机数 |
ts | r值 |
bv | t值 |
salt | i值 |
sign | 对"fanyideskweb" + e + i + "Tbh5E8=q6U3EXe+&L[4c@" 进行md5加密 |
三、Python重新构造参数
经过以上分析,我们只要利用Python将上述参数重构即可啦,该部分代码如下:
- ts值
def get_ts():
ts=int(time.time()*1000)
return ts
- salt值
def get_salt(ts):
num = int(random.random()*10)
salt = str(ts) + str(num)
return salt
- sign值
def get_sign():
left = 'fanyideskweb'
e= 'stop' #翻译的词
i = get_salt(get_ts())
right = 'Tbh5E8=q6U3EXe+&L[4c@'
str_data = left + e + i + right
m=hashlib.md5()
m.update(str_data.encode('utf-8'))
sign=m.hexdigest()
return sign
全部代码
以类进行封装如下(注意,headers要带上cookie)
import requests
import time
import random
import hashlib
class youdao:
def __init__(self, url, header, word):
self.url = url
self.header = header
self.word = word
def get_ts(self):
self.ts = int(time.time()*1000)
return self.ts
def get_salt(self, ts):
self.num = int(random.random()*10)
self.salt = str(ts) + str(self.num)
return self.salt
def get_sign(self, salt):
left = 'fanyideskweb'
e = self.word # 翻译的词,图中的e
i = salt
right = 'Tbh5E8=q6U3EXe+&L[4c@'
str_data = left + e + i + right
m = hashlib.md5()
m.update(str_data.encode('utf-8'))
sign = m.hexdigest()
return sign
def get_from_data(self):
ts = self.get_ts()
salt = self.get_salt(ts)
from_data = {
'i': self.word,
'from': 'AUTO',
'to': 'AUTO',
'smartresult': 'dict',
'client': 'fanyideskweb',
'salt': salt,
'sign': self.get_sign(salt),
'lts': ts,
'bv': '4abf2733c66fbf953861095a23a839a8',
'doctype': 'json',
'version': '2.1',
'keyfrom': 'fanyi.web',
'action': 'FY_BY_CLICKBUTTION',
}
return from_data
if __name__ == '__main__':
url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36',
'Referer': 'http://fanyi.youdao.com/',
'Cookie': 'OUTFOX_SEARCH_USER_ID_NCOO=920530045.7161942; OUTFOX_SEARCH_USER_ID="1506261127@10.108.160.17"; _ga=GA1.2.1288125049.1599879702; _ntes_nnid=7dc5dc11be36895d173bef7fe1b51cb1,1606305443534; JSESSIONID=aaaAC5o5FTuNsgXAdm7Dx; ___rl__test__cookies=1612686603098'
}
word = 'enthusiastic'
fanyi = youdao(url, header, word)
r = requests.post(url, headers=header, data=fanyi.get_from_data())
data = r.json()
print("输入单词:{},翻译为{}".format(
data['translateResult'][0][0]['src'], data['translateResult'][0][0]['tgt']))
以上就是本次分享的全部内容~