【爬虫实战】使用Python和JS逆向基于webpack的看准网

前言

之前学习了一个基于webpack的网站,接下来再来一个加深一下印象。看准网 为用户提供以下信息:企业基本信息、企业评价、薪酬资讯、面试经验、招聘职位等。不管这个网站的数据是否可靠,总之我们只是用来学习一下而已。

一、目标整理

我们要拿到该网站主要接口的加密方法,获取返回数据并解析。以一个综合接口为例:

搜索结果

接口筛选不再细述,重点看这个接口:

该接口的载荷明显是做过处理的,这是首要目标。其次可以看一下请求头,可以发现并没有什么明显的字段,最多也就是Cookie和一个追踪ID,是否有用之后再说。

而接口的响应数据明显是密文,需要解密处理:

33 BBo5wlNYHjwok3qLCZ46UivPnMif3tveYpXwz9W+fYOnkwmQF74jNn/6.....省略

二、逻辑分析

1.定位加密函数

通过载荷可以看到关键字kiv,可以尝试搜索一下,会发现只有1条记录,那么这就很可能是咱们要找的目标。点击进入后要注意再次搜索一下,因为源代码是折叠在一行的,实际上有可能有n个kiv。这时候会发现有15条记录,那么哪一条是咱们要找的呢,有两种方式,一个是打断点排除,要么就是通过启动器来查。要记得在XHR这里打上断点/api_to/search/comprehensive.json,少走弯路。

首先会来到 p.send(d) ,再看一下前面的代码,这明显就是一个Ajax请求,因此加密操作肯定在之前。

这是一个过滤器,所以继续往前找:

一段熟悉的代码,和之前的虚拟货币几乎是一样的,unshift往前放(请求前),push往后放(响应后)。然后输出t[0]获取第一个函数。

代码就走到了这里,这里明显是调用前和调用后的函数,所有有可能会发懵。但是可以点击逐步运行,跟着走看能到哪里。走了几次后看到到了一个a函数:

而且出现了kiv和b值,那么加密函数肯定在这里,接下来分别找这两个参数的加密位置。

s的赋值这段代码有点长,分开在控制台执行,可以看到是(s = (0,M._A)()起决定性作用的。

M._A函数也就是图片上的这一段,运行多次后会发现e并没有传值,只是一个常量16,然后生成随机字符串。

接下来就是b值,是M.mA(),但是要传值,分别是查询参数"{"pageNum":1,"limit":10,"query":"python"}"和刚才获取的kiv,其实这里的查询参数要记得带上反斜杠,我之前就在这里坑了很久,其实也是经验的问题。

这个h函数就是刚才的M.mA(),但是还要调用l函数和u函数。这样两个值就出来了。

2. 定位解密函数

其实往往加密和解密离得很近,其实刚才的截图里c函数就是用来解密的。可以直接搜索decrypt,然后打上断点试试即可。

三、代码实现

1. JS部分

const CryptoJS = require('crypto-js');
p = function (e) {
    
    
    void 0 === e && (e = 16);
    for (var t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""), n = "", r = 0; r < e; r++) {
    
    
        n += t[Math.ceil(61 * Math.random())]
    }
    return n
}

// var e = 16
// console.log(p())

// var h1 = '"{"pageNum":1,"limit":10,"query":"python"}"'
// var h2 = "/api_to/search/comprehensive.json"


l = function (e, t) {
    
    
    console.log('l函数', e, t)
    void 0 === e && (e = ""), void 0 === t && (t = "");
    var n = u()

    var r = CryptoJS.AES.encrypt(e.toString(), n.key, {
    
    
        iv: CryptoJS.enc.Utf8.parse(t), mode: n.mode, padding: n.pad   // "ofn562MM8Lw9hf60"
    });
    console.log('l函数', e, t, r.toString())
    return r = r.toString()
}
var s, u = (s = null, function () {
    
    
    return s || (s = function () {
    
    
        var e, t, n, r, i = null;
        return i || (t = new RegExp("\\u200c", "g"), n = new RegExp("\\u200d", "g"), r = new RegExp(".{8}", "g"), e = "".replace(r, (function (e) {
    
    
                return String.fromCharCode(parseInt(e.replace(t, 1).replace(n, 0), 2))
            }
        )), // console.log(n,r,e),
            i = {
    
    
                key: CryptoJS.enc.Utf8.parse(e), mode: CryptoJS.mode.CBC, pad: CryptoJS.pad.Pkcs7
            }), i
    }()), s
})

h = function (e, t) {
    
    
    console.log('h函数', e)
    return e ? ("string" != typeof e && (e = e.toString()), l(e, t.iv)) : ""
}

function getKiv(q_str) {
    
    

    new_iv = p()
    // new_iv = "ZgL9gX14UFHTb1nW"
    // console.log(new_iv)
    t = h(q_str, {
    
    iv: new_iv}).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "~");
    console.log(new_iv, t)
    return [new_iv, t]
}

decodeF = function (e, t) {
    
    
    void 0 === e && (e = ""),
    void 0 === t && (t = "");
    var n = u()
        , r = CryptoJS.AES.decrypt(e.toString(), n.key, {
    
    
        iv: CryptoJS.enc.Utf8.parse(t),
        mode: n.mode,
        padding: n.pad
    });
    return r = r.toString(CryptoJS.enc.Utf8)
}

输出:

2. Python部分

实际上不加Cookie也可以运行

import json

import requests
import execjs


headers = {
    
    
    'authority': 'www.kanzhun.com',
    'accept': 'application/json, text/plain, */*',
    'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,la;q=0.7,lv;q=0.6,da;q=0.5,sm;q=0.4',
    'cache-control': 'no-cache',
    'content-type': 'application/x-www-form-urlencoded;charset=utf-8',
    # 'cookie': '__c=1701053108; wd_guid=12975940-d466-4271-b5c6-dc395ccff15b; __g=-; Hm_lvt_1f6f005d03f3c4d854faec87a0bee48e=1701053110; historyState=state; __l=r=&l=%2Fapi_to%2Fhome%2Frec.json%3Fb%3D-ero8orkHyFDHJm77d0zyg~~%26kiv%3DjLCwR7zEcefCiNp8; Hm_lpvt_1f6f005d03f3c4d854faec87a0bee48e=1701066660; __a=28605012.1701053108..1701053108.39.1.39.39',
    'dnt': '1',
    'href': 'https://www.kanzhun.com/search/?query=python&type=0',
    'pragma': 'no-cache',
    'referer': 'https://www.kanzhun.com/search/?query=python&type=0',
    'reqsource': 'fe',
    'sec-ch-ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"macOS"',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-origin',
    # 'traceid': '6bc9498b-9453-41d3-ae70-b35c00ecee4d',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
    'x-requested-with': 'XMLHttpRequest',
}
with open('kanzhun.com.js') as f:
    js_code = f.read()
js = execjs.compile(js_code)
q_str = json.dumps({
    
    "pageNum": 1, "limit": 10, "query": "python"})
kiv, b = js.call('getKiv', q_str)
print(kiv, b)
params = {
    
    
    'b': b,
    'kiv': kiv,
}
print(params)
response = requests.get(
    'https://www.kanzhun.com/api_to/search/comprehensive.json',
    params=params,
    # cookies=cookies,
    headers=headers,
)

print(response.text)

content = js.call('decodeF', response.text, kiv)
print(json.loads(content))

输出,上面是密文,下面是明文:

四、总结

代码很简单,但是在JSON序列化这一块坑了很久,还是经验问题。通过这次练习进一步加深了对webpack类的定位方法的使用。当然不一定非要用这种方式,如开头所说,方式有多种,哪个有效用哪个。

其实对于这种网站,只要逆向成功一个接口,其他的接口加密方式基本一致,试了一下其他的接口比如job等等都没啥问题,很少有人会写多套加密逻辑。

免责声明

  • 教育和研究用途:本文章提供的信息和示例代码仅供教育和研究用途。它们的目的是帮助读者了解爬虫技术的原理和应用。
  • 合法合规性:请注意,网络爬虫可能会侵犯网站的服务条款或法律法规。在实际应用中,你必须确保你的爬虫活动合法、合规,并遵守所有相关法律。
  • 责任限制:作者对于读者使用文章中提供的信息和代码所导致的任何问题或法律纠纷概不负责。读者应自行承担风险并谨慎操作。
  • 合理使用:请在使用网络爬虫时保持谨慎和礼貌。不要对目标网站造成不必要的干扰或侵害他人利益。请在遵守法律的前提下使用爬虫技术。
  • 变动和更新:作者保留随时更改文章内容的权利,以反映新的法规、技术和最佳实践。
  • 资源和参考文献:本文章中的示例代码和信息可能依赖于第三方资源,作者会尽力提供相关参考文献和资源链接。作者不对这些资源的可用性或准确性负责。
  • 协商:如果您有任何关于本文内容或责任声明的疑虑或疑问,请在使用之前与专业法律顾问协商。

猜你喜欢

转载自blog.csdn.net/u013046615/article/details/134651378