在使用爬虫中,经常会遇到网页请求数据是经过 JS 处理的,特别是模拟登录时可能有加密请求。而目前绝大部分前端 JS 代码都是经过混淆的,可读性极低,想理解代码逻辑需要花费大量时间。这时不要着急使用 Selenium 暴力解决,毕竟 Selenium 严重拖慢爬虫效率,我们可以尝试使用一些第三方库,来直接执行前端 JS 代码得到处理过后的结果。
1、PyExecJS
PyExecJS的优点是您不需要照顾JavaScript环境。特别是,它可以在Windows环境中运行,而无需安装额外的库。PyExecJS的缺点之一是性能。PyExecJS通过文本传达JavaScript运行时,并且运行缓慢。另一个缺点是它不完全支持运行时特定的功能。对于某些用例,PyV8可能是更好的选择。安装之前先安装JS环境,Node.js安装好了,就可以安装接下来的库了。
安装:
pip3 install -i https://pypi.douban.com/simple PyExecJS
import execjs
execjs.get().name # 查看调用的环境 'Node.js (V8)'
ctx = execjs.compile("""
function add(x, y) {
return x + y;
}
""")
print(ctx.call("add", 1, 2)) #3
with open("enc.js", "r", encoding="utf-8") as f: #执行js文件
js = execjs.compile(f.read())
url = js.call("posturl")
print(url) #http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=2019115032868
enc.js:
function posturl() {
var s = new Date;
url = "http://www.renren.com/ajaxLogin/login?1=1" + "&uniqueTimestamp=" + s.getFullYear() + s.getMonth() + s.getDay() + s.getHours() + s.getSeconds() + s.getUTCMilliseconds();
return url
}
2、PyV8
PyV8是Google V8引擎的Python包装器,它充当Python和JavaScript之间的桥梁?对象,并支持使用python脚本托管Google的v8引擎。和 `PyExecJS` 相比,这个库很轻量,不需要额外装 JS环境,因为 V8 本身就是环境,同时也因为不需要启动外部环境,执行速度很快。Python3 安装不要使用pip,因为官方只支持 Python2,需要在这里下载对应系统的二进制文件, 然后解压后将 PyV8.py 与 _PyV8.so (如so不是这个名字需要改成这样) 两文件复制到 Python 的 site-packages目录下,如 /usr/local/lib/python3.6/site-packages
。
下载地址:
https://github.com/emmetio/pyv8-binaries
import PyV8 # 注意大小写
with PyV8.JSContext() as ctx:
ctx.eval("""
function add(x, y) {
return x + y;
}
""")
print(ctx.locals.add(1, 2)) #3
3、Js2Py
将JavaScript转换为Python代码。Js2Py几乎可以翻译和执行任何JavaScript代码。Js2Py用纯python编写,没有任何依赖关系。基本上是纯Python中JavaScript核心的实现。这种方式可以摆脱调用 JS 环境的瓶颈,但遗憾的是如果用于很长的混淆 JS 代码,转译过来的大概率会报错… 所以只建议先尝试一下,如果报错及时更换上面的库。
安装:
pip3 install -i https://pypi.douban.com/simple js2py
import js2py
add = js2py.eval_js("""
function add(x, y) {
return x + y;
}
""")
print(add) # 可以看到大括号里已被转译 #'function add(x, y) { [python code] }'
print(add(1, 2)) #3
import js2py
# 使用下边这个方法可以输出转译后的代码
# 可以保存到文件里,下次不需要再次转译
print(js2py.translate_js('var x = 1'))
#打印结果:
"""
from js2py.pyjs import *
# setting scope
var = Scope( JS_BUILTINS )
set_global_object(var)
# Code follows:
var.registers(['x'])
var.put('x', Js(1.0))
"""
实战技巧:
接下来就是定位目标网页需要调用的 JS 函数,每个网站的写法都大不相同。只要通过 Debug,查看数据从请求,每一步都经过哪些 JS 函数,输出什么样的数据,就可以顺藤摸瓜找到一些可疑的函数,然后将这些方法逐一复制出来,层层去除多余代码,通过上面的库传入参数执行,看是否和目标网页处理后的数据一致,就可以找到目标函数。