2023-Python实现烯牛数据采集

【JS 逆向百例】 1/100

学习记录:哈喽~ 前面我们接触了一些JS逆向的数据获取,如果前面的百度,有道翻译和正方教务系统的登录加密你已掌握,说明我们已经入门啦。征程万里风正劲,重任千钧再出发。后面还有很长的路要走,让我们一起努力吧!今天的学习记录是关于烯牛数据网站的数据采集,这是一个很综合的例子, 它的返回数据和请求体都进行了加密。 这篇将详细记录调试步骤。

👉1、目标网址

主页:https://www.xiniudata.com/industry/newest?from=data
接口:https://www.xiniudata.com/api/search3/company/search_company_for_lib
接口:https://www.xiniudata.com/api2/service/x_service/person_company4_list/list_companies4_list_by_codes

👉2、接口分析

数据接口:
在这里插入图片描述
分析:
查看其各个参数,直接上搜索大法查找。先查看密文数据的解密。找合适的字符进行搜索定位,在这个接口里,我们尝试用返回的数据或请求体中的参数的关键字进行搜索大法时,发现都不好进行JS定位。对于此,我们可以搜索常用的关键字:decryptencryptJSON.parse搜索该接口的路径参数,还可以在Network 选项卡下,该请求的 启动器(Initiator) 列里看到它的调用栈,其调用顺序为由上而下:
在这里插入图片描述
鼠标悬停在上面可以看到这个接口调用栈的情况,并且可以找到触发的位置。一般情况下,我们直接双击就会自动的跳转到【源码 / Sources】面板并显示对应JS代码 (点击最上面的JS效果一样)。
进入后ctr + f 进行搜索,多试了几次,在用JSON.parse进行搜素时,发现了可疑代码。如下:
图1:
在这里插入图片描述
图2:
在这里插入图片描述
两处都很可疑,我们打上断点进行调试:
在这里插入图片描述
如上图:从左向右

  • Pause/Resume script execution:暂停/恢复脚本执行(程序执行到**下一断点**停止)。
  • Step over nextfunction call:执行到下一步的函数调用(跳到下一行)。
  • Step into next functioncall:进入当前函数
  • Step out of current function:跳出当前执行函数
  • Step: 一步步执行代码,遇到有函数调用,则进入函数。
  • Deactive/Activeall breakpoints:关闭/开启所有断点(不会取消)。
  • Pause on exceptions:异常情况自动断点设置。

调试发现,图1 为我们的目标JS:
在这里插入图片描述再对上面变量进行打印测试:
在这里插入图片描述
这里好像就是拿到密文后再对其解密的操作,所以我们继续跟下去:
在这里插入图片描述
在这里插入图片描述
分别跟进去:有d1,d2两个函数。到这里先分别打上断点进行调试观察:这里扣下JS,补齐参数进行数据测试:
在这里插入图片描述
我们将在右侧复制的d的值放进去测试时:数据是成功解密了,但他只有一条,emm~,难道我们找错啦?
观察前面几个接口返回的数据格式发现都如下图;
在这里插入图片描述
相当于这里的几个接口都是走的我们扣出的JS进行密文解密,那就直接测试:退出断点,观察我们要的公司数据接口解密是不是走的这里,如下图:
在这里插入图片描述
OK~复制进去测试:
在这里插入图片描述
发现没问题~到这里数据的解密已OK,那就去看看接口的参数有没有什么要处理的:
在这里插入图片描述
继续搜索大法:
发现还是在上面那个JS中找到可疑代码:2处
图1:
在这里插入图片描述
图2:
在这里插入图片描述
其实看到图2 的条件就知道它是不会执行的,但为了保险还是分别打上断点去调试:
调试后锁定图1:跟进发现这里的参数加密与密文解密一样也是走的一处,参数格式上也相似,所以我们像上面密文解密一样,退出掉其他的接口,让断点加载到我们的目标接口让他去生成加密参数:我们退出时不要点快了,看着右边的t,它是接口路径参数,等它到目标接口时就不要退出断点了,我们继续调试即可:
在这里插入图片描述
在此时,我们直接执行到下一步的函数调用进行观察:如果进入的循环太多,直接退出就OK。

在这里插入图片描述
发现它参数居然没变 !去多几次请求测试几次发现:网站数据是下拉分页加载数据,对于每一页的请求参数它是不变的,虽然它不变,但它不是我们所要的理想定值,因为不同的数据有不同的定值,那他就不可行,所以,我们还是要去进行加密分析的。
老办法,分别跟进JS进行调试:
在这里插入图片描述
边看边扣出JS进行调试:扣出来稍微改写一下(保证能调用能运行):先run ,梭一把,看看有什么反应:
在这里插入图片描述
不怕他报错,就怕它什么反应都不给你。看报错说: l is not defined ,这个就是差什么补什么,确认不是代码格式等问题后,就到对应的 JS 代码部分下进行调试:
在这里插入图片描述
OK~复制出来,补到我们扣下来的JS中去就好。

在这里插入图片描述
继续跟下去,发现有一个MD5加密:
JS实现MD5 :
安装:npm install crypto-js
导入:const crypto = require(“crypto”);
在这里插入图片描述
JS代码:

const  crypto = require("crypto");
function md5(res) {
    return crypto.createHash('md5').update(res).digest('hex')
}

到这里我们的请求体参数和解密已经分析的差不多啦,是可以进行数据的采集了,但是,这个网站的数据是下拉分页加载的,在调试它的分页请求数据时,下拉不了无法在请求数据。结束断点。正常下拉观察接口:开始我是去看它的page分页是怎么控制的,但没什么
在这里插入图片描述
每次下拉一页,就会更新两个接口。再回到断点:
在这里插入图片描述
发现一个参数:limit,看到它就要注意一下,一般是请求数据的限制,而他的值还为10,那更应该去看一下了。最后发现规律:https://www.xiniudata.com/api/search3/company/search_company_for_lib
这个接口请求加载出公司的编号,后面的详情接口https://www.xiniudata.com/api2/service/x_service/person_company4_list/list_companies4_list_by_codes依据前面的limt的值而返回多少条数据。多调试就OK,在调试的时候自己改变加载公司编号的接口limit的值,观察 n 的变化,再继续调试走到公司详情接口,会发现limt的值不同,返回的数据也不同。

👉3、代码实现

JS:
decrypt.js

const crypto = require("crypto");


var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="

var _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O"

// 参数加密
function ret(n) {
    var s = JSON.stringify(n)
        , l = JSON.parse(s);

    var r = 1
    var f = e1(e2(JSON.stringify(l.payload)))
        , p = sig(f);
    l.payload = f
    l.sig = p
    l.v = Number(r)


    return l


}

function e1(e) {
    if (null == e)
        return null;
    for (var t, n, r, o, i, a, c, u = "", s = 0; s < e.length;)
        o = (t = e.charCodeAt(s++)) >> 2,
            i = (3 & t) << 4 | (n = e.charCodeAt(s++)) >> 4,
            a = (15 & n) << 2 | (r = e.charCodeAt(s++)) >> 6,
            c = 63 & r,
            isNaN(n) ? a = c = 64 : isNaN(r) && (c = 64),
            u = u + _keyStr.charAt(o) + _keyStr.charAt(i) + _keyStr.charAt(a) + _keyStr.charAt(c);
    return u
}

function e2(e) {
    if (null == (e = _u_e(e)))
        return null;
    for (var t = "", n = 0; n < e.length; n++) {
        var r = _p.charCodeAt(n % _p.length);
        t += String.fromCharCode(e.charCodeAt(n) ^ r)
    }
    return t
}

function sig(e) {
    return md5(e + _p).toUpperCase()
}

function md5(res) {
    return crypto.createHash('md5').update(res).digest('hex')
}

function _u_e(e) {
    if (null == e)
        return null;
    e = e.replace(/\r\n/g, "\n");
    for (var t = "", n = 0; n < e.length; n++) {
        var r = e.charCodeAt(n);
        r < 128 ? t += String.fromCharCode(r) : r > 127 && r < 2048 ? (t += String.fromCharCode(r >> 6 | 192),
            t += String.fromCharCode(63 & r | 128)) : (t += String.fromCharCode(r >> 12 | 224),
            t += String.fromCharCode(r >> 6 & 63 | 128),
            t += String.fromCharCode(63 & r | 128))
    }
    return t
}


// console.log(ret(n))

encrypt.js

//密文解密
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
    , _p = "W5D80NFZHAYB8EUI2T649RT2MNRMVE2O";


function d1(e) {
    var t, n, r, o, i, a, c = "", u = 0;
    for (e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); u < e.length;)
        t = _keyStr.indexOf(e.charAt(u++)) << 2 | (o = _keyStr.indexOf(e.charAt(u++))) >> 4,
            n = (15 & o) << 4 | (i = _keyStr.indexOf(e.charAt(u++))) >> 2,
            r = (3 & i) << 6 | (a = _keyStr.indexOf(e.charAt(u++))),
            c += String.fromCharCode(t),
        64 != i && (c += String.fromCharCode(n)),
        64 != a && (c += String.fromCharCode(r));
    return c
}

function d2(e) {
    for (var t = "", n = 0; n < e.length; n++) {
        var r = _p.charCodeAt(n % _p.length);
        t += String.fromCharCode(e.charCodeAt(n) ^ r)
    }
    return t = _u_d(t)
}

function _u_d(e) {
    for (var t = "", n = 0, r = 0, o = 0, i = 0; n < e.length;)
        (r = e.charCodeAt(n)) < 128 ? (t += String.fromCharCode(r),
            n++) : r > 191 && r < 224 ? (o = e.charCodeAt(n + 1),
            t += String.fromCharCode((31 & r) << 6 | 63 & o),
            n += 2) : (o = e.charCodeAt(n + 1),
            i = e.charCodeAt(n + 2),
            t += String.fromCharCode((15 & r) << 12 | (63 & o) << 6 | 63 & i),
            n += 3);
    return t
}

function decrypt(l) {
    var d = d1(l)
        , v = d2(d)
        , y = JSON.parse(v);

    return y
}

// console.log(decrypt(l))

Python

"""
CSDN: 抄代码抄错的小牛马
"""

import execjs
import requests


# 加密 公司的代码编号 的参数 payload  sig
def use_encrypt_JS1():
    # 读取js文件
    with open('./encrypt.js', encoding='utf-8') as f:
        js = f.read()

    # 通过compile命令转成一个js对象
    docjs = execjs.compile(js)
    n1 = {
        "payload": {
            "industry_ids": 921,
            "domestic": True,
            "corporate_locationIds": [],
            "tag_names": [],
            "corporate_rounds": [],
            "sort": 76006,
            "order": -1,
            "start": 0,
            "limit": 30
        }
    }
    print(type(n1))
    company_params = docjs.call('ret', n1)

    print('JS返回的num:', type(company_params))
    print('JS返回的num:', company_params)
    return company_params


# 加密 公司详情 的参数 payload  sig
def use_encrypt_JS2(company_code):
    # 读取js文件
    with open('./encrypt.js', encoding='utf-8') as f:
        js = f.read()

    # 通过compile命令转成一个js对象
    docjs = execjs.compile(js)
    # n1 = {
    #     "payload": {
    #         "codes": [
    #             "ZStack",
    #             "tingyukeji49",
    #             "fnmzhbjkjxgs",
    #             "xuehukeji37",
    #             "ExceedData",
    #             "xinhongpu",
    #             "jidatongxin",
    #             "xinmiershijue",
    #             "BQSJEZR0",
    #             "zhongkehaiwei"
    #         ]
    #     }
    # }
    n2 = {
        "payload": {
            "codes": company_code
        }
    }
    print(type(n2))
    details_params = docjs.call('ret', n2)

    print('JS返回的公司详情参数:', type(details_params))
    print('JS返回的公司详情参数:', details_params)
    return details_params


#  解密 获取的公司的代码编号
def use_decrypt_JS3(encrypt):
    # 读取js文件
    with open('./decrypt.js', encoding='utf-8') as f:
        js = f.read()

    # 通过compile命令转成一个js对象
    docjs = execjs.compile(js)

    # 调用function ==> 调用的方法名, 参数1  参数2
    decrypted = docjs.call('decrypt', encrypt)

    print('----------------------烯牛数据----------------------')
    print('解密后的公司数量数据::')
    # print(decrypted)
    print('要获取公司的数量为:', len(decrypted['data']))
    company_code = [i['company_code'] for i in decrypted['data']]
    print(company_code)
    # for i in decrypted['data']:
    #     print(i['company_code'])

    return company_code
    pass


#  解密 公司详情
def use_decrypt_JS4(details_data):
    # 读取js文件
    with open('./decrypt.js', encoding='utf-8') as f:
        js = f.read()

    # 通过compile命令转成一个js对象
    docjs = execjs.compile(js)

    # 调用function ==> 调用的方法名, 参数1  参数2
    end_data = docjs.call('decrypt', details_data)

    print('----------------------烯牛数据  详情----------------------')
    print('解密后的公司详情数据::')
    print(end_data)

    pass


# 获取请求 公司的代码编号 返回的密文
def get_data1(params):
    list_api1 = 'https://www.xiniudata.com/api/search3/company/search_company_for_lib'
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
        'cookie': 'btoken=LZKRG0M4XQJ6TXVDCZF4GLMBEG3UD3CA; hy_data_2020_id=18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f; hy_data_2020_js_sdk={"distinct_id":"18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f","site_id":211,"user_company":105,"props":{},"device_id":"18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f"}; utoken=QYT58WVGWTVX6IT6RPU3XPLLEKLNF999; username=YOU; export_notice=true; Hm_lvt_42317524c1662a500d12d3784dbea0f8=1681281817,1681350920,1681357499,1681430886; Hm_lpvt_42317524c1662a500d12d3784dbea0f8=1681438188'
    }

    lists = requests.post(url=list_api1, json=params, headers=headers).json()

    print(lists)
    # print(lists['d'])
    return lists['d']
    pass


# 获取请求 公司详情 返回的密文数据
def get_data2(details_params):
    list_api2 = 'https://www.xiniudata.com/api2/service/x_service/person_company4_list/list_companies4_list_by_codes'
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
        'cookie': 'btoken=LZKRG0M4XQJ6TXVDCZF4GLMBEG3UD3CA; hy_data_2020_id=18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f; hy_data_2020_js_sdk={"distinct_id":"18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f","site_id":211,"user_company":105,"props":{},"device_id":"18764a41e178-0110df9ab084b5-3a716f07-1846428-18764a41e1861f"}; utoken=QYT58WVGWTVX6IT6RPU3XPLLEKLNF999; username=YOU; export_notice=true; Hm_lvt_42317524c1662a500d12d3784dbea0f8=1681281817,1681350920,1681357499,1681430886; Hm_lpvt_42317524c1662a500d12d3784dbea0f8=1681438188'
    }

    lists = requests.post(url=list_api2, json=details_params, headers=headers).json()
    print('----------------------烯牛数据----------------------')
    print('公司详情密文::')
    print(lists)
    # print(lists['d'])
    return lists['d']
    pass


if __name__ == '__main__':
    params = use_encrypt_JS1()
    company_params = get_data1(params)
    company_code = use_decrypt_JS3(company_params)
    details_params = use_encrypt_JS2(company_code)
    details_data = get_data2(details_params)
    use_decrypt_JS4(details_data)

运行截图:
在这里插入图片描述
over~

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

抄代码抄错的小牛马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值