查看原文
其他

另辟蹊径js逆向爬取百度翻译

爬虫俱乐部 Stata and Python数据分析 2022-03-15

本文作者:张延丰(投稿)

文字编辑:孙晓玲

技术总编:张   邯

1.技术分析

百度翻译大家都用过,左边输入框,右边呈现结果,在翻译的过程中网页也没有重新加载,因此我们可以判定其使用的AJAX。在通过在xhr数据栏里寻找其请求的url地址,发现为POST请求,且携带一个唯一动态sign参数,经过多次试验,不同的词汇有固定的sign参数,但我们不知道。因此我们要做的是,找到sign值的js文件,自行定义sign值就可以任意发起请求。

2.爬取思路——寻找sign函数

我们整理一下解析的思路,其实通过输入固定的sign值也可以爬取当前页面的内容,但是我们不能爬取任意的网页内容,因此我们需要知道如何寻找登录的接口,知道如何确定js文件的位置,知道如何观察js的执行过程及方法,进而调出js代码来破解sign值。

网上有很多用JavaScript逆向爬取百度翻译的例子,概括一下通常包含两种方法:第一种是直接通过搜索请求url里的参数v2transapi来定位函数位置,乍一看挺突兀的,为什么会选择它来定位函数呢,后来想了一下,觉得可能是在写js脚本的时候会在构建url地址时,要将该参数写出来,因此可以通过其来定位函数位置。


第二种是通过搜索sign来定位函数,原因与上述一样。但是这种情况,往往是个例,在使用JavaScript逆向爬取中国空气质量在线监测平台的数据时,上述两种方法就完全失效了。首先,搜索url地址里的特殊参数,只有一个文件且并没有反馈出有用的信息,另外在通过其url地址中的唯一可变参数d来定位,那就更是大海捞针了。

经过之前的分析我们知道,我们捕获到的AJAX请求是通过点击了页面中输入词汇后的查询按钮后(其实一输入就可以呈现翻译,但我们必须得点击“翻译”,进而分析操作)触发的,也就是说该查询按钮上一定绑定了某个点击事件且触发了对应AJAX请求发送的事件。

那么接下来我们可以通过火狐浏览器(火狐浏览器可以分析页面某个元素的绑定事件以及定位到具体的代码在哪一行,Chrome也可以)去检测该查询按钮上到底绑定了哪些事件。

首先,我们通过在html定位翻译键发现,的确绑定有事件event,点击event发现有两个click事件:第一个显示为jQuery,因此我们通过分析第一个click事件的函数fuction()来解析翻译键的作用。点击jQuery左边的在调试器中打开并点击左下角的{},将页面调整为规范格式,得到以下页面:

定位到了,click事件的函数位置后,我们接下来分析函数function(){returne.apply(t || this, r.concat(it.call(arguments)))}。从函数中可以知道是返回,向对象e输入(t || this, r.concat(it.call(arguments))参数后的值,而apply.()中传入的第一个参数为新建对象(类似Python中,e为建立的类,第一个参数为对象),第二个参数为输入的值,即执行的是e(r)函数。因此我们在当前页面搜索function e,但并没有结果。

因此跳转network页面,全局搜索function e(在火狐中不知道原因打不了断点,因此以下操作在Chrome中执行),发现只有两处有e(r)函数,通过在第一个e(r)函数打断点发现其作用类似储存输入过的值,接下来,我们单击第二个e(r)的文件位置,并在函数中打断点,得到以下页面:

发现r为输入解释的内容,在标红的for行后面输出了p值630227,与我们在第一张图中的请求参数sign中数字的整数值一致,并且在最后一行标红内容中呈现为(A+‘.’+B)形式,判断为sign的浮点数形式,因此该段函数就是用于解析sign值的函数。

3.代码实现

接下来我们将该函数function e(r)写入Pycharm中并命名为code.js,并另在一个Python文件写入运行函数。

本程序需要使用execjs库,请未安装的读者首先pip install PyExecJS。
def sign_number(query): word = query with open('code.js', 'r', encoding='utf-8') as f: p = execjs.compile(f.read())#通过安装pyexecjs,输入execjs将JavaScript转换为Pyhon语言读取 sign = p.call('e', query)#调用e对象中的query值    return sign

发现输出结果显示,未定义i的值,多次运行不同的query发现i值固定为320305.131321201,因此直接定义变量var i = "320305.131321201"。再次运行函数呈现错误,未定义对象即r值,从上面截取r的函数录入code.js代码中,结果呈现正常。

Code.js代码如下:

function n(r, o) { for (var t = 0; t < o.length - 2; t += 3) { var a = o.charAt(t + 2); a = a >= "a" ? a.charCodeAt(0) - 87 : Number(a), a = "+" === o.charAt(t + 1) ? r >>> a : r << a, r = "+" === o.charAt(t) ? r + a & 4294967295 : r ^ a } return r }
var i = "320305.131321201"function e(r) { var o = r.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g); if (null === o) { var t = r.length; t > 30 && (r = "" + r.substr(0, 10) + r.substr(Math.floor(t / 2) - 5, 10) + r.substr(-10, 10)) } else { for (var e = r.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), C = 0, h = e.length, f = []; h > C; C++) "" !== e[C] && f.push.apply(f, a(e[C].split(""))), C !== h - 1 && f.push(o[C]); var g = f.length; g > 30 && (r = f.slice(0, 10).join("") + f.slice(Math.floor(g / 2) - 5, Math.floor(g / 2) + 5).join("") + f.slice(-10).join("")) } var u = void 0 , l = "" + String.fromCharCode(103) + String.fromCharCode(116) + String.fromCharCode(107); u = null !== i ? i : (i = window[l] || "") || "";
for (var d = u.split("."), m = Number(d[0]) || 0, s = Number(d[1]) || 0, S = [], c = 0, v = 0; v < r.length; v++) { var A = r.charCodeAt(v); 128 > A ? S[c++] = A : (2048 > A ? S[c++] = A >> 6 | 192 : (55296 === (64512 & A) && v + 1 < r.length && 56320 === (64512 & r.charCodeAt(v + 1)) ? (A = 65536 + ((1023 & A) << 10) + (1023 & r.charCodeAt(++v)), S[c++] = A >> 18 | 240, S[c++] = A >> 12 & 63 | 128) : S[c++] = A >> 12 | 224, S[c++] = A >> 6 & 63 | 128), S[c++] = 63 & A | 128) } for (var p = m, F = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(97) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(54)), D = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(51) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(98)) + ("" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(102)), b = 0; b < S.length; b++) p += S[b], p = n(p, F); return p = n(p, D), p ^= s, 0 > p && (p = (2147483647 & p) + 2147483648), p %= 1e6, p.toString() + "." + (p ^ m) }

接下来就正常的录入请求头,url,发起请求并呈现响应即可。全部代码如下:

import execjsimport requestsimport jsondef sign_number(query):#输入查询的词 word = query with open('code.js', 'r', encoding='utf-8') as f: p = execjs.compile(f.read())#通过安装pyexecjs,输入execjs将JavaScript转换为pyhon语言读取 sign = p.call('e', query)#调用e对象中的query值 return sign
def get_data(sign,query): data={ 'from': 'zh',#此模式为汉译英 'to': 'en', 'query': query, 'transtype': 'realtime', 'simple_means_flag': '3', 'sign': sign, 'token': '380c27bbe719bcb775945a4125b72721', 'domain': 'common' } headers = { "Host": "fanyi.baidu.com", "Connection": "keep-alive", "Content-Length": "149", "Accept": "*/*", "Sec-Fetch-Dest": "empty", "X-Requested-With": "XMLHttpRequest", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "https://fanyi.baidu.com", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Referer": "https://fanyi.baidu.com/", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en,zh-CN;q=0.9,zh;q=0.8", "Cookie": "BAIDUID=7E736869D29BDC0C480CFEF238B9E926:FG=1; PSTM=1535272183; BIDUPSID=AC2D3D05590936929DB7C1EBA13B7E50; H_WISE_SIDS=130610_126886_131439_132656_133470_114744_130510_132689_128143_133737_120188_133016_132910_133043_131246_132440_130763_132393_132378_132326_132212_131517_132260_118892_118862_118843_118821_118800_131650_131577_132841_131533_131529_132604_107315_133158_132590_132781_130121_133569_132708_133116_133351_132565_133479_133303_129647_124893_131862_132557_132540_133289_133473_131905_128891_132294_132552_132674_133387_129643_131423_132428_133414_133586_110085_127969_123290_133663_127417_131751_133179; BDUSS=GhMT0J0M2dKQkhnNTlKMU5kN0w1cVllVWNWekl0NFhRcGdNWnI5SFI3R1lSR2RlRVFBQUFBJCQAAAAAAAAAAAEAAABvg7MKbGl1eGluZ195dW5sdW8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJi3P16Ytz9eO; FANYI_WORD_SWITCH=1; REALTIME_TRANS_SWITCH=1; HISTORY_SWITCH=1; SOUND_PREFER_SWITCH=1; SOUND_SPD_SWITCH=1; Hm_lvt_afd111fa62852d1f37001d1f980b6800=1586486564; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1586486405,1586491138,1586502459; yjs_js_security_passport=93d819885978ce723898bf3af33b41f3455ce421_1586502457_js; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1586502474; __yjsv5_shitong=1.0_7_a3aac793117cc987de437de4ad9a7be2c043_300_1586502471488_111.227.124.90_8251440a"}    url = "https://fanyi.baidu.com/v2transapi?from=zh&to=en" response = requests.post(url, headers = headers, data = data).text return json.loads(response)['trans_result']['data'][0]['dst']    if __name__ == "__main__": query = input("请输入要查询的单词:")    print(get_data(sign_number(query),query))






对我们的推文累计打赏超过1000元,我们即可给您开具发票,发票类别为“咨询费”。用心做事,不负您的支持!
往期推文推荐
可迭代对象、迭代器、生成器傻傻分不清楚
Ftools命令组之fegen命令介绍
提升效率的利器——如何用labvarch批量修改变量标签
二进制序列类型——bytes()、bytearray()
今日头条海外疫情数据爬取
anythingtodate带你轻松处理日期
_variables介绍
查询12306车次信息
海外疫情仍严峻,劝君更在家中留
国外疫情怎么看?——实时新闻获真知

基于贝叶斯定理的算法——朴素贝叶斯分类

中国知网爬虫(CNKI) 批量下载PDF格式论文
sencode命令介绍
Ftools命令组之fisid命令和fsort命令介绍
“抽丝剥茧”,层层下分——机器学习基本算法之决策树

爬取东方财富网当日股票交易情况

stata调用python爬取时间数据——借他山之石以攻玉

关于我们



微信公众号“Stata and Python数据分析”分享实用的stata、python等软件的数据处理知识,欢迎转载、打赏。我们是由李春涛教授领导下的研究生及本科生组成的大数据处理和分析团队。

此外,欢迎大家踊跃投稿,介绍一些关于stata和python的数据处理和分析技巧。
投稿邮箱:statatraining@163.com
投稿要求:
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿+推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到有关数据处理、分析等问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存