1 引言
数月前写过某网站(请原谅个人掩耳盗铃)的爬虫,这两天须要从新采集一次,用的是scrapy-redis框架,本觉得二次爬取能够轻松完成的,可没想到爬虫启动没几秒,出现了大堆的重试提示,内心顿时就咯噔一下,清闲时光估计要结束了。
仔细分析后,发现是获取店铺列表的请求出现问题,经过浏览器抓包,发现请求头参数中相比以前多了一个X-Shard和x-uab参数,以下图所示:
X-Shard
却是没什么问题,一看就是兴趣点的经纬度,但x-uab看过以后就让人内心苦了,js加密啊,只能去逆向解密了。
2 js逆向求解
最直接的思路是根据“x-uab”关键字在全部关键中查找(chrome浏览器-source中按ctrl + shift + F快捷键),结果以下所示:
接下来,打个断点调试一下:在数字那里点一下,数字位置出现蓝点,表示添加断点成功,而后刷新获取店铺列表的页面,程序会在断点处停下。以下所示:
在控制台调试o.getUA()函数,看一下输出:
果真是,证实猜想没错,就是这个o.getUA()函数负责生成请求头中的x-uab参数。
继续向下查看这个getUA()函数的引用(把光标放在要查看的函数上,就能够查看这个函数的引用),就是下图这个函数:
图中的s就是咱们要的x-uab参数,下图在控制台输出能够证实:
因此,u-xab是这里的e生成的,而函数e传入的参数中,第一个是常量2,第二个参数a是undefined,呵,看起来没有传其它参数。继续向下找这个e(2,a)函数:
就是这个function e(r, i, n, h, p) 方法,直接运行能够获取加密后的参数。把这个function e(r, i, n, h, p) 方法所有代码取出来,另存为一个js文件。
3 撸代码
3.1 方案一
你觉得上面找出生成x-uab的js代码,就大功告成了吗?少年,you are too young too simple!
怎么把这段js脚本运行起来,才是关(nan)键(dian)。
这个function e(r, i, n, h, p) 函数有近4万行代码,从新用Python实现难(jiu)度(shi)有(bu)点(ke)大(neng)。因此,我选择直接用Python来执行这段js脚本。
怎么用python执行js脚本,度娘会给你一堆资料,本身查吧。我这里选择的是execjs。
由于在上面复制出来的脚本中,只单单定义了一个e(r, i, n, h, p)方法,并无调用这个方法,因此,我要要在js文件的末尾添加一些代码来调用:
function getParam() {
var a;
var param = e(2,a);
return param
};
而后,开始撸Python代码吧:
import execjs
node = execjs.get()
file = 'eleme.js'
ctx = node.compile(open(file).read())
js_encode = 'getParam()'
params = ctx.eval(js_encode)
print(params)
尝试执行,心凉,代码异常:
execjs._exceptions.ProgramError: TypeError: 'window' 未定义
window
对象估计是浏览器打开是建立的,蕴含浏览器的信息,因此用Python来执行这段代码时,没有这个对西乡。原本想尝试伪造window对象,但查找以后发现js脚本中上百个地方用到window,这还没完,代码通过混淆,在下水平不够,无法追根溯源(这地方困扰了我许久,哪位前辈若是知道方法,请告知)。
后来,从一个前辈那里(感谢前辈)获知一个方法绕过去。这个前辈的方法是将execjs的引擎换成PhantomJS这个无头浏览器(以前用的引擎是node.js),换句话说就是用PhantomJS来执行js脚本,PhantomJS是一个浏览器,天然就会建立window对象。
使用PhantomJS以前,须要下载它的驱动,而后放下Python代码统一目录下。对以前的Python代码也进行修改:
import execjs
import os
os.environ["EXECJS_RUNTIME"] = "PhantomJS"
node = execjs.get()
file = 'eleme.js'
ctx = node.compile(open(file).read())
js_encode = 'getParam()'
params = ctx.eval(js_encode)
print(params)
果真,按照这个方法,成功获取加密字符串。
3.2 方案二
事实上,这个方案二才是我在出现未定义window对象异常后首先尝试的方法,不过由于往js代码中添加的js脚本有问题,觉得行不通,因此请教前辈,获得了方案一。
方案二的思路和方案一相似,不过更加粗暴一些。不是由于没在浏览器执行,形成没有window对象吗?那我就模拟浏览器来执行。
在执行以前,一样要修改js脚本,在js文件末尾调用e方法,添加以下代码:
var a;
var param = e(2,a);
return param;
切记:不要放在任何函数里面,我以前就是由于将这段代码放在函数里头强制执行,致使的结果就是在浏览器里能够获取加密字符串,可是在Python中获取到的倒是None。
模拟浏览器用的selenium和chrome的webDriver,代码以下:
from selenium import webdriver
browser = webdriver.Chrome(executable_path='chromedriver.exe')
with open('eleme.js', 'r') as f:
js = f.read()
print(browser.execute_script(js))
这个方法也是能够得到加密以后的字符串。
最后,有必要说一下的是,若是须要获取大量的x-uab,采用方案二效率会高一下,由于采用方案二的话,能够自打开一个浏览器(都调用一个webdriver对象),而后快速执行js,返回加密字符串。
4 总结
一次js逆向解密,算是完成了吧。可是也留下了一些问题:
(1)使用chrome断点调试时,js脚本都是压缩混淆以后的,经过chrome的pretty print功能(也就是说那对花括号)能够格式美化,可是,有的时候却会失败,就像下图,格式化后,仍是一团糟:
这个问题耽搁了我很长时间,无法调试啊!
(2)在下js基础不行,很困惑为何运行时,先经过o.getUA()调用e函数内的嵌套函数,而后e函数内部嵌套函数中调用e方法自己,这是什么操做?函数调用不都应该先外层函数,而后再调用嵌套函数吗?
(3)若是不适用浏览器执行js的方法,就只能替换window对象,这该如何操做?
(4)这个e函数有近4万行,一个加密函数这么多代码,我可不信,里面确定不少事混淆视听用的,但我尝试调试追踪过,只能说混淆以后让我无从追踪,头晕。怎么才能简化这段脚本呢?
若是哪位前辈能够解惑,请必定告知,不胜感激!拜谢!