2023-Python实现烯牛数据采集
文章目录
- 👉1、目标网址
- 👉2、接口分析
- 👉3、代码实现
【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定位。对于此,我们可以搜索常用的关键字:decrypt
–encrypt
–JSON.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 = 1var f = e1(e2(JSON.stringify(l.payload))), p = sig(f);l.payload = fl.sig = pl.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 参数2decrypted = 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_codepass# 解密 公司详情
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 参数2end_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']passif __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~