使用 Python 执行 JavaScript

案例引入

网站: https://spa7.scrape.center

这里是一个简单的 NBA 球星的网站, 用卡片形式展示了一些球星的基本信息。另外,每张卡片其实都有一个加密字符串,这个加密字符串其实和球星的信息是由关联的, 并且每个球星的加密字符串也是不同的。

所以,这里我们要做的就是找出这个加密字符串的加密算法并且用程序把加密字符串的生成过程模拟出来

准备工作

这里我们要使用 Python 来执行 JS , 需要用的库叫作 PyExecJS

安装: pip install pyexecjs

PyExecJS 是用于执行 JS 的, 但执行 JS 的功能需要依赖于 JS 的运行环境, 所以除了安装这个库之外,还需要一个 JS 的环境, 例如 Node.js , 具体安装方式请查阅资料

都安装好之后检测环境

复制代码
import execjs

print(execjs.get().name)

Node.js (V8)

如果你成功安装了,那么对应的输出就一样,如果是其他环境,那么就找对应的测试方法

分析

我们在这里很快就可以找到加密字符串的生成逻辑(因为这里主要说的是在 Python 执行 JS)

这里首先声明了 球员的列表, 然后对每个球员调用加密算法对其进行信息加密,这里加入断点看一下,其实 getToken 方法的输入就是单个球员的信息, 就是上面列表的每个元素信息,this.key 就是一个固定的字符串。整个加密逻辑就是提取球员的各项信息,先进行 Base64 编码,然后进行 DES 加密,最后返回结果

加密算法怎么实现的呢 ? 其实是依赖了 crypto-js 库, 使用 CryptoJS 对象来实现的。

那么 , CryptoJS 这个对象哪里来的呢? 其实这个网站直接引用 crypto-js 库

执行 crypto-js 库对应的这个 JS 文件之后 , CryptoJS 就会被注入浏览器全局环境下,一次我们就可以在别的方法里直接使用 CryptoJS 对象里的方法了

模拟调用

首先我们要模拟的其实就是 getToken 方法, 输入球员相关信息,得到最终的加密字符串。这里我们直接把 key 替换下, 把 getToken 方法稍微改写一下

复制代码
function getToken(player) {
    let key = CryptoJS.enc.Utf8.parse("fipFfVsZsTda94hJNKJfLoaqyqMZFFimwLt");
    const {name, birthday, height, weight} = player;
    let base64Name = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(name));
    let encrypted = CryptoJS.DES.encrypt(
        `${base64Name}${birthday}${height}${weight}`,
        key,
        {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7,
        }
    );
    return encrypted.toString();
}

因为这个方法的模拟执行需要 CryptoJS 对象, 如果我们直接调用这个方法,肯定会报 CryptoJS 未定义的错误

我们只需要再模拟执行一下刚才看到的 crypto-js.min.js 就好了

因此我们需要模拟执行的内容就是一下两部分

  1. 模拟运行 crypto-js.min.js 里面的 JavaScript 。 用于声明 CryptoJS 对象

  2. 模拟运行 getToken 方法的定义,用于声明 getToken 方法

接着我们就把 crypto-js.min.js 里面的代码和 上面的 getToken 方法的代码复制一下,都粘贴到一个 JS 文件里面, 比如叫作 crypto.js

接下来,我们就用 PyExecJS 模拟执行一下

复制代码
import execjs
import json

item = {
    'name': '凯文-杜兰特',
    'image': 'durant.png',
    'birthday': '1988-09-29',
    'height': '208cm',
    'weight': '108.9KG'
}
file = 'crypto.js'
node = execjs.get()
ctx = node.compile(open(file).read())

js = f"getToken({json.dumps(item, ensure_ascii=False)})"
print(js)
result = ctx.eval(js)
print(result)

这里单独定义了一个球员的信息, 并将其赋值 item 变量。然后使用execjs 的 get 方法获取 JS 执行环境, 赋值为 node

接着我们调用 node 的 compile 方法, 这里给它传入刚才定义的 crypto.js 文件的文本内容。 compile 方法会返回一个 JS 的上下文对象, 我们将其赋给 ctx 。 执行到这里,其实就可以理解为, ctx 对象里面就执行过了 crypto-js.min.js , CryptoJS 就声明好了,然后紧接着 getToken 方法的声明代码也被执行,所以 getToken 方法也定义好了, 相当于完成了一些初始化工作

接着我们只需要定义我们想要执行的 JS 代码。 我们定义了 js 变量,其实就是模拟调用了 getToken 方法并传入了球员信息。 并打印出了 球员信息

接着,调用 ctx 对象的 eval 方法并传入了 js 变量, 其实就是模拟执行这句代码, 照理说最终返回的就是加密字符串了,但是却报错了

这里说 CryptoJs is not defined

问题其实出现在 crypto-js.min.js ,可以看其中声明了一个 JS 的自执行方法

复制代码
!function(t, e) {
    "object" == typeof exports ? module.exports = exports = e() : "function" == typeof define && define.amd ? define([], e) : t.CryptoJS = e()
}(this, function() 

自执行方法: 就是声明了一个方法,然后接着调用执行。 格式:

!(function(a,b){ console.log('result', a, b)})(1, 2)

这里我们先声明了一个 function , 它接收 a 和 b 两个参数,然后把内容输出出来,接着我们把这个 function 用小括号括起来。 这其实就是一个方法, 可以被直接调用,怎么调用呢? 后面在跟上对应的参数就好了, 比如这里传入 1, 2 执行结果就是

result 1 2

同理,crypto-js.min.js 也符合这个格式,它接收 t, e 两个参数, t 就是 this , 其实就是浏览器中的 widow 对象, e 就是一个 function (用于定义 CryptoJS 和核心内容)

我们再来看下 crypto-js.min.js 开头的定义

"object" == typeof exports

? module.exports = exports = e()

: "function" == typeof define && define.amd

? define([], e)

: t.CryptoJS = e() }(this, function()

在 Node.js 中, 其实 exports 用来就一些对象的定义导出, 这里 " object " == typeof exports 的结果其实就是 true , 所以执行了 module.exports = exports = e() 这段代码, 这相当于把 e() 作为整体导出, 而这个 e()其实就是对应后面整个 function 里面定义了加密相关的各个实现,其实就是代指整个加密算法库

但是在在浏览器中,其结果就不一样了, 浏览器环境中并没有 exorts 和 define 这两个对象,所以,上述代码在浏览器中最后执行的就是 t.CryptoJS = e() 这段代码, 其实这里就是把 CryptoJS 对象挂在到了 this 上面, 而 this 就是浏览器中的全局 window 对选哪个, 后面就可以直接用了。如果我们把这段代码放在浏览器中,没有任何问题

然而,使用的 PyExecJS 是依赖于一个 Node.js 执行环境的, 所以上述代码其实执行的是 module.exports = exports = e() , 这里面饼没有声明 CryptoJS 对象,也没有把 CryptoJS 挂在到全局对象里面, 所以就报错了

我们在这里只需要声明一个 CryptoJS 变量, 然后手动声明一下它的初始化就好了

复制代码
!function(t, e) {
    CryptoJS = e();
    "object" == typeof exports ? module.exports = exports = e() : "function" == typeof define && define.amd ? define([], e) : t.CryptoJS = e()
}(this, function()

我们就加了 CryptoJS = e(); 然后重新运行

DG1uMMq1M7OeHhds71HlSMHOoI2tFpWCB4ApP00cVFqptmlFKjFu9RluHo2w3mUw

得到了加密 字符串

相关推荐
2401_857439692 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna2 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
Dream_Snowar4 小时前
速通Python 第三节
开发语言·python
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
信号处理学渣5 小时前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客5 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
jasmine s5 小时前
Pandas
开发语言·python