蝉妈妈登录分析
2024年10月18日大约 6 分钟
特此声明:所有爬虫文章均用于记录学习与交流, 请勿用于其非法用途或侵害他人利益, 若非法使用作者概不负责。
蝉妈妈官网:https://www.chanmama.com/
接口流程
[POST]
调用https://passport.chanmama.com/v2/cas/authorize
接口获取token
参数示例:响应示例:{ "appid": 10000, // 写死 "grant_type": "password", // 写死 "mobile": "${手机号}", // 手机号 "password": "edb1872c2fe3c992975ea4474cb2c6a0", // 原始密码md5再转小写 "device": "PC" // 写死 }
{ "code": 0, "data": { "expire_time": 1727422609, "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBfaWQiOjEwMDAwLCJleHAiOjE3Mjc0MjI2MDksImlhdCI6MTcyNzQyMjMwOSwibHZ0IjoxLCJ1bmlxdWVfaWQiOiJDQVMtRFo1M0NaSE0yWEhDLUZZNUE1UCJ9.3461vlCSkvmcgDESgxn7n0lDswWBbN2V565bXaurtn4", "version": "v2" }, "msg": "success", "rid": "a6d8f8ab3591471f4ca922f7899b9659" }
[POST]
调用https://api-service.chanmama.com/v1/access/token
接口获取token
信息 参数示例:{ "from_platform": null, // 写死 "appId": 10000, // 写死 "timeStamp": 1727422310, // 当前时间戳 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBfaWQiOjEwMDAwLCJleHAiOjE3Mjc0MjI2MDksImlhdCI6MTcyNzQyMjMwOSwibHZ0IjoxLCJ1bmlxdWVfaWQiOiJDQVMtRFo1M0NaSE0yWEhDLUZZNUE1UCJ9.3461vlCSkvmcgDESgxn7n0lDswWBbN2V565bXaurtn4", // 步骤1的接口返回的$.data.token "grant_type": "cas" // 写死 }
- 步骤2返回
code=42024
则需要短信验证码验证 [GET]
调用https://captcha-service.chanmama.com/api/captcha/get?aid=CMMPC&ua=Mozilla%2F5.0+(Windows+NT+10.0%3B+Win64%3B+x64)+AppleWebKit%2F537.36+(KHTML,+like+Gecko)+Chrome%2F129.0.0.0+Safari%2F537.36&refer=https:%2F%2Fwww.chanmama.com%2Flogin.html
获取滑块图片与sessionid[POST]
调用https://captcha-service.chanmama.com/api/captcha/verify
接口校验验证码[POST]
调用https://api-service.chanmama.com/v1/access/sendLoginCode
接口将步骤6
中的ticket
传入captcha_ticket
参数,参数示例:{ "appId": 10000, "username": "${手机号}", "captcha_ticket": "9ARY9mUPCjinZsxBIP9O8bS_PeLrR4YlN8YMj4vqbXRTWpYXDjS1NoXbGIlZG8WULjzST-2OVuZrlmOqFnQRmygdP4FIJ5GCruEVG5C2pX8EQrZ8v6IrMC4m88smvz1ioyCDmRsq200", // 步骤6返回的ticket "captcha_randstr": "", "valid_type": "2" }
[POST]
重新调用https://api-service.chanmama.com/v1/access/token
接口获取token
信息, 需要将步骤7
中的quene_id
传入{ "from_platform": null, "appId": 10000, "timeStamp": 1728368377, "username": "${手机号}", // 手机号 "code": "123456", // 验证码 "quene_id": "2d4ff4f5c8e79a4eb121b5a9c0bd4f55", // 步骤7返回的quene_id "grant_type": "sms" }
滑块加密算法流程
加密前3个参数
参数1:
(MouseEvent.clientX - this.startX) / this.parentWidth * 100
: 当前鼠标clientX减去初始clientX(22~32左右) 再除以父宽度(340) 最后乘以100参数2: 轨迹数组, 示例:
[{"x":6.47235294117647,"y":74.71,"t":157044}, ...]
2.1.x
计算逻辑:// 其中MouseEvent.clientX为当前鼠标的clientX, this.startX为初始的clientX parseFloat(((MouseEvent.clientX - this.startX) / 340 * 100).toFixed(2)) + 40 / 340 * 100 / 2
2.2.
y
计算逻辑:// 其中MouseEvent.clientY为当前鼠标的clientY parseFloat((MouseEvent.clientY / 340 * 100).toFixed(2))
2.3.
t
计算逻辑: 当前毫秒时间戳-初始时间戳参数3: 当前毫秒时间戳减去开始毫秒时间戳
verity
接口参数处理, 参数示例:
{
"aid": "CMMPC",
"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
"sessid": "9ARY9mUPCjg9WQXxuzLkIpXLsQ5YbUbGPzS4QhpihpmXozMtp6ajpzNRpES9hwKUDpjV4ZxdFO4X7LzNv4X59qr9McWDzNjYLvkGNqGQyUdYFUhZ9ihcR7J_biu7n13tL7LDi4uXD0qC5sxjh7ZOGi5z6RpyKvtim4zq7vVL9G-SQ5h83GaRD54JZhUG1I20xLuX0uaPEGIjtGak0hQPxbel82ReqsUdBNQtoUqD6wQJSPXmdd_7-etPdpQdj1JeL9ZLfk6gz7vgOtOdDikPx1ELJ9gE3Jyj",
"sign" : "2586B1982700D62E3A91382E12ADD0F1",
"timestamp": 1728462692,
"collect": "1D87293551C71CA43E72458AFEA2231FD1BDDB188AF1B93A81675DC62A333FF81558C86B5F31F0E04A57F0623A0A361AF508E0C3561C701611BF059D42D478BEE81AA2F85EBC47A95B245BB95AF6E9AB3903E119B5FCFDFE02676B5367D8A427",
"version": "1727373326"
}
2.1. `aid`: 写死`"CMMPC"`
2.2. `ua`: 随便写个`ua`
2.3. `sessid`: `https://captcha-service.chanmama.com/api/captcha/get?aid=CMMPC&ua=Mozilla%2F5.0+(Windows+NT+10.0%3B+Win64%3B+x64)+AppleWebKit%2F537.36+(KHTML,+like+Gecko)+Chrome%2F129.0.0.0+Safari%2F537.36&refer=https:%2F%2Fwww.chanmama.com%2Flogin.html` 接口返回的sessid
2.4. `version`: 动态参数, 与后续AES加密的`iv`, `key`以及`sign`计算的`secret`(这个未测试,建议一起绑定)一一对应,修改`version`需要去js文件中找出当前`version`的`iv`, `key`, `sign`的`secret`值,这里提供一组
version='1728410126'
iv='J503J9YKY5MUVS1S'
key='HE32F3BET7KIHQKN'
sign_secret='SCIWNV4SMHOW9TD7'
2.5. `collect`: 轨迹参数, 计算逻辑如下:
/**
* 加密函数
* @param version sdk version, 不同version的key和iv不同
* @param data '{"position": xxx, "path": [{"x": ...,"y": ..., "t": ...}]}' 轨迹数据 具体见步骤[加密前3个参数的第一个参数计算逻辑], position在该值基础上加上常数值 50 / 340 * 100 / 2
* @returns {Object} {version: '版本', collect: '加密后的文本'}
*/
function encrypt(data, version) {
// 编码
version = version || default_v
data = CryptoJS.enc.Utf8.parse(data) // data
const key = CryptoJS.enc.Utf8.parse(v_key_map[version]) // key
const iv = CryptoJS.enc.Utf8.parse(v_iv_map[version]) // iv
// 配置加密选项
const options = {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
};
// 执行加密操作
const encrypted = CryptoJS.AES.encrypt(data, key, options);
// 获取ciphertext并转换为大写字符串
const ciphertext = encrypted.ciphertext.toString().toUpperCase();
// console.log(ciphertext)
return {
'version': version,
'collect': ciphertext,
}
}
2.6. `sign`: 校验参数, 计算逻辑如下:
/**
* 签名函数
* @param data json结构
* {
* "aid": "CMMPC",
* "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
* "sessid": "9ARY9mUPCjihUI9Sm7RwHCTf2D81_-MP6K5yEV9uWSdEnghf7fS8IgQVYxZllTdZiaTFMAFlCnssajWwOtW8h3t7xkNaASJFLil7S7lcld2OPW1yogGgi4HGIcfLE2VvHebWJG16hr5__EGrN-NwtoshbGIjIwpKIz0s2MNn4U_Jw4aLhUhneryOb41nKQOHYVW2n9pG4W-KLvr02xuIYedpcSHGjhgXP3zGWhw_3NHlzVME4plDJkn2rj_SKkD1cZAZ65OndXEHGRG4IzYKJr_vqHYeG1vn",
* "collect": "A570E4D1CF62B1A4B10A4D267B9825362F77F925AEAAF17C6398A888C42353B892040BF2D7EBCD82BA987A5C43A21871276DC9257A4120E4C1BBAB4F7D13AF3D6CD29DE004F443236068D2D83CE66B83AE40FB9AD6E657BEC6ACC94F94000D8BDEB1AAECD686A630C503699144446DC30081B3E248560067EB5E9D7886B48653E3DA10BDF6BE7050BB04E1FF47346696B34B8B8EB29CF90FDB414762F6F50F94B43601EF5EC3E60D8ACA4988DA66B4906503A3813189D19878B8BAB6C04DF8B7BB54C1C2E647C4E26329F0B64BCF280B72C86D5A4DE603E9B1AE107F7C9B67BB4A5F514C3F13FF3EA85EAB2D1A33903B724C9ED57F6C4BFE6BF70F74403F686B4F667F7B7CE04B52BD0DB34C7B9B6A1D50A3D4FD7705D6FE26D2BA7AEAA8C0D1684BF9290BE6728779B3E39201CAB67FC2D8D6FC0FE4B5716E892BC4CEBACAAA190780BA326405B339E4C73AC73D0B67FE4F252D62E69653DF5130D5351DBB8EC7367283C25989A026FDB7ADDF2202D202BB1FE614F9D1B3947F9936EE54B354C48E89903D31E30F088E56B0BB3006B2F38AD9E1FBFAD019BB73ABFCCF3F520D4EB869B54E33969704EDF28F653710C181A0D884A28314CF442BEDF205704F025411024BA0FCFCAEA2D8ADB25B9882209390C9AD36F1D5EDE6817068C81E0EE295DD1986097B5E4483FC116F844F43573149A80B9BF2F2D3DB24A7078081C6C381E762706A1A384A012DAE9F546144DA611126C6ABB61001D9E05738DCAB5585D6A1F616B77FB947E40BE3E5492FC30979A51099341504E772BCE717BC5571EE3990F648845B6954E6BBB1007E8887EFF1F7AA298405CF6E50B8EA235C0ED99ADF73468BBEB57B562882D534081F9B2763318C6993E1DD11308C11EA45306030",
* "version": "1728410126",
* "timestamp": 1728458815
* }
* @returns 签名参数
*/
function sign(data) {
const keys = Object.keys(data).sort()
const p_arr = []
for (let key of keys) {
p_arr.push(`${key}=${data[key]}`)
}
p_arr.push(v_sign_secret_map[data['version']])
const ciphertext = CryptoJS.MD5(p_arr.join('&')).toString().toUpperCase()
console.log(ciphertext)
return ciphertext
}
2.7. 轨迹生成算法, 传入ocr计算的原始偏移像素值(px)
const gjTemplate = [
{"x": 5.88235294117647, "y": 73.82, "t": 14}, {"x": 6.17235294117647, "y": 73.82, "t": 102},
{"x": 6.76235294117647, "y": 73.82, "t": 109}, {"x": 7.94235294117647, "y": 73.82, "t": 117},
{"x": 8.82235294117647, "y": 73.82, "t": 127}, {"x": 10.292352941176471, "y": 73.82, "t": 134},
{"x": 12.06235294117647, "y": 73.82, "t": 142}, {"x": 14.412352941176469, "y": 73.82, "t": 149},
{"x": 16.76235294117647, "y": 73.82, "t": 158}, {"x": 17.94235294117647, "y": 73.82, "t": 165},
{"x": 19.41235294117647, "y": 73.82, "t": 174}, {"x": 21.47235294117647, "y": 73.82, "t": 182},
{"x": 23.53235294117647, "y": 73.82, "t": 190}, {"x": 25.002352941176472, "y": 73.82, "t": 198},
{"x": 26.76235294117647, "y": 73.82, "t": 206}, {"x": 27.94235294117647, "y": 73.82, "t": 213},
{"x": 28.53235294117647, "y": 73.82, "t": 221}, {"x": 29.12235294117647, "y": 73.82, "t": 229},
{"x": 29.412352941176472, "y": 74.41, "t": 238}, {"x": 30.29235294117647, "y": 74.41, "t": 245},
{"x": 30.592352941176472, "y": 74.41, "t": 254}, {"x": 31.17235294117647, "y": 74.41, "t": 262},
{"x": 32.35235294117647, "y": 74.41, "t": 270}, {"x": 33.23235294117647, "y": 74.41, "t": 277},
{"x": 34.41235294117647, "y": 74.41, "t": 285}, {"x": 35.59235294117647, "y": 74.41, "t": 293},
{"x": 36.76235294117647, "y": 74.41, "t": 302}, {"x": 37.94235294117647, "y": 74.41, "t": 309},
{"x": 39.12235294117647, "y": 74.41, "t": 318}, {"x": 40.59235294117647, "y": 74.41, "t": 325},
{"x": 41.17235294117647, "y": 74.41, "t": 333}, {"x": 42.35235294117647, "y": 74.41, "t": 342},
{"x": 43.23235294117647, "y": 74.41, "t": 350}, {"x": 44.12235294117647, "y": 74.41, "t": 357},
{"x": 45.00235294117647, "y": 74.41, "t": 366}, {"x": 45.29235294117647, "y": 74.41, "t": 374},
{"x": 46.17235294117647, "y": 74.41, "t": 382}, {"x": 46.472352941176474, "y": 74.41, "t": 398},
{"x": 46.762352941176474, "y": 74.41, "t": 414}, {"x": 47.06235294117647, "y": 74.41, "t": 422},
{"x": 47.35235294117647, "y": 74.41, "t": 429}, {"x": 47.94235294117647, "y": 74.41, "t": 437},
{"x": 48.23235294117647, "y": 74.41, "t": 445}, {"x": 49.12235294117647, "y": 74.41, "t": 454},
{"x": 50.29235294117647, "y": 74.41, "t": 461}, {"x": 51.762352941176474, "y": 74.41, "t": 469},
{"x": 52.64235294117647, "y": 74.41, "t": 477}, {"x": 54.12235294117647, "y": 74.41, "t": 486},
{"x": 55.00235294117647, "y": 74.41, "t": 493}, {"x": 55.88235294117647, "y": 74.41, "t": 502},
{"x": 56.472352941176474, "y": 74.41, "t": 509}, {"x": 57.06235294117647, "y": 74.41, "t": 517},
{"x": 57.35235294117647, "y": 74.41, "t": 533}, {"x": 57.64235294117647, "y": 74.41, "t": 541},
{"x": 57.94235294117647, "y": 74.41, "t": 558}, {"x": 58.23235294117647, "y": 74.41, "t": 590},
{"x": 58.53235294117647, "y": 74.41, "t": 605}, {"x": 58.82235294117647, "y": 74.41, "t": 622},
{"x": 59.12235294117647, "y": 74.41, "t": 645}, {"x": 59.41235294117647, "y": 74.41, "t": 669},
{"x": 59.70235294117647, "y": 74.41, "t": 694}, {"x": 60.00235294117647, "y": 74.41, "t": 782},
{"x": 60.29235294117647, "y": 74.41, "t": 798}, {"x": 60.59235294117647, "y": 74.41, "t": 805},
{"x": 60.88235294117647, "y": 74.41, "t": 893}, {"x": 61.17235294117647, "y": 74.41, "t": 910},
{"x": 61.472352941176474, "y": 74.41, "t": 957}, {"x": 61.762352941176474, "y": 74.41, "t": 981},
{"x": 62.06235294117647, "y": 74.41, "t": 1110}, {"x": 62.35235294117647, "y": 74.12, "t": 1334}
]
const mouseStart = 21
/**
* 生成轨迹算法
* @param x ocr计算的原始偏移值, 内部按等比例自动缩放
* @returns {{path: *[], position: number}}
*/
function genGuiJi(x) {
x = x * 340 / 700
const arr = revXGuiJi(gjTemplate, mouseStart)
const end = arr[arr.length - 1]['x']
// 最后一个偏移点的position
let lastPX = 1 / 3.4
const ans = []
for (let e of arr) {
// 等比例转换像素点
e['x'] = parseInt((x / end * e['x']).toString())
// 计算position
lastPX = positionX(e['x'], mouseStart)
// x 加盐
e['x'] = slideX(e['x'], mouseStart)
if (e['x'] >= 40 / 340 * 100 / 2) {
ans.push(e)
}
}
return {
'position': lastPX + 50 / 340 * 100 / 2,
'path': ans,
}
}
function slideX(mouseClientX, start) {
const x = positionX(mouseClientX, start)
return parseFloat(x.toFixed(2)) + 40 / 340 * 100 / 2
}
function positionX(mouseClientX, start) {
return (mouseClientX - start) / 340 * 100
}
function revSlideX(x, start) {
x -= 40 / 340 * 100 / 2
x = x / 100 * 340
return parseInt(x.toString())
}
function revXGuiJi(arr, start) {
const ans = []
for (let e of arr) {
ans.push({x: revSlideX(e['x']) + start, y: e['y'], t: e['t']})
}
return ans
}
const gj = genGuiJi(308)
console.log(gj)
原创声明
平台文章均为原创文章,未经许可,禁止转载。
如需转载,请联系作者获取授权,并注明来源及原文链接。