This commit is contained in:
BF
2026-05-06 16:30:59 +08:00
parent 83ab7a79e8
commit c2276f17f4
36 changed files with 432386 additions and 543 deletions

View File

@@ -523,6 +523,7 @@ class JC(object): # 聚查网API封装类
# j = JC() # 创建JC实例
# j.load_cookies()
# j.load_juming_cookies() # 加载聚名网Cookie
# logger.info(j.juming_cookie)
# logger.info("开始登录...")
# login_result = j.auth_login() # 使用聚名网账号登录聚查网
# logger.info(f"登录结果: {login_result}")

View File

@@ -14,6 +14,7 @@ import json # JSON处理模块
import os # 操作系统接口模块
import pickle # 序列化模块
import random # 随机数生成模块
import re
import subprocess # 子进程管理模块
import time # 时间处理模块
from functools import partial
@@ -23,7 +24,6 @@ import requests # HTTP请求库
from loguru import logger # 日志记录库
from requests.cookies import RequestsCookieJar # Cookie处理
# 常量定义
BASE_URL = "https://www.juming.com" # 聚名网基础URL
CAPTCHA_MAX_RETRY = 5 # 验证码最大重试次数
@@ -46,6 +46,7 @@ startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8', startupinfo=startupinfo)
def calculate_seed(t: int) -> str: # 计算验证码种子值
now = int(time.time()) # 获取当前时间戳
c = now - t # 计算时间差
@@ -110,6 +111,113 @@ def encrypt_password(loginToken: str, password: str) -> str: # 加密密码
return final_result # 返回最终加密结果
def decode_a0j_strings(js_content):
"""
从 JS 代码中提取 a0i 字符串数组,并模拟 a0j 解码函数还原所有字符串。
JS 中的 a0j 函数使用自定义 Base64 alphabet 进行字符串解码:
- 原始 alphabet: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=
- 标准 alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
"""
# 兼容多行和单行压缩格式
match = re.search(r"var N\s*=\s*\[(.*?)\];\s*a0i\s*=\s*function", js_content, re.DOTALL)
if not match:
# 尝试更宽松的正则
match = re.search(r"var N=\[(.*?)\];function a0i\(\)", js_content, re.DOTALL)
if not match:
raise ValueError("无法找到 a0i 数组")
arr_str = match.group(1)
strings = re.findall(r"'([^']*?)'", arr_str)
def a0j_decode(index):
d = index - 0xfb
if d < 0 or d >= len(strings):
return None
encoded = strings[d]
# 转换自定义 Base64 到标准 Base64
std_b64 = encoded.translate(str.maketrans(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=',
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
))
padding = 4 - len(std_b64) % 4
if padding != 4:
std_b64 += '=' * padding
try:
return base64.b64decode(std_b64).decode('utf-8')
except Exception:
return encoded
decoded_strings = {}
for i in range(0xfb, 0xfb + len(strings)):
decoded_strings[i] = a0j_decode(i)
return decoded_strings, strings
def decrypt_arg1(arg1, m_arr, key_str):
"""
核心解密逻辑:
1. 按 m 数组重排 arg1 字符
2. 与密钥 key_str 进行 XOR 运算
"""
# 步骤1按 m 数组重排
q = [''] * len(m_arr)
for i, ch in enumerate(arg1):
for j, idx in enumerate(m_arr):
if idx == i + 1:
q[j] = ch
u = ''.join(q)
# 步骤2与密钥进行 XOR
v = ''
max_len = min(len(u), len(key_str))
for x in range(0, max_len, 2):
u_val = int(u[x:x + 2], 16)
p_val = int(key_str[x:x + 2], 16)
xor_result = u_val ^ p_val
v += f"{xor_result:02x}"
return v
def extract_params(js_content):
"""从 JS 代码中提取所有关键参数"""
# 解码字符串表
decoded_strings, raw_strings = decode_a0j_strings(js_content)
# 提取 arg1兼容空格和压缩格式
arg1_match = re.search(r"var arg1\s*=\s*'([A-Fa-f0-9]+)'", js_content)
arg1 = arg1_match.group(1) if arg1_match else ''
# 提取 m 数组(兼容空格和压缩格式)
m_match = re.search(r"var m\s*=\s*\[(.*?)\]", js_content)
m_arr = []
if m_match:
m_str = m_match.group(1)
m_arr = [int(x.strip(), 16) for x in m_str.split(',')]
# 查找密钥
# 注意a0j 函数有自修改机制,实际密钥可能不在 0x115
# 通过长度判断哪个是十六进制密钥(应该是 40 位数字字符串)
key_p = ''
for idx, val in decoded_strings.items():
if val and len(val) >= 30 and all(c in '0123456789' for c in val):
key_p = val
break
return {
'arg1': arg1,
'm_arr': m_arr,
'key': key_p,
'decoded_strings': decoded_strings
}
class JM(object): # 聚名网API封装类
token: str # 验证码token
loginToken: str = '' # 登录令牌
@@ -124,7 +232,8 @@ class JM(object): # 聚名网API封装类
'Host': 'www.juming.com', # 主机名
'sec-ch-ua-platform': '"Windows"', # 平台标识
'x-requested-with': 'XMLHttpRequest', # AJAX请求标识
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0', # 用户代理
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',
# 用户代理
'accept': '*/*', # 接受所有类型
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"', # 浏览器标识
'sec-ch-ua-mobile': '?0', # 是否移动端
@@ -139,18 +248,21 @@ class JM(object): # 聚名网API封装类
}
def __init__(self, proxies: Optional[Dict] = None): # 初始化方法
self.acw_sc__v2 = None
self.session = requests.Session() # 创建会话对象
self.session.timeout = REQUEST_TIMEOUT # 设置超时时间
self.session.proxies = proxies # 设置代理
self.base_url = BASE_URL # 设置基础URL
logger.debug(proxies)
def captcha_init(self) -> Tuple[bool, str]: # 初始化滑块验证码
def captcha_init(self, retry_count=0) -> Tuple[bool, str]: # 初始化滑块验证码
"""
初始化滑块验证码
Returns:
Tuple[bool, str]: (是否成功, 消息)
"""
if retry_count > 3: # 限制递归深度
return False, "验证码初始化失败:重试次数过多"
self.fingerprint = random_fingerprint() # 生成随机指纹
data = { # 构建请求数据
"request_id": self.fingerprint, # 请求ID
@@ -162,22 +274,35 @@ class JM(object): # 聚名网API封装类
url = f"{self.base_url}/captcha/init" # 初始化URL
# self.cookie.clear()
# self.cookie.update({'acw_sc__v2': '69d948ed49117d89433d4316a79ada6f00d68f26'})
# self.cookie.update({'acw_sc__v2': '69e9cfd94b5ec072ae8cd67b6f06ff5a0c0e70c8'})
# logger.info(self.cookie)
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie).json() # 发送请求
logger.info(response)
res = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie) # 发送请求
try:
response = res.json()
logger.info(response)
if response['code'] == 1: # 初始化成功
self.captchaId = response['data']['captchaId'] # 保存验证码ID
self.encryptionPublicKey = response['data']['encryptionPublicKey'] # 保存加密公钥
return response['code'] == 1, response['msg'] # 返回结果
if response['code'] == 1: # 初始化成功
self.captchaId = response['data']['captchaId'] # 保存验证码ID
self.encryptionPublicKey = response['data']['encryptionPublicKey'] # 保存加密公钥
return response['code'] == 1, response['msg'] # 返回结果
except:
js_content = res.content.decode('utf8')
params = extract_params(js_content)
logger.info(params)
self.acw_sc__v2 = decrypt_arg1(params['arg1'], params['m_arr'], params['key'])
self.cookie.update({'acw_sc__v2': self.acw_sc__v2})
logger.info(self.cookie)
return self.captcha_init(retry_count + 1)
def captcha_verify(self) -> Tuple[bool, str]: # 验证滑块验证码
def captcha_verify(self, retry_count=0) -> Tuple[bool, str]: # 验证滑块验证码
"""
验证滑块验证码
Returns:
Tuple[bool, str]: (是否成功, 消息)
"""
if retry_count > 3: # 限制递归深度
return False, "验证码验证失败:重试次数过多"
data = { # 构建验证数据
"offset": SLIDE_OFFSET, # 滑块偏移量
"duration": SLIDE_DURATION, # 滑动持续时间
@@ -214,7 +339,7 @@ class JM(object): # 聚名网API封装类
data_str = json.dumps(data, separators=(",", ":")) # 转换为JSON字符串压缩格式
current_dir = os.path.dirname(__file__)
jsFile_path = current_dir+"/sdk_leg_env.js" # JavaScript脚本路径
jsFile_path = current_dir + "/sdk_leg_env.js" # JavaScript脚本路径
if not os.path.exists(jsFile_path): # 检查脚本文件是否存在
logger.error(f"Node.js脚本文件不存在: {jsFile_path}") # 记录错误
@@ -241,7 +366,10 @@ class JM(object): # 聚名网API封装类
url = f"{self.base_url}/captcha/verify" # 验证URL
self.headers['Content-Type'] = 'application/json' # 设置内容类型
response = self.session.post(url, headers=self.headers, data=verify_data, cookies=self.cookie).json() # 发送验证请求
res = self.session.post(url, headers=self.headers, data=verify_data,
cookies=self.cookie) # 发送验证请求
response = res.json() # 解析JSON响应
if response['code'] == 1: # 验证成功
self.token = response['data']['token'] # 保存token
self.session_id = response['data']['session_id'] # 保存会话ID
@@ -249,7 +377,17 @@ class JM(object): # 聚名网API封装类
except subprocess.TimeoutExpired: # 超时异常
logger.error("Node.js脚本执行超时") # 记录错误
return False, "Node.js脚本执行超时" # 返回失败
except requests.exceptions.JSONDecodeError: # JSON解析错误
js_content = res.content.decode('utf8')
params = extract_params(js_content)
logger.info(params)
self.acw_sc__v2 = decrypt_arg1(params['arg1'], params['m_arr'], params['key'])
self.cookie.update({'acw_sc__v2': self.acw_sc__v2})
logger.info(self.cookie)
return self.captcha_verify(retry_count + 1)
except Exception as e: # 其他异常
logger.info(type(e))
logger.error(f"执行Node.js脚本异常: {str(e)}") # 记录错误
return False, f"执行Node.js脚本异常: {str(e)}" # 返回失败
@@ -266,7 +404,8 @@ class JM(object): # 聚名网API封装类
try: # 异常处理
response = self.session.post(url, headers=self.headers).json() # 发送请求
self.token = response['data']["token"] # 保存token
return response['code'] == 1, response['msg'], response['data']['url'] if response['code'] == 1 else None # 返回结果
return response['code'] == 1, response['msg'], response['data']['url'] if response[
'code'] == 1 else None # 返回结果
except Exception as e: # 异常处理
return False, f"请求失败: {str(e)}", None # 返回失败
@@ -320,7 +459,10 @@ class JM(object): # 聚名网API封装类
msg = result.get("msg", "登录接口未返回消息") # 获取消息
if code == 1: # 登录成功
self.cookie.clear()
self.cookie = response.cookies # 保存Cookie
self.cookie.update({'acw_sc__v2': self.acw_sc__v2})
logger.info(self.cookie)
return True, msg # 返回成功
elif code == -118: # token过期
@@ -337,7 +479,8 @@ class JM(object): # 聚名网API封装类
except Exception as e: # 异常处理
return False, f"请求失败: {str(e)}" # 返回失败
def ykj_get_list(self, page: int = 1, page_size: int = 50, data: Optional[Dict] = None) -> Tuple[bool, Any]: # 获取一口价域名列表
def ykj_get_list(self, page: int = 1, page_size: int = 50, data: Optional[Dict] = None) -> Tuple[
bool, Any]: # 获取一口价域名列表
"""
获取一口价域名列表
Args:
@@ -357,6 +500,8 @@ class JM(object): # 聚名网API封装类
res = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie) # 发送请求
response = res.json() # 解析响应
logger.info(response)
if response['code'] == -401: # 需要验证码
captcha_response = False, f'滑块验证码失败,{CAPTCHA_MAX_RETRY}次内未成功' # 默认失败
for _ in range(CAPTCHA_MAX_RETRY): # 循环尝试验证
@@ -373,11 +518,12 @@ class JM(object): # 聚名网API封装类
'sessionId': self.session_id,
}, separators=(",", ":"))
}
return self.ykj_get_list(page, page_size,data=data) # 递归调用
return self.ykj_get_list(page, page_size, data=data) # 递归调用
return captcha_response[0], captcha_response[1] # 返回验证码结果
elif response['code'] == 1 and response.get('data') == 'yzmhuaok': # 验证码通过
self.cookie = res.cookies # 更新Cookie
self.cookie.update(res.cookies)
# self.save_cookies()
return self.ykj_get_list(page, page_size) # 递归调用
elif response['code'] == 1 and response.get('html'): # 成功获取HTML
@@ -402,7 +548,23 @@ class JM(object): # 聚名网API封装类
response = self.session.get(url, timeout=DOWNLOAD_TIMEOUT) # 下载文件
response_text = response.content.decode('utf-8') # 解码内容
lines = response_text.strip().splitlines() # 按行分割
return lines # 返回域名列表
# 过滤掉可能包含的日期和非域名行
domains = []
for line in lines:
line = line.strip()
# 过滤掉空行
if not line:
continue
# 过滤掉日期格式的行YYYY-MM-DD
if re.match(r'^\d{4}-\d{2}-\d{2}$', line):
continue
# 过滤掉可能的标题行
if '域名' in line or 'domain' in line.lower():
continue
domains.append(line)
return domains # 返回过滤后的域名列表
except Exception as e: # 异常处理
logger.error(f"<获取删除域名列表错误>: {str(e)}") # 记录错误
return [] # 返回空列表
@@ -415,7 +577,7 @@ class JM(object): # 聚名网API封装类
"""
with open(filepath, "wb") as f: # 以二进制写入模式打开文件
pickle.dump(self.cookie, f) # 序列化保存
# 保存到Redis
try:
import redis
@@ -453,18 +615,21 @@ class JM(object): # 聚名网API封装类
# m = JM() # 创建JM实例
#
# # 账号密码登录(请替换为您的账号密码)
# email = 'chaofanai1998@gmail.com' # 邮箱账号
# email = 'chaofanai1998@gmail.com' # 邮箱账号
# password = 'llzz123,./' # 密码
#
# logger.info(f"开始登录: {email}") # 记录日志
# login_result = m.user_zh_p_login(email, password) # 执行登录
# logger.info(f"登录结果: {login_result}") # 记录结果
#
# m.save_cookies() # 保存Cookie
#
# if not login_result[0]: # 登录失败
# logger.error("登录失败,程序退出") # 记录错误
# exit(1) # 退出程序
#
# m.load_cookies() # 加载Cookie
# #
# # m.load_cookies() # 加载Cookie
# logger.info(m.cookie)
#
# # 获取一口价域名
# import re # 导入正则表达式模块

Binary file not shown.