浏览器环境下 JS 的模拟执行

分析

在某些情况下,我们需要某个变量的值,来作为参数构造请求。而生成这个变量的方法又很复杂,如果去对生成方法追根溯源太麻烦。所以我们需要用浏览器作为环境,直接拿到变量的值即可

案例

网站:https:/spa2.scrape.center

Ajax 请求中携带了一个 token, 每次刷新,token 的值都会随机改变

添加 XHR 断点并通过调用栈找到 token 生成入口

可以发现参数的  token 就是变量 e ,它的生成过程

 var a = (this.page - 1) * this.limit
                      , e = Object(i["a"])(this.$store.state.url.index, a);

此处添加断点调试一下,看看具体变量的值

进过对比发现, 变量 a 其实就是 offset ,数据一页 10 条, 所以第 1 页 a  就是 0, 第 2 页 a 就是 10 ,依次类推。this.$store.state.url.index 的值就是 /api/movie , 但是调用 Object(i[ 'a' ]) 方法后, 结果 e 也就是最终的 token 就得到了。

因此我们可以断定 Object(i['a']) 里面就是核心的加密逻辑, 我们再把 i['a'] 方法追踪一下

 我们大致可以看到,这里又掺杂了时间, SHAI , Base64, 列表等各种操作。要深入分析,需要花费一些时间

现在核心方法找到了, 参数我们也知道怎么构造了,就是方法内部比较复杂,但我们想要的其实就是这个方法运行的结果,即最终 token

实战

首先,我们来实现 Object(i[ 'a' ]) 的全局挂载,我们只需要将其赋值给 widow 对象的一个属性即可,属性名任意,只要不和现有的属性冲突即可

比如我们要在

var a = (this.page - 1) * this.limit
                      , e = Object(i["a"])(this.$store.state.url.index, a);

下面添加用于挂载全局的 window 对象的代码

window.encrypt = Object( i[ 'a' ])

比如。这里我们将 Object( i[ 'a' ]) 挂载给 window 对象的  encrypt 属性。 这样只要代码执行完毕,我们调用 window.encrypt  方法就相当于调用了 Object( i[ 'a' ]) 方法

接着我们将修改后的整个 JS 代码保存到本地, 并将其命名为 chunk.js 

接下来我们利用 playwright 启动一个浏览器, 并使用 Request Interception 将 JS 文件替换

from playwright.sync_api import sync_playwright

BASE_URL = 'https://spa2.scrape.center'
context = sync_playwright().start()
browser = context.chromium.launch()
page = browser.new_page()
page.route(
    "/js/chunk-10192a00.243cb8b7.js",
    lambda  route: route.fulfill(path="./chunk.js")
)

page.goto(BASE_URL)

这里首先用 playwright 创建了一个 Chromium 无头浏览器, 然后利用 new_page 方法创建了一个新的页面, 并定义了一个关键路由

page.route( "/js/chunk-10192a00.243cb8b7.js",

lambda route: route.fulfill(path="./chunk.js")

)

这里路由的第一个参数是原本加载文件的路径,比如原本加载的 JS 路径为

 /js/chunk-10192a00.243cb8b7.js

第二个参数利用 route 的 fulfill 方法指定本地的文件, 也就是我们修改后的文件 chunk.js 

这样 playwright 加载  /js/chunk-10192a00.243cb8b7.js 文件的时候,其内容就会被替换为我们本地保存的 chunk.js 文件。 当执行之后 Object(i ['a']) 也就被挂载给 window 对象的 encrypt 属性了, 所以调用 window.encrypt  方法就相当于调用了 Object(i['a']) 方法了

怎么调用呢? 很简单, 只需在 playwright 环境中额外执行 JS 代码即可 比如可以定义方法如下

def get_token(offset):
    result = page.evaluate(""" ()=>{
        return window.encrypt("%s", "%s")
    }""" % ('/api/movie', offset))
    return result

这里我们声明了 get_token 方法,经过上文分析,模拟执行方法需要传入两个参数,第一个参数是固定值 /api/movie , 另一个参数是变值, 所以将其当做参数传入

在模拟执行的过程中,我们直接使用 page对象的 evaluate 方法, 传入 JS 字符串即可, 这个 JS 字符串是一个方法,返回的就是 window.encrypt 方法的执行结果, 最后奖结果赋值给 result 变量,并返回

到此为止核心代码就完成了,最后看一下最终逻辑

from playwright.sync_api import sync_playwright
import time
import requests

BASE_URL = 'https://spa2.scrape.center'
INDEX_URL = BASE_URL + '/api/movie?limit={limit}&offset={offset}&token={token}'
MAX_PAGE = 10
LIMIT = 10

context = sync_playwright().start()
browser = context.chromium.launch()
page = browser.new_page()
page.route(
    "https://spa2.scrape.center/js/chunk-10192a00.243cb8b7.js",
    lambda route: route.fulfill(path="./chunk.js")
)

page.goto(BASE_URL)
# time.sleep(1000)

def get_token(offset):
    result = page.evaluate('''()=>{
        return window.encrypt("%s", "%s")
    }''' % ('/api/movie', offset))
    return result


for i in range(MAX_PAGE):
    offset = i * LIMIT
    token = get_token(offset)
    index_url = INDEX_URL.format(limit=LIMIT, offset=offset, token=token)
    response = requests.get(index_url)
    print('response', response.json())

注意: page.route 前面是按照教程写的 相对路径 /js/chunk-10192a00.243cb8b7.js 结果在运行的时候报错,经过排查,发现是文件替换失败了,这里改成绝对路径运行成功。所以留着作为记忆

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值