以前,很多人学习Python爬虫的第一个爬虫就是爬的有道翻译,但是现在由于有道翻译进行了参数加密,增加了反爬机制,所以很多新手在使用以前的代码的时候经常会遇到{"errorCode":50}错误。这篇文章就来分析一下有道翻译的反爬机制,依然通过Python爬虫来爬有道翻译。
有道翻译的请求分析
首先,我们根据使用浏览器的F12开发者工具来查看一下有道翻译网页在我们进行翻译的时候都进行了什么请求操作。
请求链接
首先让我们来看一下在有道翻译输入要翻译的内容然后提交进行翻译之后发生了什么。
首先通过一张截图来看翻译加载的请求链接:
通过多次输入新的翻译内容,通过F12查看XHR中的异步加载的内容,可以看到每次都有一个新的请求产生,所以可以初步预测这个链接就是请求的链接。
继续查看请求的结果信息:
查看每个请求的结果内容,果然能看到翻译的结果,所以现在可以肯定这个链接就是有道翻译的请求地址了。
请求参数
已经确定了请求的链接,现在可以继续查看这个页面的请求参数,其中一般包括以下内容:
请求地址
请求方式(GET或者POST)
请求头headers参数
传递的参数data(如果是POST请求的话)
可以根据截图来看一下这个链接的这些参数,首先是请求参数:
然后是传递的参数data:
对比参数
可以通过多次重复提交要翻译的信息来查看每次参数的变化情况,最后能得到一个大概的结论,就是传递的data参数中除了需要的翻译的信息外,还有2个参数是会每次都变动的,它们就是salt和sign。
如果直接复制一次网页中的salt和sign,去使用Python请求链接,则会发现根本请求不到要翻译的结果,而是会得到如下的结果:
{"errorCode":50}
所以,我们大概能判断,这两个参数应该是有加密的,当然,salt其实一眼就能看出来跟时间戳有关,所以现在的重点是需要找到sign参数的获取方式。
分析sign参数获取方式
查看sign参数首先可以去网页的源代码中查看,然后会发现找不到这个参数,于是可以考虑它的生成方式应该在js中,所以可以去网页加载时候的js文件中查看这个参数,最后可以在 fanyi.min.js 这个文件中找到3个结果。
找到了参数生成的位置,现在就要分析js了,因为这个文件是处理过的js,直接看是难以看出逻辑的,所以可以把js代码放到一些可以重新排版的工具中再查看,最后可以看到sign的生成方式如下片段:
varn=b.val(),r=""+((newDate).getTime()+parseInt(10*Math.random(),10)),o=u.md5(S+n+r+D),a=n.length;if(L(),w.text(a),a>5e3){varl=n;n=l.substr(0,5e3),o=u.md5(S+n+r+D);varc=l.substr(5e3);c=(c=c.trim()).substr(0,3),u("#inputTargetError").text("有道翻译字数限制为5000字,“"+c+"”及其后面没有被翻译!").show(),w.addClass("fonts__overed")}elsew.removeClass("fonts__overed"),u("#inputTargetError").hide();f.isWeb(n)?i():s({i:n,from:_,to:C,smartresult:"dict",client:S,salt:r,sign:o,doctype:"json",version:"2.1",keyfrom:"fanyi.web",action:e||"FY_BY_DEFAULT",typoResult:!1
从上面的js代码中可以看到salt果然是时间戳,获取的方式是:
r = "" + ((new Date).getTime() + parseInt(10 * Math.random()
然后sign的获取方式是:
o = u.md5(S + n + r + D)
这个里面其实是有4个参数的,通过下面的data参数可以发现,S就是client参数,通过之前网页请求的时候发现这个是一个字符串fanyideskweb,n就是需要翻译的内容了,r是时间戳,D参数需要到js代码中继续找,最后会找到这样的一段:
D = "ebSeFb%=XZ%T[KZ)c(sy!"
也就是说,这个D也是一个固定的字符串。
现在上面4个参数都找到了,sign是这个4个参数的字符串拼接之后进行MD5加密的结果。到这里,有道翻译的爬虫需要的两个重要的参数的获取方式就分析结束了,现在可以着手将逻辑写成代码了。
Python爬虫代码
源码展示
import requests
import time
import random
import hashlib
ss = requests.Session()
# 合成盐
salt =int(time.time() *1000) + random.randint(1,9)
n =input('请输入您要翻译的内容')
# 合成sign
sign ='fanyideskweb' + n +str(salt) +"ebSeFb%=XZ%T[KZ)c(sy!"
mm = hashlib.md5()
mm.update(sign.encode('utf-8'))
sign = mm.hexdigest()
# post表单数据,通过分析只有salt和sign是变动的,通过js代码能够查到加密方法;
formdata = {
"i":n,
"from":"AUTO",
"to":"AUTO",
"smartresult":"dict",
"client":"fanyideskweb",
"salt":salt,
"sign":sign,
"doctype":"json",
"version":"2.1",
"keyfrom":"fanyi.web",
"action":"FY_BY_REALTIME",
"typoResult":"false"
}
# 通过分析需要提交这些头部信息,cookie的值每次都是变动的不一样的,但是服务器并不审查值的具体内容,只要有即可
headers = {
# 'Cookie': 'OUTFOX_SEARCH_USER_ID=-2022895048@10.168.8.76;',
"Cookie":"OUTFOX_SEARCH_USER_ID=1660767496@10.168.8.63; JSESSIONID=aaaWHiSuUEECp5jcWeZiw; OUTFOX_SEARCH_USER_ID_NCOO=1085528731.0395057; fanyi-ad-id=41685; fanyi-ad-closed=1; ___rl__test__cookies=%d" %int(time.time()*1000),
"Referer":"http://fanyi.youdao.com/",
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36"
}
# post地址
url ='http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
# post返回的数据是json格式的(可以在chrome-network中查看该请求的response),通过解析得到翻译结果,
response = ss.post(url,data=formdata,headers=headers).json()
# print(response)
# 打印翻译结果
print(response["translateResult"][0][0]["tgt"])
headers参数解读
通过一个参数一个参数的注释掉然后再去请求,可以发现我这个源代码里面注释掉的部分都是可以不用带上的,并且,在Cookie这个参数中,也只有OUTFOX_SEARCH_USER_ID这个参数是必要的,只要格式满足就可以使用随机的方式去生成这个参数,而其他的参数就可以不用了。
总结:有道翻译的这个Python其实算是一个非常常规的应对有反爬虫机制的网站的分析方法了,这个分析的关键地方其实在于对JS代码的理解,所以,要想爬虫技术提高,JS代码必须要看的懂才行,这个其实也是我正在学习的地方,共勉吧!
请求头中cookie传的值,是您反复测试的出来的吗? 经过测试:
"Cookie":"OUTFOX_SEARCH_USER_ID=1660767496@10.168.8.63;JSESSIONID=aaaWHiSuUEECp5jcWeZiw; OUTFOX_SEARCH_USER_ID_NCOO=1085528731.0395057; fanyi-ad-id=41685; fanyi-ad-closed=1;"
"Cookie":"OUTFOX_SEARCH_USER_ID=1660767496@10.168.8.63; JSESSIONID=aaaWHiSuUEECp5jcWeZiw; OUTFOX_SEARCH_USER_ID_NCOO=1085528731.0395057; fanyi-ad-id=41685; "
"Cookie":"OUTFOX_SEARCH_USER_ID=1660767496@10.168.8.63; JSESSIONID=aaaWHiSuUEECp5jcWeZiw; OUTFOX_SEARCH_USER_ID_NCOO=1085528731.0395057;"
"Cookie":"OUTFOX_SEARCH_USER_ID=1660767496@10.168.8.63; JSESSIONID=aaaWHiSuUEECp5jcWeZiw; "
"Cookie":"OUTFOX_SEARCH_USER_ID=1660767496@10.168.8.63; "
"Cookie":"OUTFOX_SEARCH_USER_ID=1660767496@10.168.8.63; JSESSIONID=aaaWHiSuUEECp5jcWeZiw; OUTFOX_SEARCH_USER_ID_NCOO=1085528731.0395057; fanyi-ad-id=41685; fanyi-ad-closed=1;rl_testcookies=%d" % int(time.time()*1000)
"Cookie":"OUTFOX_SEARCH_USER_ID=1660767496@10.168.8.63; JSESSIONID=aaaWHiSuUEECp5jcWeZiw; OUTFOX_SEARCH_USER_ID_NCOO=1085528731.0395057; fanyi-ad-id=41685; fanyi-ad-closed=1;rl_testcookies=%d" % 任意13位的unix时间戳
这些都是可以的........................
服务器似乎对于cookie具体的值并不关心,但是cookie这个头信息是不能少的;