convert domainCheck to regular directory

This commit is contained in:
Your Name
2026-04-16 13:33:06 +08:00
parent e406d73334
commit d37c444929
107 changed files with 254185 additions and 1 deletions

View File

@@ -0,0 +1,155 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :aizhan.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/3/29 20:59
@explain : 爱站网域名查询工具 - 获取域名的网站标题信息
'''
import re # 正则表达式模块用于从HTML中提取网站标题
import requests # HTTP请求库用于发送网络请求
from loguru import logger # 日志记录模块,用于记录程序运行日志
from typing import List, Optional # 类型提示
def check_aizhan(domain: str, sensitive_words: Optional[List[str]] = None, proxies: dict = None):
'''
查询域名的网站标题信息是否存在敏感词
通过爱站网aizhan.com查询域名的网站标题
:param domain: 待查询的域名www.baidu.com不带协议头
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
:return: str - 网站标题字符串,如果查询失败则返回空字符串
'''
# 初始化敏感词列表
if sensitive_words is None:
sensitive_words = []
# 构建爱站网查询URL
# 格式https://www.aizhan.com/cha/{域名}/
url = f'https://www.aizhan.com/cha/{domain}/'
# 构建HTTP请求头模拟浏览器访问
headers = {
'Accept': '*/*', # 接受所有类型的内容
'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',
# 用户代理(浏览器标识)
'Referer': url, # 来源页面(防盗链)
'host': 'www.aizhan.com', # 目标主机地址
}
# 添加重试机制
max_retries = 3
for retry in range(max_retries):
try:
# 发送GET请求获取页面内容
# url: 请求URL
# headers: HTTP请求头
# timeout=10: 设置超时时间10秒
# proxies: 代理配置(如需使用代理)
response = requests.get(url, headers=headers, timeout=10, proxies=proxies)
# 判断请求是否成功HTTP 200表示成功
if response.status_code == 200:
# 使用正则表达式提取网站标题
# 正则解释:匹配 id="webpage_title"> 开头,</div> 结尾,中间的内容
# 匹配模式id="webpage_title">标题内容</div>
# ([^<]+) 表示匹配一个或多个非<字符,作为捕获组
match = re.search(r'id="webpage_title">([^<]+)</div>', response.text)
# 如果找到匹配则返回标题文本,否则返回空字符串
title = match.group(1) if match else ''
if title:
for sensitive_word in sensitive_words:
# 使用正则表达式检查是否包含敏感词
if re.search(sensitive_word, title, re.IGNORECASE):
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {title}')
return False, f'检测到敏感词: {sensitive_word} 在结果: {title}'
return True, title
# 请求失败,继续重试
logger.warning(f"爱站网检测第{retry+1}次失败: {domain}, 状态码: {response.status_code}")
if retry < max_retries - 1:
import time
time.sleep(2) # 等待2秒后重试
else:
# 达到最大重试次数,尝试不使用代理
if proxies:
logger.info('爱站网检测失败,尝试不使用代理')
try:
response = requests.get(url, headers=headers, timeout=10, proxies=None)
if response.status_code == 200:
match = re.search(r'id="webpage_title">([^<]+)</div>', response.text)
title = match.group(1) if match else ''
if title:
for sensitive_word in sensitive_words:
if re.search(sensitive_word, title, re.IGNORECASE):
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {title}')
return False, f'检测到敏感词: {sensitive_word} 在结果: {title}'
return True, title
return True, ''
except Exception as e2:
logger.error(f"不使用代理的爱站网检测也失败: {domain}, 错误: {e2}")
return False, str(e2)
return True, ''
except Exception as e: # 捕获所有异常(网络错误、超时等)
logger.warning(f"爱站网检测第{retry+1}次失败: {domain}, 错误: {e}")
if retry < max_retries - 1:
import time
time.sleep(2) # 等待2秒后重试
else:
# 达到最大重试次数,尝试不使用代理
if proxies:
logger.info('爱站网检测失败,尝试不使用代理')
try:
response = requests.get(url, headers=headers, timeout=10, proxies=None)
if response.status_code == 200:
match = re.search(r'id="webpage_title">([^<]+)</div>', response.text)
title = match.group(1) if match else ''
if title:
for sensitive_word in sensitive_words:
if re.search(sensitive_word, title, re.IGNORECASE):
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {title}')
return False, f'检测到敏感词: {sensitive_word} 在结果: {title}'
return True, title
return True, ''
except Exception as e2:
logger.error(f"不使用代理的爱站网检测也失败: {domain}, 错误: {e2}")
return False, str(e2)
return False, str(e)
# # ========================= 【测试代码】 =========================
# if __name__ == '__main__':
# # 测试用例1查询常见域名
# logger.info("=== 测试爱站网查询 ===")
# test_domain_1 = 'baidu.com'
# logger.info(f"查询域名: {test_domain_1}")
#
# result_1 = check_aizhan(test_domain_1)
# logger.info(f"网站标题: {result_1}")
# logger.info("")
#
# # 测试用例2查询另一个域名
# logger.info("=== 测试查询QQ域名 ===")
# test_domain_2 = 'qq.com'
# logger.info(f"查询域名: {test_domain_2}")
#
# result_2 = check_aizhan(test_domain_2)
# logger.info(f"网站标题: {result_2}")
# logger.info("")
#
#
#
# # 测试用例4查询可能不存在的域名
# logger.info("=== 测试不存在域名 ===")
# test_domain_4 = 'nonexistentdomain123456.com'
# logger.info(f"查询域名: {test_domain_4}")
#
# result_4 = check_aizhan(test_domain_4)
# logger.info(f"网站标题: {result_4 if result_4 else '(无结果)'}")

263
domainCheck/detect/baidu.py Normal file
View File

@@ -0,0 +1,263 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :baidu.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/3/29 21:54
@explain : 百度域名检测工具 - 包含百度搜索结果查询和百度安全API检测功能
'''
import random # 随机数生成模块,用于随机选择浏览器类型
import re # 正则表达式模块用于从HTML中提取搜索结果
import time
from loguru import logger # 日志记录模块,用于记录程序运行日志
from curl_cffi import requests # curl_cffi库支持浏览器模拟的HTTP请求
from typing import List, Optional, Union, Any # 类型提示
def check_site(domain: str, sensitive_words: Optional[List[str]] = None, proxies: dict = None) -> Union[
None, tuple[bool, str], list[Any]]:
'''
通过百度搜索查询域名的相关信息
使用curl_cffi模拟浏览器访问百度搜索获取site:domain的搜索结果标题
:param domain: 待查询的域名www.baidu.com
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
:return: list - 搜索结果标题列表,如果查询失败则返回空列表
'''
# 初始化敏感词列表
if sensitive_words is None:
sensitive_words = []
# 添加重试机制
max_retries = 3
for retry in range(max_retries):
try: # try-except块用于捕获网络请求中的异常
# 构建搜索查询参数
# wd: 搜索关键词格式为site:域名
params = {
'wd': f'site:{domain}' # 搜索site:domain限制搜索结果为指定域名
}
cookies = {
'BIDUPSID': '053DBE4D820C6EFB729DC7B13B1F82B2',
'PSTM': '1775657119',
'H_PS_PSSID': '63148_67862_67986_68002_68142_68148_68152_68141_68165_68189_68226_68267_68296_68336_68369_68453_68438_68464_68541_68546_68558_68520_68589_68621_68615_68606_68601_68682_68671_68735_68544_68733_68766_68807_68901_68918_68836_68921_68955_68976_68997_69007_69010_69018_69024_69014',
'BAIDUID': '053DBE4D820C6EFB729DC7B13B1F82B2:FG=1',
'BDORZ': 'B490B5EBF6F3CD402E515D22BCDA1598',
'BAIDUID_BFESS': '053DBE4D820C6EFB729DC7B13B1F82B2:FG=1',
'BA_HECTOR': 'ah0l24210la4050l0g0k0g2g0h20071ktcocj27',
'ZFY': 'Qxyr75Xm7o8zUxYzuFnYoW7cnS:AnVp:BpnrNVkr3usno:C',
'delPer': '0',
'BAIDUID_REF': '053DBE4D820C6EFB729DC7B13B1F82B2:FG=1',
'H_WISE_SIDS': '110085_661771_667681_673676_683089_682564_685373_660925_687556_686285_690306_690303_690478_690576_690334_691545_685595_690883_692754_692777_692118_692910_693221_693364_693403_693656_693941_694124_693392_694171_694236_693996_694316_694324_693886_694409_694379_694696_694780_694783_692006_694884_694918_694996_694991_695032_695118_694840_695140_695193_695110_695262_688610_694933_695278_694865_695214_695374_690651_694577_692378_695452_695457_695480_695603_695642_695631_695668_695824_695843_695866_695888_695892_695897_695975_694177_695900_695939_696145_696150_696153_696069_696078_696066_694359_696288_696308_694985_696297_695715_696128_696313_696457_696431_696473_696492_696590_693385_696610_696682_696679_696662_696651_696655_696658_696673_696643_696110_696729_696733_696772_8000116_8000133_8000138_8000159_8000163_8000167_8000176_8000186_8000190_8000204',
'H_WISE_SIDS_BFESS': '110085_661771_667681_673676_683089_682564_685373_660925_687556_686285_690306_690303_690478_690576_690334_691545_685595_690883_692754_692777_692118_692910_693221_693364_693403_693656_693941_694124_693392_694171_694236_693996_694316_694324_693886_694409_694379_694696_694780_694783_692006_694884_694918_694996_694991_695032_695118_694840_695140_695193_695110_695262_688610_694933_695278_694865_695214_695374_690651_694577_692378_695452_695457_695480_695603_695642_695631_695668_695824_695843_695866_695888_695892_695897_695975_694177_695900_695939_696145_696150_696153_696069_696078_696066_694359_696288_696308_694985_696297_695715_696128_696313_696457_696431_696473_696492_696590_693385_696610_696682_696679_696662_696651_696655_696658_696673_696643_696110_696729_696733_696772_8000116_8000133_8000138_8000159_8000163_8000167_8000176_8000186_8000190_8000204',
'MSA_PBT': '147',
'MSA_ZOOM': '1000',
'wpr': '0',
'COOKIE_SESSION': '0_0_0_0_0_0_0_0_0_0_0_0_0_1775657396%7C1%230_0_0_0_0_0_0_0_1775657396%7C1',
'MSA_PHY_WH': '1440_3440',
'POLYFILL': '0',
'MSA_WH': '1254_940',
'kleck': '7ce2abac229d2e8ba435a8bcc6256f57f53e9d08954443bf',
'PSCBD': '16%3A1%3A3',
'SE_LAUNCH': '5%3A1775657396_16%3A29594323%3A3',
'BDSVRTM': '3',
'PSINO': '6',
'__bsi': '17976785662214065461_00_7_R_R_6_0303_c02f_Y',
}
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'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',
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
# 百度搜索URL
url = 'https://m.baidu.com/s'
# 发送GET请求获取搜索结果
# url: 请求URL
# params: 查询参数
# headers: HTTP请求头
# timeout=15: 超时时间15秒
# proxies: 代理配置
# impersonate: 模拟的浏览器类型
# .content: 获取响应内容(字节流)
# .decode('utf-8'): 解码为UTF-8字符串
response_text = requests.get(url, params=params, headers=headers, cookies=cookies, timeout=15,
proxies=proxies).content.decode('utf-8')
# 使用正则表达式提取搜索结果标题
# re.findall()返回所有匹配的结果列表
# 正则解释:匹配 <!--s-text--> 和 <!--/s-text--> 之间的内容
match = re.findall(r'<!--s-text-->([^<]+)<!--/s-text-->', response_text,re.S)
# 返回匹配结果列表,如果没有匹配则返回空列表
search_results= match if match else []
# 如果没有搜索结果,直接返回通过
if not search_results:
return True, ''
# 检查每个搜索结果是否包含敏感词
for result in search_results:
for sensitive_word in sensitive_words:
# 使用正则表达式检查是否包含敏感词
if re.search(sensitive_word, result, re.IGNORECASE):
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {result}')
return False, f'检测到敏感词: {sensitive_word} 在结果: {result}'
# 所有搜索结果都不包含敏感词,返回通过
return True, ''
except Exception as e: # 捕获所有异常
# logger.warning(f"百度site检测第{retry+1}次失败: {domain}, 错误: {e}")
if retry < max_retries - 1:
import time
time.sleep(2) # 等待2秒后重试
proxies=None
else:
# 达到最大重试次数,返回检测失败
logger.error(f"百度site检测失败: {domain}, 已达到最大重试次数")
return False, str(e) # 返回错误信息
def baidu(domain: str, proxies: dict = None) -> str:
'''
通过百度安全API检测域名安全状态
调用百度移动安全API检测域名是否为风险网站
:param domain: 待检测的域名www.baidu.com
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
:return: str - 安全状态描述("风险网站提示""高危网站提示""正常"),如果检测失败则返回错误信息
'''
# 构建HTTP请求头
headers = {
'Cache-Control': 'no-cache', # 不使用缓存
'Content-Type': 'application/json; charset=utf-8', # 内容类型为JSON
'Host': 'mobsec-sec.baidu.com', # 目标主机
'User-Agent': 'okhttp/3.12.12', # 模拟OKHttp客户端
}
# 构建请求参数
params = {
'auth_ver': '2', # 认证版本
'appkey': '4665fd2c6b0922a551e8ae74', # API应用密钥
'nonce': '1751309255340', # 随机数(注意:实际使用时应该动态生成)
'lc': '77qHTv4VtmRiXYtd', # 本地配置参数
'pkg': 'com.baidu.searchbox', # 应用包名
'vc': '-1', # 版本码
'cuid': 'CF0E6FCCD824D146605C16AC1C8BAC95%7CVFWAO56TC', # 设备ID注意实际使用时应该动态生成
'tk': '', # 令牌(空)
'type': '3', # 检测类型
'vn': '2.6.0', # 版本号
's': 'fdfef2ce96c1c7193f9b80425020f63e', # 签名(注意:实际使用时应该动态生成)
}
# 构建JSON请求数据
json_data = {
'url': f'https://{domain}/', # 待检测的URL添加https协议头
}
# 提取URL作为后续获取结果的键
key = json_data['url']
# 添加重试机制
max_retries = 3
for retry in range(max_retries):
try: # try-except块用于捕获API调用中的异常
# 发送POST请求到百度安全API
# url: API接口地址
# params: 查询参数
# headers: HTTP请求头
# json: JSON请求数据
# proxies: 代理配置
# .json(): 解析JSON响应
response = requests.post('https://mobsec-sec.baidu.com/3.1/scanurl', params=params, headers=headers,
json=json_data, proxies=proxies, timeout=15).json()
# # 记录API响应调试用
# logger.info(response)
# 安全状态码映射表
msgs = {
2193: '风险网站提示', # 风险网站
2243: '高危网站提示', # 高危网站
'_': '正常' # 默认正常
}
# 从响应中获取安全等级并映射为状态描述
# response['response']['datas'][key].get('grand_level', 'level'): 获取安全等级,默认值为'level'
# msgs.get(): 根据安全等级获取对应的状态描述,默认值为'正常'
return msgs.get(response['response']['datas'][key].get('grand_level', 'level'), '正常')
except Exception as e: # 捕获所有异常
logger.warning(f"百度网页安全中心检测第{retry+1}次失败: {domain}, 错误: {e}")
if retry < max_retries - 1:
time.sleep(2) # 等待2秒后重试
else:
# 达到最大重试次数,尝试不使用代理
if proxies:
logger.info('百度网页安全中心检测失败,尝试不使用代理')
try:
response = requests.post('https://mobsec-sec.baidu.com/3.1/scanurl', params=params, headers=headers,
json=json_data, proxies=None, timeout=15).json()
msgs = {
2193: '风险网站提示', # 风险网站
2243: '高危网站提示', # 高危网站
'_': '正常' # 默认正常
}
return msgs.get(response['response']['datas'][key].get('grand_level', 'level'), '正常')
except Exception as e2:
logger.error(f"不使用代理的百度网页安全中心检测也失败: {domain}, 错误: {e2}")
return str(e2) # 返回错误信息
return str(e) # 返回错误信息
# # ========================= 【测试代码】 =========================
# if __name__ == '__main__': # 程序入口,当直接运行此文件时执行以下代码
# # proxies = get_proxies()
# proxies = None
# # 测试用例1查询域名搜索结果
# logger.info("=== 测试百度搜索结果查询 ===")
# test_domain_1 = '920zl.com'
# logger.info(f"查询域名: {test_domain_1}")
#
# result_1 = check_site(test_domain_1, proxies=proxies)
# logger.info(result_1)
#
# # 测试用例3检测域名安全状态
# logger.info("=== 测试域名安全检测 ===")
# test_domain_3 = 'baidu.com'
# logger.info(f"检测域名: {test_domain_3}")
#
# result_3 = baidu(test_domain_3, proxies=proxies)
# logger.info(f"安全状态: {result_3}")
# logger.debug(proxies)
# domains = ['Lqyingye.com', 'tjjinLikeji.com', 'guokangLxs.com', 'zhuoyijz.com', 'jiandanrongyi.com', 'fromhhc.com',
# 'djxow.com', '725game.com', 'gcLpchd.cn', '6rdb.com.cn', 'sunmantech.com', 'hroxa.com', 'guLhu65.cn',
# 'xxjdch.com', '7e7fm1.cn', 'kaoxueb.com', 'jabas.cn', 'shzhongyi.com', 'figuangze.com', 'eztw9k.cn',
# 'foxok.com', 'oLLbmr.com', 'vwpwwkp.cn', 'cbmrs.com', 'zaozhuang56.com', 'hengxiangracing.com',
# 'jdyfbpq.cn', 'czca.cn', 'shibingxiongdi.com', 'ubxbm.cn', 'ydbaoshi.com', '2046hd.cn', 'mffmcp.com',
# 'hgLdhzz.com', 'okswmi.cn', 'caidaozg.com', 'dkdywx.cn', 'qkjps.com', 'Lwxgk.cn', 'drtechnoLogy.cn',
# 'akcie.cn', '588uu.com', 'kuaiyijian.net', 'c5b.cn', 'faka866.cn', 'wiraf03.cn', 'bobjibar.com',
# '166233.cn', 'adLfjcjq.cn', 'whrsom.com']
# for domain in domains:
# result_1 = check_site(domain)
# logger.info(f"搜索结果标题数: {len(result_1)}")
# for i, title in enumerate(result_1, 1):
# logger.info(f" 结果{i}: {title}")
# time.sleep(1)

191
domainCheck/detect/c360.py Normal file
View File

@@ -0,0 +1,191 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :c360.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/4/2 15:57
@explain : 360搜索敏感词检测工具 - 检测域名是否包含敏感词
'''
import re # 正则表达式模块
import requests # HTTP请求库
from loguru import logger # 日志记录
from typing import List, Tuple, Optional, Dict # 类型提示
# 常量定义
SO_SEARCH_URL = 'https://www.so.com/s' # 360搜索URL
SO_REFERER_TEMPLATE = 'https://www.so.com/s?ie=utf-8&q=site%3A{domain}' # Referer模板
SEARCH_PATTERN = r'target="_blank">([^<]+)</a></h3>' # 搜索结果匹配模式
BLOCKED_CODE = 3 # 拦截状态码
def check_domain(
domain: str,
sensitive_words: Optional[List[str]] = None,
proxies: Optional[Dict] = None
) -> Tuple[bool, str]:
"""
检查域名是否包含敏感词
通过360搜索获取域名的搜索结果然后检查搜索结果中是否包含敏感词
Args:
domain: 要检测的域名
sensitive_words: 敏感词列表如果为None则初始化为空列表
proxies: 代理配置
Returns:
Tuple[bool, str]: (是否通过, 错误信息)
- True, '': 通过检测,不包含敏感词
- False, '敏感词xxx': 包含敏感词,返回具体的敏感词
"""
# 初始化敏感词列表
if sensitive_words is None:
sensitive_words = []
# 构建请求头
headers = {
'Accept': '*/*', # 接受所有类型
'Accept-Language': 'zh-cn', # 中文语言
'Referer': SO_REFERER_TEMPLATE.format(domain=domain), # 来源页面
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36', # 用户代理
'Host': 'www.so.com', # 主机名
}
# 构建查询参数
params = {
'ie': 'utf-8', # 编码格式
'q': f'site:{domain}', # 搜索查询
}
# 添加重试机制
max_retries = 3
for retry in range(max_retries):
try:
# 发送GET请求获取搜索结果
response = requests.get(
SO_SEARCH_URL,
params=params,
headers=headers,
proxies=proxies,
timeout=15 # 增加超时时间到15秒
)
# 解析响应内容
response_html = response.content.decode('utf-8', errors='ignore')
# 使用正则表达式提取搜索结果
search_results = re.findall(SEARCH_PATTERN, response_html)
# 记录搜索结果数量
logger.info(f'360搜索结果数量: {len(search_results)}')
# 如果没有搜索结果,直接返回通过
if not search_results:
return True, ''
# 检查每个搜索结果是否包含敏感词
for result in search_results:
for sensitive_word in sensitive_words:
# 使用正则表达式检查是否包含敏感词
if re.search(sensitive_word, result, re.IGNORECASE):
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {result}')
return False, f'检测到敏感词: {sensitive_word} 在结果: {result}'
# 所有搜索结果都不包含敏感词,返回通过
return True, ''
except requests.exceptions.Timeout:
logger.warning(f'360搜索请求第{retry+1}次超时')
if retry < max_retries - 1:
import time
time.sleep(2) # 等待2秒后重试
else:
# 达到最大重试次数,尝试不使用代理
if proxies:
logger.info('360搜索请求超时尝试不使用代理')
try:
response = requests.get(
SO_SEARCH_URL,
params=params,
headers=headers,
proxies=None,
timeout=15
)
response_html = response.content.decode('utf-8', errors='ignore')
search_results = re.findall(SEARCH_PATTERN, response_html)
logger.info(f'不使用代理的360搜索结果数量: {len(search_results)}')
if not search_results:
return True, ''
for result in search_results:
for sensitive_word in sensitive_words:
if re.search(sensitive_word, result, re.IGNORECASE):
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {result}')
return False, f'检测到敏感词: {sensitive_word} 在结果: {result}'
return True, ''
except Exception as e:
logger.error(f'不使用代理的360搜索请求也失败: {str(e)}')
return False, f'360搜索请求超时'
return False, '360搜索请求超时'
except requests.exceptions.RequestException as e:
error_str = str(e)
logger.warning(f'360搜索请求第{retry+1}次失败: {error_str}')
# 检查是否是"Too many open files"错误
if 'Too many open files' in error_str:
if retry < max_retries - 1:
import time
time.sleep(2) # 等待2秒后重试
else:
# 达到最大重试次数返回failure标记不拉黑域名
logger.error(f'360搜索请求失败已达到最大重试次数: {error_str}')
return True, 'failure'
else:
if retry < max_retries - 1:
import time
time.sleep(2) # 等待2秒后重试
else:
# 达到最大重试次数,尝试不使用代理
if proxies:
logger.info('360搜索请求失败尝试不使用代理')
try:
response = requests.get(
SO_SEARCH_URL,
params=params,
headers=headers,
proxies=None,
timeout=15
)
response_html = response.content.decode('utf-8', errors='ignore')
search_results = re.findall(SEARCH_PATTERN, response_html)
logger.info(f'不使用代理的360搜索结果数量: {len(search_results)}')
if not search_results:
return True, ''
for result in search_results:
for sensitive_word in sensitive_words:
if re.search(sensitive_word, result, re.IGNORECASE):
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {result}')
return False, f'检测到敏感词: {sensitive_word} 在结果: {result}'
return True, ''
except Exception as e2:
logger.error(f'不使用代理的360搜索请求也失败: {str(e2)}')
return False, f'360搜索请求失败: {str(e)}'
return False, f'360搜索请求失败: {str(e)}'
except Exception as e:
logger.warning(f'360搜索第{retry+1}次未知错误: {str(e)}')
if retry < max_retries - 1:
import time
time.sleep(2) # 等待2秒后重试
else:
return False, f'未知错误: {str(e)}'
# if __name__ == '__main__':
# # 测试用例
# test_domain = 'niuniushouka.com'
# test_sensitive_words = ['赌博', '色情', '暴力','收卡']
#
# logger.info(f'开始检测域名: {test_domain}')
# result = check_domain(test_domain, test_sensitive_words)
# logger.info(f'检测结果: {result}')

1257
domainCheck/detect/chinaz.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,242 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :chinaz.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/3/27 23:48
@explain : 站长工具SEO查询工具 - 获取域名网站标题、SEO排名等信息
'''
import json # JSON处理模块用于解析API响应数据
import os # 操作系统接口模块,用于获取脚本所在目录路径
import re # 正则表达式模块,用于提取网页中的标题和加密密钥
import time # 时间处理模块,用于生成时间戳
from typing import List, Optional # 类型提示
import quickjs # JavaScript执行引擎用于执行JS加密代码生成签名
import requests # HTTP请求库用于发送网络请求
from loguru import logger # 日志记录模块,用于记录程序运行日志
def check_title(domain: str,sensitive_words: Optional[List[str]] = None, proxies: dict = None):
'''
检测域名标题并获取SEO数据
:param domain: 待检测域名www.baidu.com不带协议头
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
:return: Tuple[bool, str, Tuple[bool, Any]] - (是否成功, 消息, (SEO查询是否成功, SEO数据或错误信息))
'''
# 初始化敏感词列表
if sensitive_words is None:
sensitive_words = []
# 添加重试机制
max_retries = 3
for retry in range(max_retries):
try:
# 构建SEO查询URL站长工具网站
url = f'https://seo.chinaz.com/{domain}'
# 发送GET请求获取页面内容设置超时10秒
response = requests.get(url, timeout=10, proxies=proxies)
# 判断请求是否成功HTTP 200表示成功
if response.status_code == 200:
# 使用正则表达式提取网站标题从id="site_title"的div标签中提取文本内容
# 正则解释:匹配 id="site_title"> 开头,</div> 结尾,中间的内容
match = re.search(r'id="site_title">([^<]+)</div>', response.text)
# 使用正则表达式提取加密密钥enkey用于后续API请求的身份验证
# 正则解释:匹配 var enkey = '...' 中的单引号内容
pattern = r"var enkey = '([^']+)''"
match_enkey = re.search(pattern, response.text)
# 如果上面的正则匹配失败,尝试匹配正确的单引号版本
if not match_enkey:
pattern = r"var enkey = '([^']+)" # 匹配 var enkey = '...' 格式
match_enkey = re.search(pattern, response.text)
# 如果找到enkey则提取并去除首尾空格否则为空字符串
enkey = match_enkey.group(1).strip() if match_enkey else ''
# 提取网站标题,如果找到则返回,否则返回空字符串
title = match.group(1).strip() if match else ''
if title:
for sensitive_word in sensitive_words:
# 使用正则表达式检查是否包含敏感词
if re.search(sensitive_word, title, re.IGNORECASE):
logger.warning(f'检测到敏感词: {sensitive_word} 在结果: {title}')
return False, f'检测到敏感词: {sensitive_word} 在结果: {title}', ()
# 调用get_site_data获取详细SEO数据
success, result = get_site_data(domain, enkey, proxies=proxies)
# 检查网站分类
if success and result:
# 定义需要拉黑的分类
blacklist_categories = ['视频电影', '体育运动', '常用查询', '游戏网站', '游戏', '视频', '体育']
# 检查分类是否在黑名单中
if isinstance(result, dict):
# 检查可能的分类字段
category_fields = ['Category', 'category', '分类', '网站分类']
for field in category_fields:
if field in result:
category = result[field]
if isinstance(category, str):
for blacklist_category in blacklist_categories:
if blacklist_category in category:
logger.warning(f'检测到黑名单分类: {category} 在域名: {domain}')
return False, f'网站分类: {category} 属于黑名单分类', ()
# 检查Result是否为列表
elif isinstance(result, list):
for item in result:
if isinstance(item, dict):
category_fields = ['Category', 'category', '分类', '网站分类']
for field in category_fields:
if field in item:
category = item[field]
if isinstance(category, str):
for blacklist_category in blacklist_categories:
if blacklist_category in category:
logger.warning(f'检测到黑名单分类: {category} 在域名: {domain}')
return False, f'网站分类: {category} 属于黑名单分类', ()
return True, 'success', (success, result)
# 请求失败返回空标题和空元组
return True, '', ()
except Exception as e: # 捕获所有异常
error_str = str(e)
# 检查是否是需要重试的错误类型
if any(error_type in error_str for error_type in ['Too many open files', 'EOF occurred in violation of protocol', 'Max retries exceeded', 'Read timed out']):
logger.warning(f"站长工具检测第{retry+1}次失败: {domain}, 错误: {e}")
if retry < max_retries - 1:
time.sleep(2) # 等待2秒后重试
continue
else:
# 达到最大重试次数,返回失败但不拉黑
logger.error(f"站长工具检测失败,已达到最大重试次数: {domain}, 错误: {e}")
return True, 'failure', (False, str(e))
else:
# 其他错误直接返回
logger.error(f"站长工具检测失败: {domain}, 错误: {e}")
return False, str(e), ()
# ========================= 【最终请求】 =========================
def get_site_data(domain, enkey: str, proxies: dict = None):
'''
获取站点的详细SEO数据
通过站长工具API获取域名的SEO排名、权重等信息
:param domain: 待检测域名www.baidu.com
:param enkey: 加密密钥从check_title函数获取
:param proxies: 代理配置(如:{'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
:return: Tuple[bool, Any] - (是否成功, 结果数据或错误信息)
- (True, Result数据): 查询成功
- (False, 错误信息): 查询失败
'''
try:
# 获取当前脚本所在目录用于构建JS文件的完整路径
current_dir = os.path.dirname(__file__)
# 构建JS加密代码文件的完整路径
js_path = os.path.join(current_dir, "chinaz.js")
# 读取JS加密代码文件包含生成签名的函数
with open(js_path, "r", encoding="utf-8") as f:
js_code = f.read()
# 创建JavaScript执行上下文QuickJS引擎
ctx = quickjs.Context()
# 【关键步骤】加载JS代码到上下文使其可以被调用
# 这一步必须先执行,否则后续的函数调用会失败
ctx.eval(js_code)
# 调用JS函数生成host_key主机密钥是生成签名的基础
# 格式generateHostKey("domain")
host_key = ctx.eval(f'generateHostKey("{domain}")')
# 获取当前时间戳(毫秒),用于防止缓存和重放攻击
ts = str(int(time.time() * 1000))
# 构建请求参数字典包含API所需的全部参数
params = {
# jQuery回调函数名格式jQuery + 随机数字 + 时间戳)
"callback": f"jQuery11130943805094955342_{int(time.time() * 1000) - 1000}",
"action": "GetCategory", # 动作类型:获取分类/排名数据
"host": domain, # 主机域名
"secretkey": enkey, # 加密密钥(从页面获取)
# 随机数字调用JS函数生成与host_key相关
"rd": ctx.eval(f'getRandomNum("{host_key}")'),
"ts": ts, # 时间戳(毫秒)
# MD5令牌调用JS函数生成用于请求签名验证
"token": ctx.eval(f'generateMD5Token("{host_key}","{ts}")'),
"_": int(time.time() * 1000) # 额外时间戳参数(用于缓存破坏)
}
# 构建HTTP请求头模拟浏览器访问
headers = {
'Accept': '*/*', # 接受所有类型的内容
'Accept-Language': 'zh-cn', # 接受的语言:简体中文
'Referer': 'https://othertool.chinaz.com/GetTopRanked.ashx', # 来源页面(防盗链)
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36', # 用户代理(浏览器标识)
'Host': 'othertool.chinaz.com', # 目标主机地址
}
# API请求URL站长工具SEO数据查询接口
url = 'https://othertool.chinaz.com/GetTopRanked.ashx'
# 发送GET请求获取SEO数据
resp = requests.get(
url, # 请求URL
params=params, # 查询参数自动拼接为URL参数
headers=headers, # 请求头
timeout=10, # 超时时间10秒
proxies=proxies # 代理配置
)
# 判断请求是否成功
if resp.status_code == 200:
# 使用正则表达式提取JSON数据API返回格式callback(json_data)
# 正则解释匹配括号内的JSON内容
match = re.search(r'\((.*?)\)', resp.text)
# 提取JSON字符串
json_str = match.group(1)
# 解析JSON数据为Python字典
data = json.loads(json_str)
# 返回状态码和结果StateCode为1表示成功
return data['StateCode'] == 1, data['Result']
except Exception as e: # 捕获所有异常
logger.error(e) # 记录错误日志
return False, str(e) # 返回失败状态和错误信息
# # ========================= 【测试代码】 =========================
# if __name__ == '__main__':
#
#
# # 测试用例3使用代理查询
# logger.info("=== 测试使用代理 ===")
# test_domain_3 = 'tt.com'
#
# logger.info(f"查询域名: {test_domain_3}")
#
#
# title_3, seo_data_3 = check_title(test_domain_3)
# logger.info(f"域名标题: {title_3}")
# logger.info(f"SEO数据: {seo_data_3}")

View File

@@ -0,0 +1,253 @@
# -*- coding:utf-8 -*-
import requests, re, json, base64
import io, os, random
import time, cv2, json
from PIL import Image
from functools import partial
from io import BytesIO
from urllib3.util import proxy
from detect.module.use_ua import randomUA
from detect.module.gap import quekou
import subprocess
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')
import execjs
class slide():
def __init__(self):
self.headers = randomUA()
self.t = round(time.time() * 1000)
def __ease_out_expo(self, sep):
if sep == 1:
return 1
else:
return 1 - pow(2, -10 * sep)
def get_slide_track(self, vdict):
distance = self.huak(vdict)
slide_track = [
[random.randint(-50, -10), random.randint(-50, -10), 0],
[0, 0, 0],
]
count = 30 + int(distance / 2)
t = random.randint(50, 100)
_x = 0
_y = 0
for i in range(count):
x = round(self.__ease_out_expo(i / count) * distance)
t += random.randint(10, 20)
if x == _x:
continue
slide_track.append([x, _y, t])
_x = x
slide_track.append(slide_track[-1])
return slide_track
def tp_huanyuan(self, content):
_img = Image.open(BytesIO(content))
_Ge = [{"x": -157, "y": -58}, {"x": -145, "y": -58}, {"x": -265, "y": -58}, {"x": -277, "y": -58},
{"x": -181, "y": -58}, {"x": -169, "y": -58}, {"x": -241, "y": -58}, {"x": -253, "y": -58},
{"x": -109, "y": -58}, {"x": -97, "y": -58}, {"x": -289, "y": -58}, {"x": -301, "y": -58},
{"x": -85, "y": -58}, {"x": -73, "y": -58}, {"x": -25, "y": -58}, {"x": -37, "y": -58},
{"x": -13, "y": -58}, {"x": -1, "y": -58}, {"x": -121, "y": -58}, {"x": -133, "y": -58},
{"x": -61, "y": -58}, {"x": -49, "y": -58}, {"x": -217, "y": -58}, {"x": -229, "y": -58},
{"x": -205, "y": -58}, {"x": -193, "y": -58}, {"x": -145, "y": 0}, {"x": -157, "y": 0},
{"x": -277, "y": 0}, {"x": -265, "y": 0}, {"x": -169, "y": 0}, {"x": -181, "y": 0},
{"x": -253, "y": 0}, {"x": -241, "y": 0}, {"x": -97, "y": 0}, {"x": -109, "y": 0},
{"x": -301, "y": 0}, {"x": -289, "y": 0}, {"x": -73, "y": 0}, {"x": -85, "y": 0},
{"x": -37, "y": 0}, {"x": -25, "y": 0}, {"x": -1, "y": 0}, {"x": -13, "y": 0},
{"x": -133, "y": 0}, {"x": -121, "y": 0}, {"x": -49, "y": 0}, {"x": -61, "y": 0},
{"x": -229, "y": 0}, {"x": -217, "y": 0}, {"x": -193, "y": 0}, {"x": -205, "y": 0}]
w_sep, h_sep = 10, 58
new_img = Image.new('RGB', (260, 116))
for idx in range(len(_Ge)):
x = abs(_Ge[idx]['x'])
y = 58 if _Ge[idx]['y'] == -58 else 0
img_cut = _img.crop((x, y, x + w_sep, y + h_sep))
new_x = idx % 26 * 10
new_y = 0 if idx < 26 else 58
new_img.paste(img_cut, (new_x, new_y))
img_byte = BytesIO()
new_img.save(img_byte, 'png')
return img_byte.getvalue()
def huak(self, vdict):
count = 1
bgbase64 = ""
tpbase64 = ""
for idv, p_url in vdict.items():
p_url = 'http://static.geetest.com/' + p_url
# print(p_url)
vcode = requests.get(p_url, headers=self.headers)
text = vcode.content
if idv == 'bg':
text = self.tp_huanyuan(text)
bgbase64 = base64.encodebytes(text).decode()
else:
tpbase64 = base64.encodebytes(text).decode()
count += 1
if bgbase64 and tpbase64:
dis = quekou().get_distance(bgbase64, tpbase64)
else:
dis = 0
return dis
class Geetest2():
def __init__(self):
self.header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36",
}
# 获取当前文件的目录
current_dir = os.path.dirname(__file__)
with open(os.path.join(current_dir, 'module', 'crack_geetest2x.js'), "r", encoding="gb2312",
errors='ignore') as f:
js_encrypt = f.read()
self.js = execjs.compile(js_encrypt)
self.t = round(time.time() * 1000)
self.gct_vdi = {}
self.gct_volue = ['', ''] # 初始值
self.token_vdi = {}
self.timeout = (7, 9.05)
self.geetest_type = 'slide'
self.geetest_path = "/static/js/geetest.6.0.9.js"
def new_requests(self):
self.session = requests.session()
self.session.headers = self.header
def get_vsion(self, http, gt, proxies, pparmas):
url = "https://api.geetest.com/gettype.php"
params = {
"gt": gt,
"callback": f"geetest_{self.t}"
}
try:
respe = http.get(url=url, params=params, proxies=proxies).text
except Exception as e:
if proxy:
if pparmas:
vdict = {'result': '错误', 'message': f'代理IP超时!,请重新更换代理IP,{e}', 'proxy': proxies,
'pparmas': pparmas}
else:
vdict = {'result': '错误', 'message': f'代理IP超时!,请重新更换代理IP,{e}', 'proxy': proxies}
else:
vdict = {'result': '错误', 'message': f'请求错误,{e}'}
return vdict
try:
data = json.loads(respe[22:-1])["data"]
except:
vdict = {'result': '错误', 'type': 'auto', 'message': respe}
return vdict
self.geetest_type = data["type"]
self.geetest_path = data['path']
self.token_vdi[gt] = 1
def get_tp(self, gt, challenge, type_, proxies=None, pparmas=None):
self.new_requests()
self.t = round(time.time() * 1000)
if pparmas:
h_list = re.findall('([\w-]+):(.+)', pparmas)
for idx in h_list:
print('新增协议头' + str(idx))
self.session.headers[idx[0]] = idx[1].replace('\r', '').replace('\n', '').replace('\r\n', '')
print(self.session.headers)
if not self.token_vdi.get('gt', ''):
self.get_vsion(self.session, gt, proxies, pparmas)
url = f"https://api.geetest.com/get.php"
params = {
"gt": gt,
"challenge": challenge,
"product": "popup",
"offline": "false",
"protocol": "https://",
"type": self.geetest_type,
"path": self.geetest_path,
"callback": f"geetest_{self.t}"
}
response = self.session.get(url, params=params, timeout=self.timeout).text
geetest_type = self.geetest_type
if geetest_type == 'slide':
data = json.loads(response[22:-1])
else:
try:
data = json.loads(response[22:-1])["data"]
except:
vdict = {'result': '错误', 'type': type_, 'message': response}
return vdict
##print(data)
nc, ns = data['c'], data['s']
if geetest_type == 'slide':
data = json.loads(response[22:-1])
else:
try:
data = json.loads(response[22:-1])["data"]
except:
vdict = {'result': '错误', 'type': type_, 'message': json.loads(response)}
return vdict
##print(data)
nc, ns = data['c'], data['s']
gct_url = 'http://static.geetest.com' + data['gct_path']
##print('gct地址:', gct_url)
if geetest_type == 'click':
return {'result': '请求类型不是滑块', 'type': type_, 'gt': gt, 'challenge': challenge,
'当前类型': '点选'}
if geetest_type == 'slide': # slide
challenge = data['challenge'] # 这里challenge改变了
bg = data['bg'] # 背景图片
slice = data['slice'] # 缺口图片
vdict = {
"bg": bg,
"slice": slice,
}
slide_track = slide().get_slide_track(vdict)
imgload = 37
w = self.js.call('get_slide_w', nc, ns, gt, challenge, slide_track, imgload, self.gct_volue)
params = (
('gt', gt),
('challenge', challenge),
('w', w),
('callback', f"geetest_{self.t}"),
)
response = self.session.get('https://api.geetest.com/ajax.php', params=params, proxies=proxies,
timeout=self.timeout).text
data = json.loads(response[22:-1])
validate = data.get('validate')
vdict = {'result': data.get('message'), 'type': geetest_type, 'gt': gt, 'challenge': challenge,
'validate': validate}
return vdict
if __name__ == '__main__':
a = Geetest2()
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
}
url = "https://seo.juziseo.com/class/gtcode/msg/StartMsgCaptchaServlet.php"
url = "https://gsxt.hlj.gov.cn/registerValidate.jspx"
for i in range(1):
index = requests.get(url, headers=headers)
# print(index.text)
html = index.json()
challenge = html["challenge"]
gt = html["gt"]
print('gt', gt, 'challenge', challenge)
r = a.get_tp(gt, challenge, 'auto', '')
print(r)

557
domainCheck/detect/jucha.py Normal file
View File

@@ -0,0 +1,557 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :jucha.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/4/2 22:49
@explain : 聚查网API封装类 - 优化版,减少冗余代码,添加详细注释
'''
# 导入标准库
import base64 # Base64编码解码
import hashlib # 哈希加密
import json # JSON数据处理
import os # 操作系统接口
import pickle # 序列化反序列化
import random # 随机数生成
import subprocess # 子进程管理
import time # 时间处理
from functools import partial
# 导入第三方库
import requests # HTTP请求库
from loguru import logger # 日志记录
from requests.cookies import RequestsCookieJar # Cookie管理
# # 移除自定义子进程类的替换,避免影响其他模块
# class MySubprocessPopen(subprocess.Popen): # 自定义子进程类
# def __init__(self, *args, **kwargs): # 初始化方法
# kwargs['encoding'] = "UTF-8" # 设置默认编码为UTF-8
# super().__init__(*args, **kwargs) # 调用父类初始化方法
#
#
# subprocess.Popen = MySubprocessPopen # 替换subprocess.Popen为自定义类
os.environ["EXECJS_RUNTIME"] = "Node" # 设置JavaScript运行时环境为Node.js
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8', startupinfo=startupinfo)
def resolve_node_executable() -> str:
current_dir = os.path.dirname(__file__)
project_dir = os.path.dirname(current_dir)
bundled_node = os.path.join(project_dir, "tools", "node-v20.19.4-win-x64", "node.exe")
if os.path.exists(bundled_node):
return bundled_node
return "node"
def calculate_seed(t: int) -> str: # 计算验证码种子值
now = int(time.time()) # 获取当前时间戳
c = now - t # 计算时间差
seed = base64.b64encode(str(c).encode()).decode() # Base64编码
return seed # 返回种子字符串
def random_fingerprint() -> str: # 生成随机设备指纹
data = str(random.random()) + str(time.time()) # 组合随机数和时间戳
return hashlib.sha256(data.encode()).hexdigest() # 返回SHA256哈希值
class JC(object): # 聚查网API封装类
token: str # 验证码token
session_id: str # 会话ID
fingerprint: str # 设备指纹
captchaId: str # 验证码ID
encryptionPublicKey: str # 加密公钥
cookie: RequestsCookieJar = {} # 聚查网Cookie
juming_cookie: RequestsCookieJar = {} # 聚名网Cookie
# 通用请求头配置
headers = {
'accept': 'application/json, text/javascript, */*; q=0.01', # 接受的内容类型
'accept-language': 'zh-CN,zh;q=0.9', # 接受的语言
'content-type': 'application/x-www-form-urlencoded', # 内容类型
'origin': 'https://www.jucha.com', # 请求源
'priority': 'u=1, i', # 请求优先级
'referer': 'https://www.jucha.com/login', # 来源页面
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"', # 浏览器标识
'sec-ch-ua-mobile': '?0', # 是否移动端
'sec-ch-ua-platform': '"Windows"', # 操作系统平台
'sec-fetch-dest': 'empty', # 请求目标
'sec-fetch-mode': 'cors', # 请求模式
'sec-fetch-site': 'same-origin', # 请求站点
'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',
# 用户代理
'x-requested-with': 'XMLHttpRequest', # AJAX请求标识
}
def __init__(self, proxies: dict = None): # 初始化JC类
self.session = requests.Session() # 创建会话对象
self.session.timeout = 10 # 设置超时时间
self.session.proxies = proxies # 设置代理
self.base_url = "https://www.jucha.com" # 设置基础URL
self.cookie = requests.cookies.RequestsCookieJar() # 初始化CookieJar
self.juming_cookie = requests.cookies.RequestsCookieJar() # 初始化聚名网CookieJar
def _get_headers(self, referer: str = None) -> dict: # 获取请求头可自定义referer
headers = self.headers.copy() # 复制默认请求头
if referer: # 如果指定了referer
headers['referer'] = referer # 更新referer
return headers # 返回请求头
def _get_route_headers(self, route: str) -> dict: # 根据路由获取对应的请求头
referer_map = { # 路由到referer的映射
'whois': f'{self.base_url}/whois/', # WHOIS查询的referer
'beian': f'{self.base_url}/baian/', # 备案查询的referer
'safe': f'{self.base_url}/safe/', # 安全检测的referer
}
return self._get_headers(referer_map.get(route)) # 返回对应的请求头
def _handle_captcha(self, retry_times: int = 5) -> tuple: # 处理滑块验证码
for _ in range(retry_times): # 循环重试
init_result = self.captcha_init() # 初始化验证码
if init_result[0]: # 初始化成功
verify_result = self.captcha_verify() # 验证验证码
if verify_result[0]: # 验证成功
return True, '验证码验证成功' # 返回成功
return False, f'滑块验证码失败,{retry_times}次内未成功' # 返回失败
def _check_request(self, url: str, data: dict, headers: dict, cookies=None) -> dict: # 检查请求并处理验证码
res = self.session.post(url=url, data=data, headers=headers, cookies=cookies) # 发送POST请求
try:
response = res.json() # 尝试解析JSON
except Exception as e: # JSON解析失败
logger.error(f"JSON解析失败: {str(e)}") # 记录错误
logger.error(f"原始响应内容前500字符: {res.text[:500]}") # 记录原始响应
return {'code': -1, 'msg': f'JSON解析失败: {str(e)}'} # 返回错误
return response # 返回响应
def _check_and_handle_captcha(self, response: dict, domain: str, route: str, xm_codes,
data: dict = None) -> tuple: # 检查并处理验证码
# 检查response是否为字典
if not isinstance(response, dict):
return False, None, response # 返回失败
if response.get('code') == 1001: # 需要验证码
captcha_result = self._handle_captcha() # 处理验证码
if captcha_result[0]: # 验证码验证成功
new_data = data.copy() if data else {} # 复制数据
new_data.update({ # 更新验证码参数
'_csrf': '', # CSRF令牌
'ymlb': domain, # 域名
'xm_codes[]': xm_codes, # 检测代码
'type': '2' if route in ['beian', 'safe'] else '1', # 类型备案和安全检测为2WHOIS为1
'route': route, # 路由
'captcha_verify_param': self.token, # 验证码token
'sessionId': self.session_id, # 会话ID
})
return True, new_data, None # 返回需要重试
return False, None, captcha_result # 返回失败
return False, None, response # 不需要验证码
def captcha_init(self): # 初始化滑块验证码
self.fingerprint = random_fingerprint() # 生成随机指纹
data = { # 构建请求数据
"request_id": self.fingerprint, # 请求ID
"scene": "default", # 场景
"seed": calculate_seed(286) # 种子值
}
url = f"{self.base_url}/captcha/init" # 初始化URL
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie).json() # 发送请求
if response['code'] == 1: # 初始化成功
self.captchaId = response['data']['captchaId'] # 保存验证码ID
self.encryptionPublicKey = response['data']['encryptionPublicKey'] # 保存加密公钥
return response['code'] == 1, response['msg'] # 返回结果
def captcha_verify(self): # 验证滑块验证码
data = { # 构建验证数据
"offset": 290, # 滑块偏移量
"duration": 611, # 滑动持续时间
"trail": [ # 滑动轨迹
{"x": 0, "y": 0, "time": 0}, {"x": 0, "y": 0, "time": 17}, {"x": 1, "y": 0, "time": 89},
{"x": 5, "y": 0, "time": 106}, {"x": 14, "y": 0, "time": 123}, {"x": 28, "y": 0, "time": 139},
{"x": 47, "y": 0, "time": 156}, {"x": 74, "y": 0, "time": 173}, {"x": 102, "y": 0, "time": 189},
{"x": 131, "y": 0, "time": 206}, {"x": 159, "y": 0, "time": 223}, {"x": 184, "y": 0, "time": 239},
{"x": 205, "y": 0, "time": 256}, {"x": 227, "y": 1, "time": 273}, {"x": 248, "y": 0, "time": 289},
{"x": 263, "y": 0, "time": 306}, {"x": 276, "y": 0, "time": 323}, {"x": 286, "y": 0, "time": 339},
{"x": 290, "y": 0, "time": 356}, {"x": 290, "y": 0, "time": 373}, {"x": 290, "y": 0, "time": 389},
{"x": 290, "y": 0, "time": 406}, {"x": 290, "y": 0, "time": 423},
{"x": 290, "y": 0, "time": 478}
],
"fingerprint": self.fingerprint, # 设备指纹
"captchaId": self.captchaId, # 验证码ID
"serverPublicKey": self.encryptionPublicKey # 服务器公钥
}
data_str = json.dumps(data, separators=(",", ":")) # 转换为JSON字符串
current_dir = os.path.dirname(__file__)
jsFile_path = current_dir + "/sdk_leg_env.js" # JavaScript脚本路径
if not os.path.exists(jsFile_path): # 检查脚本文件是否存在
logger.error(f"Node.js脚本文件不存在: {jsFile_path}") # 记录错误
return False, f"缺少签名脚本: {jsFile_path}" # 返回失败
try: # 尝试执行Node.js脚本
with subprocess.Popen( # 创建子进程
[resolve_node_executable(), jsFile_path], # 执行node命令
stdin=subprocess.PIPE, # 标准输入管道
stdout=subprocess.PIPE, # 标准输出管道
stderr=subprocess.PIPE, # 标准错误管道
text=True, # 文本模式
encoding="utf-8", # UTF-8编码
errors="ignore" # 忽略编码错误
) as proc: # 进程上下文
stdout, stderr = proc.communicate(data_str, timeout=10) # 传入参数并获取输出
verify_data = stdout.strip() # 去除空白字符
if stderr: # 如果有错误输出
logger.error(f'genData_stderr->[{stderr}]') # 记录错误
if proc.returncode != 0: # 检查退出码
return False, f"Node.js脚本执行失败退出码: {proc.returncode}, 错误信息: {stderr}" # 返回失败
url = f"{self.base_url}/captcha/verify" # 验证URL
verify_headers = self.headers.copy() # 复制请求头
verify_headers['Content-Type'] = 'application/json' # 设置内容类型(仅用于验证请求)
verify_response = self.session.post(url, headers=verify_headers, data=verify_data,
cookies=self.cookie) # 发送验证请求
if verify_response.cookies: # 如果响应中有新的Cookie
self.cookie.update(verify_response.cookies) # 合并Cookie而不是替换
response = verify_response.json() # 解析JSON响应
if response['code'] == 1: # 验证成功
self.token = response['data']['token'] # 保存token
self.session_id = response['data']['session_id'] # 保存会话ID
return response['code'] == 1, response['msg'] # 返回结果
except subprocess.TimeoutExpired: # 超时异常
logger.error("Node.js脚本执行超时超过10秒") # 记录错误
return False, "Node.js脚本执行超时" # 返回失败
except Exception as e: # 其他异常
logger.error(f"执行Node.js脚本异常: {str(e)}") # 记录错误
return False, f"执行Node.js脚本异常: {str(e)}" # 返回失败
if not verify_data: # 检查输出是否为空
return False, "Node.js脚本未输出任何内容" # 返回失败
def save_cookies(self, filepath="jucha_cookies.pkl"): # 保存Cookie到文件
with open(filepath, "wb") as f: # 以二进制写入模式打开文件
pickle.dump(self.cookie, f) # 序列化保存Cookie
logger.info(f"已保存Cookie到文件: {filepath}")
# 保存到Redis
try:
import redis
from app.config import config
redis_client = redis.Redis(
host=config.REDIS_HOST,
port=config.REDIS_PORT,
password=config.REDIS_PASSWORD,
db=config.REDIS_DB,
decode_responses=True
)
# 将cookie转换为字典
cookie_dict = {}
# 检查self.cookie的类型
if isinstance(self.cookie, dict):
# 如果是字典,直接使用
cookie_dict = self.cookie
logger.info(f"Cookie是字典类型直接使用")
elif hasattr(self.cookie, '__iter__'):
# 如果是可迭代对象,遍历处理
for cookie in self.cookie:
# 检查cookie对象是否有name和value属性
if hasattr(cookie, 'name') and hasattr(cookie, 'value'):
cookie_dict[cookie.name] = cookie.value
logger.info(f"Cookie是可迭代对象处理后得到: {cookie_dict}")
else:
logger.warning(f"Cookie类型不支持: {type(self.cookie)}")
redis_client.set('domain_tool:jucha_cookies', str(cookie_dict))
except Exception as e:
logger.error(f"保存Cookie到Redis异常: {str(e)}")
pass
def load_cookies(self, filepath="jucha_cookies.pkl"): # 从文件加载Cookie
try: # 尝试加载
with open(filepath, "rb") as f: # 以二进制读取模式打开文件
loaded_cookie = pickle.load(f) # 反序列化加载Cookie
# 检查加载的cookie类型
if isinstance(loaded_cookie, dict):
# 如果是字典转换为RequestsCookieJar
cookie_jar = requests.cookies.RequestsCookieJar()
for name, value in loaded_cookie.items():
cookie_jar.set(name, value)
self.cookie = cookie_jar
else:
self.cookie = loaded_cookie
except Exception as e: # 加载失败
logger.error(f"加载Cookie失败: {e}")
self.cookie = requests.cookies.RequestsCookieJar() # 创建空的CookieJar
def load_juming_cookies(self, filepath="juming_cookies.pkl"): # 从文件加载聚名网Cookie
try: # 尝试加载
with open(filepath, "rb") as f: # 以二进制读取模式打开文件
self.juming_cookie = pickle.load(f) # 反序列化加载Cookie
except: # 加载失败
self.juming_cookie = requests.cookies.RequestsCookieJar() # 创建空的CookieJar
def auth_login(self): # 使用聚名网账号登录聚查网
params = { # 请求参数
'platform': 'juming', # 平台标识
}
res = self.session.get( # 发送GET请求
url=f'{self.base_url}/home/login/get_auth_url', # 获取授权URL
params=params, # 请求参数
headers=self.headers, # 请求头
cookies=self.juming_cookie # 聚名网Cookie
)
if res.cookies: # 如果响应中有Cookie
self.cookie.update(res.cookies) # 更新Cookie
response = res.json() # 解析JSON响应
if response['code'] == 1: # 获取授权URL成功
url = f'%s&tiao=1' % response['data']['auth_url'] # 构建跳转URL
# 合并聚名网Cookie和聚查网Cookie
combined_cookies = requests.cookies.RequestsCookieJar()
combined_cookies.update(self.juming_cookie)
combined_cookies.update(self.cookie)
response = self.session.get( # 发送GET请求
url=url, # 跳转URL
headers=self.headers, # 请求头
cookies=combined_cookies, # 合并后的Cookie
allow_redirects=False # 不自动重定向
)
if response.cookies: # 如果响应中有Cookie
# 安全更新Cookie避免冲突
for name, value in response.cookies.items():
self.cookie[name] = value # 直接赋值覆盖同名Cookie
if response.status_code == 302: # 重定向状态码
url = response.headers['location'] # 获取重定向URL
response = self.session.get( # 发送GET请求
url=url, # 重定向URL
headers=self.headers, # 请求头
allow_redirects=False # 不自动重定向
)
if response.cookies: # 如果响应中有Cookie
# 安全更新Cookie避免冲突
for name, value in response.cookies.items():
self.cookie[name] = value # 直接赋值覆盖同名Cookie
if response.status_code == 302: # 再次重定向
return True, '登录成功' # 返回成功
return False, '聚名登录过期' # 返回失败
return False, response['msg'] # 返回失败
def _build_check_data(self, domain: str, route: str, xm_codes, data: dict = None) -> dict: # 构建检测请求数据
if data is not None: # 如果data不为空
return data # 直接返回data
return { # 构建新的请求数据
'_csrf': '', # CSRF令牌
'ymlb': domain, # 域名
'xm_codes[]': xm_codes, # 检测代码
'type': '2' if route in ['beian', 'safe'] else '1', # 类型备案和安全检测为2WHOIS为1
'route': route, # 路由
}
def _make_check_request(self, domain: str, route: str, xm_codes, data: dict = None) -> tuple: # 发起检测请求
headers = self._get_route_headers(route) # 获取对应的请求头
data = self._build_check_data(domain, route, xm_codes, data) # 构建请求数据
url = f'{self.base_url}/home_item/check' # 检测URL
response = self._check_request(url, data, headers, self.cookie) # 发送请求
return response, data # 返回响应和数据
def _handle_check_response(self, response: dict, domain: str, route: str, xm_codes, data: dict,
callback) -> tuple: # 处理检测响应
need_retry, retry_data, final_response = self._check_and_handle_captcha( # 检查并处理验证码
response, domain, route, xm_codes, data
)
if need_retry: # 需要重试
return callback(domain=domain, data=retry_data) # 递归调用回调
# 检查final_response是否为字典
if not isinstance(final_response, dict):
return False, f'响应格式错误: {final_response}', '' # 返回失败
if final_response['code'] == 1: # 检测成功
return self._get_search_result(final_response, domain, route) # 获取查询结果传入domain
return False, final_response.get('msg', '未知错误'), '' # 返回失败
def _get_search_result(self, response: dict, domain: str, route: str) -> tuple: # 获取查询结果
data = { # 构建查询数据
'_csrf': '', # CSRF令牌
'domain': domain, # 域名注意使用domain而不是ymlb
'rwid': response['data']['rwid'], # 任务ID
'type': '2' if route in ['beian', 'safe'] else '1', # 类型
'route': 'baian' if route == 'beian' else route, # 路由
}
headers = self._get_route_headers(route) # 获取对应的请求头
url = f'{self.base_url}/home/item/search' if route != 'whois' else f'{self.base_url}/home/item/search_one' # 查询URL
search_response = self._check_request(url, data, headers, self.cookie) # 发送查询请求
# 检查search_response是否为字典
if not isinstance(search_response, dict):
return False, f'响应格式错误: {search_response}', '' # 返回失败
if search_response.get('code', -1) != 1: # 查询失败
return False, search_response.get('msg', '查询失败'), '' # 返回失败
if route == 'whois': # WHOIS查询
# 安全获取WHOIS状态
whois_zt = ''
try:
data1 = search_response.get('data', {})
if isinstance(data1, dict):
data2 = data1.get('data', {})
if isinstance(data2, dict):
whois = data2.get('whois', {})
if isinstance(whois, dict):
data3 = whois.get('data', {})
if isinstance(data3, dict):
data4 = data3.get('data', {})
if isinstance(data4, dict):
whois_zt = data4.get('zt', '')
except Exception as e:
logger.error(f"获取WHOIS状态失败: {e}")
return ( # 返回WHOIS结果
True, # 成功
search_response['msg'], # 消息
whois_zt # WHOIS状态
)
elif route == 'beian': # 备案查询
# 安全获取备案数据
beian_data = {}
beian_msg = ''
try:
data1 = search_response.get('data', {})
if isinstance(data1, dict):
data2 = data1.get('data', {})
if isinstance(data2, dict):
beian = data2.get('beian', {})
if isinstance(beian, dict):
data3 = beian.get('data', {})
if isinstance(data3, dict):
beian_data = data3.get('data', {})
beian_msg = data3.get('msg', '')
except Exception as e:
logger.error(f"获取备案数据失败: {e}")
is_dict = isinstance(beian_data, dict) # 是否为字典
return ( # 返回备案结果
True, # 成功
search_response['msg'], # 消息
( # 备案信息元组
beian_data.get('sj', '') if is_dict else '', # 备案时间
beian_data.get('lx', '') if is_dict else '', # 备案类型
beian_data.get('sy', '') if is_dict else '', # 备案首页地址
beian_msg, # 备案状态
)
)
elif route == 'safe': # 安全检测
# 安全获取安全检测数据
safe_data = {}
try:
data1 = search_response.get('data', {})
if isinstance(data1, dict):
safe_data = data1.get('data', {})
except Exception as e:
logger.error(f"获取安全检测数据失败: {e}")
check_items = [ # 检测项配置列表
('qqjc', 'QQ检测'),
('weixin', '微信检测'),
('qiang', '被墙检测'),
('dyjc', '抖音检测'),
('bdjc', '百度检测'),
('llqjcgg', '谷歌检测'),
('llqjchh', '火狐检测'),
]
results = [] # 结果列表
for item_key, _ in check_items: # 遍历检测项
try:
code = 1
msg = ''
item_data = safe_data.get(item_key, {})
if isinstance(item_data, dict):
data1 = item_data.get('data', {})
if isinstance(data1, dict):
code = int(data1.get('data', 1)) # 获取检测码
msg = data1.get('msg', '') # 获取消息
if code == 3: # 如果检测码为3
msg = '拦截' # 设置为拦截
results.append((code, msg)) # 添加到结果列表
except Exception as e:
logger.error(f"获取安全检测项 {item_key} 失败: {e}")
results.append((1, '查询失败')) # 添加失败结果
return ( # 返回安全检测结果
True, # 成功
search_response['msg'], # 消息
tuple(results) # 安全检测结果元组
)
return False, '未知路由', '' # 返回失败
def check_whois_domain(self, domain: str, data=None): # 查询域名WHOIS信息
try: # 异常处理
response, check_data = self._make_check_request(domain, 'whois', 'whois', data) # 发起检测请求
return self._handle_check_response(response, domain, 'whois', 'whois', check_data,
self.check_whois_domain) # 处理响应
except Exception as e: # 异常处理
logger.error(f"check_domain异常: {e}") # 记录错误
return False, str(e), '' # 返回失败
def beian_check_domain(self, domain: str, data=None): # 查询域名备案信息
try: # 异常处理
response, check_data = self._make_check_request(domain, 'beian', 'beian', data) # 发起检测请求
return self._handle_check_response(response, domain, 'beian', 'beian', check_data,
self.beian_check_domain) # 处理响应
except Exception as e: # 异常处理
logger.error(f"beian_check_domain异常: {e}") # 记录错误
return False, str(e), [] # 返回失败
def safe_check_domain(self, domain: str, data=None): # 查询域名安全信息
try: # 异常处理
xm_codes = [ # 检测代码列表
'qqjc', # QQ检测
'weixin', # 微信检测
'dyjc', # 抖音检测
'qiang', # 被墙检测
'bdjc', # 百度检测
'llqjcgg', # 谷歌检测
'llqjchh', # 火狐检测
]
response, check_data = self._make_check_request(domain, 'safe', xm_codes, data) # 发起检测请求
return self._handle_check_response(response, domain, 'safe', xm_codes, check_data,
self.safe_check_domain) # 处理响应
except Exception as e: # 异常处理
logger.error(f"safe_check_domain异常: {e}") # 记录错误
return False, str(e), [] # 返回失败
# # 测试代码
# if __name__ == '__main__':
# j = JC() # 创建JC实例
# j.load_cookies()
# j.load_juming_cookies() # 加载聚名网Cookie
# logger.info("开始登录...")
# login_result = j.auth_login() # 使用聚名网账号登录聚查网
# logger.info(f"登录结果: {login_result}")
# if not login_result[0]:
# logger.error("登录失败,无法继续测试")
# exit(1)
# # 测试WHOIS查询
# logger.info("测试WHOIS查询:")
# result = j.check_whois_domain('921229.com')
# logger.info(f"WHOIS查询结果: {result}")
# logger.info(j.cookie)
# # # 测试备案查询
# # logger.info("测试备案查询:")
# # result = j.beian_check_domain('baidu.com')
# # logger.info(f"备案查询结果: {result}")
# #
# # 测试安全检测
# logger.info("测试安全检测:")
# result = j.safe_check_domain('576777.com')
# logger.info(f"安全检测结果: {result}")
#
# # j.save_cookies() # 保存Cookie

View File

@@ -0,0 +1,497 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :juming.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/3/19 15:51
@explain : 聚名网API封装类 - 提供登录、域名查询、删除域名列表等功能
'''
import base64 # Base64编码模块
import hashlib # 哈希算法模块
import json # JSON处理模块
import os # 操作系统接口模块
import pickle # 序列化模块
import random # 随机数生成模块
import subprocess # 子进程管理模块
import time # 时间处理模块
from functools import partial
from typing import Optional, Tuple, Dict, List, Any # 类型提示
import requests # HTTP请求库
from loguru import logger # 日志记录库
from requests.cookies import RequestsCookieJar # Cookie处理
# 常量定义
BASE_URL = "https://www.juming.com" # 聚名网基础URL
CAPTCHA_MAX_RETRY = 5 # 验证码最大重试次数
REQUEST_TIMEOUT = 10 # 请求超时时间(秒)
DOWNLOAD_TIMEOUT = 60 # 下载超时时间(秒)
SEED_TIME_OFFSET = 286 # 种子时间偏移量
SLIDE_OFFSET = 290 # 滑块偏移量
SLIDE_DURATION = 611 # 滑动持续时间
class MySubprocessPopen(subprocess.Popen): # 自定义子进程类
def __init__(self, *args, **kwargs): # 初始化方法
kwargs['encoding'] = "UTF-8" # 设置编码为UTF-8
super().__init__(*args, **kwargs) # 调用父类初始化
subprocess.Popen = MySubprocessPopen # 替换默认的Popen类
os.environ["EXECJS_RUNTIME"] = "Node" # 设置JavaScript运行时为Node.js
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8', startupinfo=startupinfo)
def resolve_node_executable() -> str:
current_dir = os.path.dirname(__file__)
project_dir = os.path.dirname(current_dir)
bundled_node = os.path.join(project_dir, "tools", "node-v20.19.4-win-x64", "node.exe")
if os.path.exists(bundled_node):
return bundled_node
return "node"
def calculate_seed(t: int) -> str: # 计算验证码种子值
now = int(time.time()) # 获取当前时间戳
c = now - t # 计算时间差
seed = base64.b64encode(str(c).encode()).decode() # Base64编码
return seed # 返回种子字符串
def random_fingerprint() -> str: # 生成随机设备指纹
data = str(random.random()) + str(time.time()) # 组合随机数和时间戳
return hashlib.sha256(data.encode()).hexdigest() # 返回SHA256哈希值
def glwb(s: Optional[str]) -> str: # JS文本过滤函数
"""
过滤特殊字符防止XSS攻击
Args:
s: 待过滤的字符串
Returns:
过滤后的字符串
"""
if s is None or not isinstance(s, str): # 检查输入是否有效
return '' # 返回空字符串
a_nr = s # 复制字符串
a_nr = a_nr.replace('"', '&#34;') # 替换双引号
a_nr = a_nr.replace("'", '&#39;') # 替换单引号
a_nr = a_nr.replace('<', '&#60;') # 替换小于号
a_nr = a_nr.replace('>', '&#62;') # 替换大于号
a_nr = a_nr.replace('\\', '&#92;') # 替换反斜杠
return a_nr # 返回过滤后的字符串
def md5_19(text: str) -> str: # 计算MD5并返回前19位
"""
计算MD5哈希值并截取前19位
Args:
text: 待哈希的文本
Returns:
MD5哈希值的前19位
"""
if not text: # 检查输入是否为空
return '' # 返回空字符串
md5 = hashlib.md5(text.encode('utf-8')).hexdigest() # 生成32位MD5
return md5[:19] # 返回前19位
def encrypt_password(loginToken: str, password: str) -> str: # 加密密码
"""
使用双重MD5加密密码
Args:
loginToken: 登录令牌
password: 明文密码
Returns:
加密后的密码
"""
filtered_pwd = glwb(password) # 过滤密码中的特殊字符
step1 = f'[jiami{filtered_pwd}mima]' # 拼接固定盐值
md5_step1 = md5_19(step1) # 第一次MD5加密
step2 = loginToken + md5_step1 # 拼接登录令牌
final_result = md5_19(step2) # 第二次MD5加密
return final_result # 返回最终加密结果
class JM(object): # 聚名网API封装类
token: str # 验证码token
loginToken: str = '' # 登录令牌
session_id: str # 会话ID
fingerprint: str # 设备指纹
captchaId: str # 验证码ID
encryptionPublicKey: str # 加密公钥
cookie: RequestsCookieJar = {} # Cookie存储
# 默认请求头
headers = {
'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', # 用户代理
'accept': '*/*', # 接受所有类型
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"', # 浏览器标识
'sec-ch-ua-mobile': '?0', # 是否移动端
'origin': 'https://www.juming.com', # 请求源
'sec-fetch-site': 'same-origin', # 请求站点
'sec-fetch-mode': 'cors', # 请求模式
'sec-fetch-dest': 'empty', # 请求目标
'referer': 'https://www.juming.com/', # 来源页面
'accept-language': 'zh-CN,zh;q=0.9', # 接受的语言
'priority': 'u=1, i', # 请求优先级
'Content-Type': 'application/x-www-form-urlencoded', # 内容类型
}
def __init__(self, proxies: Optional[Dict] = 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]: # 初始化滑块验证码
"""
初始化滑块验证码
Returns:
Tuple[bool, str]: (是否成功, 消息)
"""
self.fingerprint = random_fingerprint() # 生成随机指纹
data = { # 构建请求数据
"request_id": self.fingerprint, # 请求ID
"scene": "default", # 场景
"seed": calculate_seed(SEED_TIME_OFFSET) # 种子值
}
logger.info(data)
self.headers['Content-Type'] = 'application/x-www-form-urlencoded' # 设置内容类型
url = f"{self.base_url}/captcha/init" # 初始化URL
# self.cookie.clear()
# self.cookie.update({'acw_sc__v2': '69d948ed49117d89433d4316a79ada6f00d68f26'})
# logger.info(self.cookie)
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie).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'] # 返回结果
def captcha_verify(self) -> Tuple[bool, str]: # 验证滑块验证码
"""
验证滑块验证码
Returns:
Tuple[bool, str]: (是否成功, 消息)
"""
data = { # 构建验证数据
"offset": SLIDE_OFFSET, # 滑块偏移量
"duration": SLIDE_DURATION, # 滑动持续时间
"trail": [ # 滑动轨迹
{"x": 0, "y": 0, "time": 0}, # 起点
{"x": 0, "y": 0, "time": 17}, # 第1个点
{"x": 1, "y": 0, "time": 89}, # 第2个点
{"x": 5, "y": 0, "time": 106}, # 第3个点
{"x": 14, "y": 0, "time": 123}, # 第4个点
{"x": 28, "y": 0, "time": 139}, # 第5个点
{"x": 47, "y": 0, "time": 156}, # 第6个点
{"x": 74, "y": 0, "time": 173}, # 第7个点
{"x": 102, "y": 0, "time": 189}, # 第8个点
{"x": 131, "y": 0, "time": 206}, # 第9个点
{"x": 159, "y": 0, "time": 223}, # 第10个点
{"x": 184, "y": 0, "time": 239}, # 第11个点
{"x": 205, "y": 0, "time": 256}, # 第12个点
{"x": 227, "y": 1, "time": 273}, # 第13个点
{"x": 248, "y": 0, "time": 289}, # 第14个点
{"x": 263, "y": 0, "time": 306}, # 第15个点
{"x": 276, "y": 0, "time": 323}, # 第16个点
{"x": 286, "y": 0, "time": 339}, # 第17个点
{"x": 290, "y": 0, "time": 356}, # 第18个点
{"x": 290, "y": 0, "time": 373}, # 第19个点
{"x": 290, "y": 0, "time": 389}, # 第20个点
{"x": 290, "y": 0, "time": 406}, # 第21个点
{"x": 290, "y": 0, "time": 423}, # 第22个点
{"x": 290, "y": 0, "time": 478}, # 终点
],
"fingerprint": self.fingerprint, # 设备指纹
"captchaId": self.captchaId, # 验证码ID
"serverPublicKey": self.encryptionPublicKey # 服务器公钥
}
data_str = json.dumps(data, separators=(",", ":")) # 转换为JSON字符串压缩格式
current_dir = os.path.dirname(__file__)
jsFile_path = current_dir+"/sdk_leg_env.js" # JavaScript脚本路径
if not os.path.exists(jsFile_path): # 检查脚本文件是否存在
logger.error(f"Node.js脚本文件不存在: {jsFile_path}") # 记录错误
return False, f"缺少签名脚本: {jsFile_path}" # 返回失败
try: # 异常处理
with subprocess.Popen( # 执行Node.js脚本
[resolve_node_executable(), jsFile_path], # 命令和参数
stdin=subprocess.PIPE, # 标准输入
stdout=subprocess.PIPE, # 标准输出
stderr=subprocess.PIPE, # 标准错误
text=True, # 文本模式
encoding="utf-8", # UTF-8编码
errors="ignore" # 忽略编码错误
) as proc: # 进程上下文
stdout, stderr = proc.communicate(data_str, timeout=REQUEST_TIMEOUT) # 传入参数并获取输出
verify_data = stdout.strip() # 去除空白字符
if stderr: # 如果有错误输出
logger.error(f'genData_stderr->[{stderr}]') # 记录错误
if proc.returncode != 0: # 检查退出码
return False, f"Node.js脚本执行失败退出码: {proc.returncode}, 错误信息: {stderr}" # 返回失败
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() # 发送验证请求
if response['code'] == 1: # 验证成功
self.token = response['data']['token'] # 保存token
self.session_id = response['data']['session_id'] # 保存会话ID
return response['code'] == 1, response['msg'] # 返回结果
except subprocess.TimeoutExpired: # 超时异常
logger.error("Node.js脚本执行超时") # 记录错误
return False, "Node.js脚本执行超时" # 返回失败
except Exception as e: # 其他异常
logger.error(f"执行Node.js脚本异常: {str(e)}") # 记录错误
return False, f"执行Node.js脚本异常: {str(e)}" # 返回失败
if not verify_data: # 检查输出是否为空
return False, "Node.js脚本未输出任何内容" # 返回失败
def user_zh_wxdl_ewm(self) -> Tuple[bool, str, Optional[str]]: # 获取微信登录二维码
"""
获取微信登录二维码
Returns:
Tuple[bool, str, Optional[str]]: (是否成功, 消息, 二维码URL)
"""
url = f"{self.base_url}/user_zh/wxdl_ewm" # 二维码URL
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 # 返回结果
except Exception as e: # 异常处理
return False, f"请求失败: {str(e)}", None # 返回失败
def user_zh_p_login(self, email: str, password: str, data: Optional[Dict] = None) -> Tuple[bool, str]: # 账号密码登录
"""
使用账号密码登录
Args:
email: 邮箱账号
password: 明文密码
data: 携带的请求数据用于递归刷新token时传参
Returns:
Tuple[bool, str]: (是否成功, 消息)
"""
try: # 异常处理
captcha_success = False # 验证码成功标志
captcha_msg = f"滑块验证码失败,{CAPTCHA_MAX_RETRY}次内未成功" # 默认失败消息
for _ in range(CAPTCHA_MAX_RETRY): # 循环尝试验证滑块
if data is None: # 如果没有传入data
captcha_success, captcha_msg = self.captcha_init() # 初始化验证码
else: # 如果已有data
captcha_success = True # 直接视为验证通过
if captcha_success: # 验证码初始化成功
if data is None: # 只有第一次需要验证滑块
captcha_success, captcha_msg = self.captcha_verify() # 验证滑块
if captcha_success: # 滑块验证成功
if data is None: # 首次登录
data = { # 构建登录数据
"re_mm": encrypt_password(self.loginToken, password), # 加密密码
"re_yx": email, # 邮箱
"fs": "tl", # 登录方式
"dltoken": self.loginToken, # 登录令牌
"token": json.dumps( # token压缩格式JSON
{"code": self.token, "sessionId": self.session_id},
separators=(",", ":")
)
}
login_url = f"{self.base_url}/user_zh/p_login" # 登录URL
response = self.session.post( # 发送登录请求
url=login_url,
headers=self.headers,
json=data,
cookies=self.cookie,
)
logger.info(response.text)
result = response.json() # 解析响应
code = result.get("code") # 获取状态码
msg = result.get("msg", "登录接口未返回消息") # 获取消息
if code == 1: # 登录成功
self.cookie = response.cookies # 保存Cookie
return True, msg # 返回成功
elif code == -118: # token过期
self.loginToken = result.get("token", "") # 更新登录令牌
data["re_mm"] = encrypt_password(self.loginToken, password) # 重新加密密码
data["dltoken"] = self.loginToken # 更新令牌
return self.user_zh_p_login(email, password, data) # 递归重新登录
else: # 其他错误
return False, msg # 返回失败
return captcha_success, captcha_msg # 返回验证码结果
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]: # 获取一口价域名列表
"""
获取一口价域名列表
Args:
page: 页码
page_size: 每页数量
data: 额外数据
Returns:
Tuple[bool, Any]: (是否成功, HTML内容或错误消息)
"""
data = { # 构建请求数据
'psize': page_size, # 每页数量
'page': page, # 页码
} if data is None else data # 如果有data则使用data
url = f"{self.base_url}/ykj/get_list" # 列表URL
try: # 异常处理
self.headers['Content-Type'] = 'application/x-www-form-urlencoded' # 设置内容类型
res = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie) # 发送请求
response = res.json() # 解析响应
if response['code'] == -401: # 需要验证码
captcha_response = False, f'滑块验证码失败,{CAPTCHA_MAX_RETRY}次内未成功' # 默认失败
for _ in range(CAPTCHA_MAX_RETRY): # 循环尝试验证
captcha_response = self.captcha_init() # 初始化验证码
if captcha_response[0]: # 初始化成功
captcha_response = self.captcha_verify() # 验证验证码
if captcha_response[0]: # 验证成功
data = {
'psize': page_size, # 每页数量
'page': page, # 页码
# 构建token数据
'token': json.dumps({ # token压缩格式JSON
'code': self.token,
'sessionId': self.session_id,
}, separators=(",", ":"))
}
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
return self.ykj_get_list(page, page_size) # 递归调用
elif response['code'] == 1 and response.get('html'): # 成功获取HTML
return response['code'] == 1, response['html'] # 返回HTML
return False, response['msg'] # 返回失败
except Exception as e: # 异常处理
return False, f"请求失败: {str(e)}" # 返回失败
def new_cha_del(self, date: str) -> List[str]: # 获取删除域名列表
"""
获取指定日期的删除域名列表
Args:
date: 日期格式YYYY-MM-DD
Returns:
List[str]: 域名列表
"""
url = f"{self.base_url}/newcha/del_down?scsj={date}" # 下载URL
try: # 异常处理
response = self.session.get(url, headers=self.headers, cookies=self.cookie, allow_redirects=False) # 发送请求
url = response.headers['Location'] # 获取重定向URL
response = self.session.get(url, timeout=DOWNLOAD_TIMEOUT) # 下载文件
response_text = response.content.decode('utf-8') # 解码内容
lines = response_text.strip().splitlines() # 按行分割
return lines # 返回域名列表
except Exception as e: # 异常处理
logger.error(f"<获取删除域名列表错误>: {str(e)}") # 记录错误
return [] # 返回空列表
def save_cookies(self, filepath: str = "juming_cookies.pkl") -> None: # 保存Cookie到文件
"""
保存Cookie到文件和Redis
Args:
filepath: 文件路径
"""
with open(filepath, "wb") as f: # 以二进制写入模式打开文件
pickle.dump(self.cookie, f) # 序列化保存
# 保存到Redis
try:
import redis
from app.config import config
redis_client = redis.Redis(
host=config.REDIS_HOST,
port=config.REDIS_PORT,
password=config.REDIS_PASSWORD,
db=config.REDIS_DB,
decode_responses=True
)
# 将cookie转换为字典
cookie_dict = {}
for cookie in self.cookie:
cookie_dict[cookie.name] = cookie.value
redis_client.set('domain_tool:juming_cookies', str(cookie_dict))
except Exception as e:
logger.error(f"保存Cookie到Redis失败: {str(e)}")
pass
def load_cookies(self, filepath: str = "juming_cookies.pkl") -> None: # 从文件加载Cookie
"""
从文件加载Cookie
Args:
filepath: 文件路径
"""
try: # 异常处理
with open(filepath, "rb") as f: # 以二进制读取模式打开文件
self.cookie = pickle.load(f) # 反序列化加载
except: # 异常处理
self.cookie = requests.cookies.RequestsCookieJar() # 创建空Cookie
# if __name__ == '__main__': # 主程序入口
# m = JM() # 创建JM实例
#
# # 账号密码登录(请替换为您的账号密码)
# email = 'chaofanai1998@gmail.com' # 邮箱账号
# password = 'llzz123,./' # 密码
#
# logger.info(f"开始登录: {email}") # 记录日志
# login_result = m.user_zh_p_login(email, password) # 执行登录
# logger.info(f"登录结果: {login_result}") # 记录结果
#
# if not login_result[0]: # 登录失败
# logger.error("登录失败,程序退出") # 记录错误
# exit(1) # 退出程序
#
# m.load_cookies() # 加载Cookie
#
# # 获取一口价域名
# import re # 导入正则表达式模块
#
# for page in range(1, 2): # 循环获取页面
# success, html = m.ykj_get_list(page) # 获取列表
# if success: # 获取成功
# pattern_ym = r"<a class='yda1 ydz' ym='([^']*)'" # 匹配域名
# results = re.findall(pattern_ym, html) # 查找所有域名
# logger.info(results)
# logger.info(f"第{page}页,找到{len(results)}个域名") # 记录结果
# if not results: # 如果没有域名
# logger.info("没有更多域名,停止获取") # 记录日志
# break # 退出循环
# time.sleep(5) # 等待5秒
#
# # 获取删除域名
# deleted_domains = m.new_cha_del("2026-03-11") # 获取删除域名
# logger.info(f"找到{len(deleted_domains)}个删除域名") # 记录结果
#
# m.save_cookies() # 保存Cookie

Binary file not shown.

View File

@@ -0,0 +1,236 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :juziseo.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/4/10 15:00
@explain : 桔子SEO API封装类 - 提供登录、域名查询等功能
'''
# 导入标准库
import os # 操作系统接口
import pickle # 序列化反序列化
import re
import time # 时间处理
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
# 导入第三方库
import requests # HTTP请求库
from loguru import logger # 日志记录
from requests.cookies import RequestsCookieJar # Cookie管理
from detect.geetest2 import Geetest2
# AES-CBC 加密
def aes_cbc_encrypt(data, key=b'pvjxzjmzwawfscft', iv=b'qvibva1wg0uxwjeu'):
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_data = pad(data, AES.block_size) # PKCS7填充
encrypted = cipher.encrypt(padded_data)
return base64.b64encode(encrypted).decode()
class Juziseo:
"""
桔子SEO API封装类
"""
cookie: RequestsCookieJar = {} # 桔子SEO Cookie
# 通用请求头配置
headers = {
'accept': 'application/json, text/javascript, */*; q=0.01', # 接受的内容类型
'accept-language': 'zh-CN,zh;q=0.9', # 接受的语言
'content-type': 'application/x-www-form-urlencoded', # 内容类型
'origin': 'https://seo.juziseo.com', # 请求源
'priority': 'u=1, i', # 请求优先级
'referer': 'https://seo.juziseo.com/login', # 来源页面
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"', # 浏览器标识
'sec-ch-ua-mobile': '?0', # 是否移动端
'sec-ch-ua-platform': '"Windows"', # 操作系统平台
'sec-fetch-dest': 'empty', # 请求目标
'sec-fetch-mode': 'cors', # 请求模式
'sec-fetch-site': 'same-origin', # 请求站点
'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',
# 用户代理
'x-requested-with': 'XMLHttpRequest', # AJAX请求标识
}
def __init__(self, proxies: dict = None):
"""
初始化桔子SEO类
:param proxies: 代理配置
"""
self.validate = None
self.challenge = None
self.gt = None
self.session = requests.Session() # 创建会话对象
self.session.proxies = proxies # 设置代理
self.session.timeout = 10 # 设置超时时间10秒
self.base_url = "https://seo.juziseo.com" # 设置基础URL
def save_cookies(self, filepath="juziseo_cookies.pkl"):
"""
保存Cookie到文件和Redis
:param filepath: 文件路径
"""
with open(filepath, "wb") as f: # 以二进制写入模式打开文件
pickle.dump(self.cookie, f) # 序列化保存Cookie
logger.info(f"已保存桔子SEO Cookie到 {filepath}")
# 保存到Redis
try:
import redis
from app.config import config
redis_client = redis.Redis(
host=config.REDIS_HOST,
port=config.REDIS_PORT,
password=config.REDIS_PASSWORD,
db=config.REDIS_DB,
decode_responses=True
)
# 将cookie转换为字典
cookie_dict = {}
for cookie in self.cookie:
cookie_dict[cookie.name] = cookie.value
redis_client.set('domain_tool:juziseo_cookies', str(cookie_dict))
except Exception as e:
pass
def load_cookies(self, filepath="juziseo_cookies.pkl"):
"""
从文件加载Cookie
:param filepath: 文件路径
"""
try: # 尝试加载
with open(filepath, "rb") as f: # 以二进制读取模式打开文件
self.cookie = pickle.load(f) # 反序列化加载Cookie
# logger.info(f"已从 {filepath} 加载桔子SEO Cookie")
except: # 加载失败
self.cookie = requests.cookies.RequestsCookieJar() # 创建空的CookieJar
logger.warning(f"加载桔子SEO Cookie失败创建空Cookie")
def start_msg_captcha_servlet(self):
url = f"{self.base_url}/class/gtcode/msg/StartMsgCaptchaServlet.php"
response = self.session.get(url, headers=self.headers, cookies=self.cookie).json()
logger.info(response)
self.gt = response['gt']
self.challenge = response['challenge']
return response['success'] == 1
def get_captcha(self):
GETT2 = Geetest2()
code = GETT2.get_tp(gt=self.gt, challenge=self.challenge, type_='auto')
logger.info(code)
if code['result'] == 'success':
self.challenge = code['challenge']
self.gt = code['gt']
self.validate = code['validate']
return True
else:
logger.error(f"获取桔子SEO验证码失败: {code.get('msg', '未知错误')}")
return False
def login(self, username, password):
"""
登录桔子SEO
:param username: 账号
:param password: 密码
:return: tuple - (是否成功, 消息)
"""
try:
for _ in range(3):
if self.start_msg_captcha_servlet():
if self.get_captcha():
break
data = {
'return_url': '/',
'user_name': aes_cbc_encrypt(bytes(username, 'utf-8')),
'password': aes_cbc_encrypt(bytes(password, 'utf-8')),
'geetest_challenge': self.challenge,
'geetest_validate': self.validate,
'geetest_seccode': self.validate + '|jordan',
'_post_type': 'ajax',
}
logger.debug(data)
# 发送登录请求
url = f"{self.base_url}/account/ajax/login_process/"
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie)
logger.info(response.text)
# 解析响应
result = response.json()
if result.get('errno') == 1:
# 登录成功保存Cookie
self.cookie = response.cookies
self.save_cookies()
logger.info("桔子SEO登录成功")
return True, "登录成功"
else:
logger.error(f"桔子SEO登录失败: {result.get('msg', '未知错误')}")
return False, result.get('msg', '登录失败')
except Exception as e:
logger.error(f"桔子SEO登录异常: {e}")
return False, f"登录失败: {str(e)}"
def check_history(self, domain: str, sensitive_words: list = None):
# 检测域名历史是否存在敏感词
if sensitive_words is None:
sensitive_words = []
data = {
'qrtypeindex': '1',
'domains': domain,
'_post_type': 'ajax',
}
url = f"{self.base_url}/snapshot/save/"
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie).json()
# logger.info(response)
if response['errno'] == 1:
url = response['rsm']['url']
response_html = self.session.get(url, headers=self.headers, cookies=self.cookie).content.decode('utf-8')
title_sensitive_words_match = re.search(r'标题敏感词\D*(\d+)', response_html, re.S)
title_suspected_sensitive_words_match = re.search(r'标题有疑似敏感词\D*(\d+)', response_html, re.S)
content_sensitive_words_match = re.search(r'内容敏感词\D*(\d+)', response_html, re.S)
baidu_sensitive_words_match = re.search(r'百度历史收录敏感\D*(\d+)', response_html, re.S)
if bool(title_sensitive_words_match or title_suspected_sensitive_words_match or content_sensitive_words_match or baidu_sensitive_words_match):
return False, "存在敏感词"
subdomain_match = re.search(r'子域名:\D*(\d+)', response_html, re.S)
if subdomain_match:
return False, f"存在子域名: {subdomain_match.group(1)}"
for sensitive_word in sensitive_words:
if sensitive_word in response_html:
return False, f"存在敏感词: {sensitive_word}"
return True, 'success'
return False, str(response.get('err', '请求失败'))
def check_external_link(self, domain: str, sensitive_words: list = None):
# 外链查询域名是否存在敏感词
if sensitive_words is None:
sensitive_words = []
data = {
'qrtypeindex': '1',
'domains': domain,
'_post_type': 'ajax',
}
url = f"{self.base_url}/domain_rank/save_domain/"
response = self.session.post(url, headers=self.headers, data=data, cookies=self.cookie).json()
logger.info(response)
if response['errno'] == 1:
url = response['rsm']['url']
response_html = self.session.get(url, headers=self.headers, cookies=self.cookie).content.decode('utf-8')
subdomain_match = re.search(r'子域名:\D*(\d+)', response_html, re.S)
if subdomain_match:
return False, f"存在子域名: {subdomain_match.group(1)}"
for sensitive_word in sensitive_words:
if sensitive_word in response_html:
return False, f"存在敏感词: {sensitive_word}"
return True, 'success'
return False, str(response.get('err', '请求失败'))

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
import base64,re
import time, os,datetime, sched
import cv2
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
import requests,re
import numpy as np
class quekou():
def __init__(self):
self.t = int(time.time() * 1000)
pass
def save_png(self,path,con):
with open(path, "wb") as fp:
fp.write(con)
def get_distance2(self,bg,tp):
bg = base64.b64decode(bg) # base64转二进制
tp = base64.b64decode(tp) # base64转二进制
res = self.det.slide_match(tp, bg, simple_target=True)
res=res['target'][0]
return res
def get_distance(self, bg, tp):
bg = base64.b64decode(bg) # base64转二进制
tp = base64.b64decode(tp) # base64转二进制
''' bg: 背景图片 tp: 缺口图片 out:输出图片 '''
# 读取背景图片和缺口图片
bg_img = Image.open(BytesIO(bg)) # 背景图片
tp_img = Image.open(BytesIO(tp)) # 缺口图片
bg_edge = cv2.Canny(np.array(bg_img), 100, 200)
tp_edge = cv2.Canny(np.array(tp_img), 100, 200)
# 转换图片格式
bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
# 缺口匹配
res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 寻找最优匹配
# 绘制方框
tl = max_loc # 左上角点的坐标
return tl[0]

View File

@@ -0,0 +1,85 @@
# encoding: utf-8
import requests
import random
from loguru import logger
msg="提示:返回出现'ip overtime','forbidden'等信息说明本机IP被目标网站限制,请使用/更换代理IP或更换电脑即可。"
warning="仅用于学习交流,请勿用于非法用途,违者后果自负!"
logger.warning(warning)
logger.info(msg)
UA = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/44.0.2403.155 Safari/537.36",
"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; pl-PL; rv:1.0.1) Gecko/20021111 Chimera/0.6",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.8 (KHTML, like Gecko, Safari) Cheshire/1.0.UNOFFICIAL",
"Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.8.1b2) Gecko/20060821 BonEcho/2.0b2 (Debian-1.99+2.0b2+dfsg-1)",
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
"Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
"Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
"Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36",
"Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0.6",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36",
"Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36"
]
def randomUA():
return {"User-Agent": random.choice(UA)}
# if __name__ == '__main__':
#
# print(randomUA())

View File

@@ -0,0 +1,75 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :register.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/3/27 22:55
@explain : 域名注册状态检测工具
'''
from datetime import datetime, timezone, timedelta # 日期时间处理,用于时区转换
import requests # HTTP请求库
from loguru import logger # 日志记录
def check_register(domain: str, postfix: str = 'com',proxies: dict = None):
'''
检测注册状态
:param domain: 待检测域名(不包含后缀)
:param postfix: 域名后缀com、net默认com
:param proxies: 代理默认None
:return: 注册状态2可注册3已注册-1检测失败过期时间格式YYYY-MM-DD HH:MM:SS为空
'''
url = f'https://rdap.verisign.com/{postfix}/v1/domain/{domain}' # 构建RDAP查询URL
# url = f'https://www.baidu.com' # 构建RDAP查询URL
# 添加重试机制
max_retries = 3
for retry in range(max_retries):
try:
response = requests.get(url, timeout=20, proxies=proxies) # 发送GET请求设置超时5秒
if response.status_code == 200: # HTTP 200表示域名已注册
json_data = response.json() # 解析JSON响应
for item in json_data['events']: # 遍历事件列表
if item['eventAction'] == 'expiration': # 如果是过期时间事件
utc_time = datetime.fromisoformat(item['eventDate'].replace('Z', '+00:00')) # 解析UTC时间
beijing_tz = timezone(timedelta(hours=8)) # 创建北京时区UTC+8
beijing_time = utc_time.astimezone(beijing_tz) # 将UTC时间转换为北京时间
return 3, beijing_time.strftime("%Y-%m-%d %H:%M:%S") # 返回已注册状态和过期时间
elif response.status_code == 404: # HTTP 404表示域名不存在可注册
# 可注册状态
return 2, '' # 返回可注册状态,过期时间为空
logger.error(response.status_code)
except requests.exceptions.RequestException as e:
# logger.warning(f"注册状态检测第{retry+1}次失败: {domain}, 错误: {e}")
if retry < max_retries - 1:
import time
time.sleep(2) # 等待2秒后重试
proxies=None
else:
# 达到最大重试次数,返回检测失败
logger.error(f"注册状态检测失败: {domain}, 已达到最大重试次数")
return -1, '' # -1表示检测失败过期时间为空
# def get_proxy():
#
# url='http://api.ch12361.com/getProxy.php?group=A&count=1'
# response = requests.get(url).json()
# username=response['username']
# password=response['password']
# ip=response['ip']
# port=response['port']
#
# proxy_url = f"http://{username}:{password}@{ip}:{port}"
# logger.info(proxy_url)
# return {'http':proxy_url,'https':proxy_url}
#
# t=datetime.now()
# logger.info(check_register('75az.com',proxies=get_proxy())) # 测试查询nnsk.com域名的注册状态
# logger.info(datetime.now()-t)

15108
domainCheck/detect/sdk_leg.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,897 @@
process_ = process;
require_ = require;
delete Buffer;
// delete process;
delete require;
delete global;
delete module;
delete exports;
delete __filename;
delete __dirname;
delete SharedArrayBuffer;
AsObj = {
// print: console.log,
print: function () { },
// print_:console.log,
}
no_print = ['Boolean','String','parseFloat','Array','Object','prepareStackTrace_'];
function watch(object, WatchName) {
const handler = {
get(target, property, receiver) {
if (
property !== 'isNaN' &&
property !== 'encodeURI' &&
property !== "Uint8Array" &&
property !== 'undefined' &&
property !== 'JSON' &&
property !== 'Number' &&
!no_print.includes(property) &&
property !== Symbol.for('nodejs.util.inspect.custom') &&
typeof property !== 'symbol'
) {
if (property === 'global') {
return undefined;
}
if (property === 'Buffer') {
return undefined;
}
if (property === 'process') {
return undefined;
}
if (WatchName === 'config_data') {
debugger
}
if (WatchName.indexOf('.prototype') != -1 && target[property] != undefined) {
return Reflect.get(target, property, receiver);
}
AsObj.print(
"方法:", "get",
"对象:", WatchName,
"属性:", property,
"属性类型:", typeof property,
"属性值:", typeof target[property] == 'object' ? "object" : target[property],
"属性值类型:", typeof target[property]
);
}
if (WatchName === 'top') {
return window;
}
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
if (WatchName.indexOf('.prototype') != -1 && value != undefined) {
return Reflect.set(target, property, value, receiver);
}
AsObj.print(
"方法:", "set",
"对象:", WatchName,
"属性:", property,
"属性类型:", typeof property,
"属性值:", typeof value == 'object' ? "object" : value,
"属性值类型:", typeof target[property]
);
return Reflect.set(target, property, value, receiver);
},
// in操作 检测
has(target, property) {
AsObj.print(
"代理对象:", WatchName,
"方法:", "has",
"检查属性:", property,
"结果:", typeof target[property] == 'object' ? "object" : target[property],
);
return Reflect.has(target, property);
},
// Object.key 检测
ownKeys(target) {
AsObj.print(
"方法:", "ownKeys",
"对象:", target+''
);
return Reflect.ownKeys(target);
}
};
return new Proxy(object, handler);
}
// function watch(object, WatchName) {
// return object
// }
// 保护函数toString检测
const safeFunction = function safeFunction(func) {
//处理安全函数
Function.prototype.$call = Function.prototype.call;
const $toString = Function.toString;
const myFunction_toString_symbol = Symbol('('.concat('', ')'));
const myToString = function myToString() {
return typeof this === 'function' && this[myFunction_toString_symbol] || $toString.$call(this);
}
const set_native = function set_native(func, key, value) {
Object.defineProperty(func, key, {
"enumerable": false,
"configurable": true,
"writable": true,
"value": value
});
}
delete Function.prototype['toString'];
set_native(Function.prototype, "toString", myToString);
set_native(Function.prototype.toString, myFunction_toString_symbol, "function toString() { [native code] }");
const safe_Function = function safe_Function(func) {
set_native(func, myFunction_toString_symbol, "function" + (func.name ? " " + func.name : "") + "() { [native code] }");
}
return safe_Function(func)
}
//创建函数,并代理上
const makeFunction = function makeFunction(name) {
v_log = AsObj.print;
// 使用 Function 保留函数名
func = new Function("v_log", `
return function ${name}() {
v_log('函数${name}传参-->', arguments);
};
`)(v_log); // 传递 v_log 到动态函数
safeFunction(func);
func = watch(func,`${name}`);
func.prototype = watch(func.prototype, `${name}.prototype`);
return func;
}
!(function () {
"use strict";
const $toString = Function.toString;
const myFunction_toString_symbol = Symbol('('.concat('', ')_', (Math.random() + '').toString(36)));
const mytoString = function () {
return typeof this == 'function' && this[myFunction_toString_symbol] || $toString.call(this);
};
function set_native(func, key, value) {
Object.defineProperty(func, key, {
"enumerable": false,
"configurable": true,
"writable": true,
"value": value
})
};
delete Function.prototype['toString'];
set_native(Function.prototype, "toString", mytoString);
set_native(Function.prototype.toString, myFunction_toString_symbol, "function toString() { [native code] }");
this.func_set_native = function (func) {
set_native(func, myFunction_toString_symbol, `function ${myFunction_toString_symbol, func.name || ''}() { [native code] }`)
}
}).call(globalThis);
// 重写全局对象原型链
function setTostringAndstringTag(obj) {
Object.defineProperties(obj.prototype, {
[Symbol.toStringTag]: {
configurable: true,
value: obj.name
}
});
safeFunction(obj);
};
// 创建标签原型
function createTagProto(propObj,portotypeObj) {
let res = propObj + ' = ' + 'function ' + propObj + '() { throw new TypeError("Illegal constructor"); };\n';
res += 'setTostringAndstringTag(' + propObj + ',null);\n';
if (portotypeObj) {
for (let key in portotypeObj) {
res += propObj + '.prototype.' + portotypeObj[key] + '= function ' + portotypeObj[key] + '() {AsObj.print("'+propObj+'.prototype.' + portotypeObj[key] + '原型方法(需在实例对象上补该方法)::",arguments)};\n';
res += 'globalThis.func_set_native(' + propObj + '.prototype.' + portotypeObj[key] + ');\n';
}
}
eval(res);
}
Object.defineProperties(globalThis, {
[Symbol.toStringTag]: {
configurable: true,
value: 'Window'
}
});
for (let key in globalThis) {
if (typeof globalThis[key] === 'function') {
safeFunction(globalThis[key])
}
}
for (let key in console) {
if (typeof console[key] === 'function') {
safeFunction(console[key])
}
}
createTagProto('EventTarget',['addEventListener']);
createTagProto('WindowProperties');
createTagProto('Window');
window = globalThis;
window.__proto__ = Window.prototype;
window.__proto__.__proto__ = WindowProperties.prototype;
window.__proto__.__proto__.__proto__ = EventTarget.prototype;
Window.__proto__ = EventTarget;
Object.defineProperty(window, 'WindowProperties', {
get: function () {
return undefined;
}
})
function randoms(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
function getRandomValues(buf) {
var min = 0,
max = 255;
if (buf instanceof Uint16Array) {
max = 65535;
} else if (buf instanceof Uint32Array) {
max = 4294967295;
}
for (var element in buf) {
buf[element] = randoms(min, max);
}
return buf;
}
self = window.self = window;
frames = window.frames = window;
top = window.top = window;
parent = window.parent = window;
global = window.global = window;
Object.defineProperty(window, "global", {
configurable:false,
enumerable: true,
set: undefined,
get: function global(){
return window
}
})
Object.defineProperty(window, "top", {
configurable:false,
enumerable: true,
set: undefined,
get: function top(){
return window
}
})
Object.defineProperty(window, "self", {
configurable:false,
enumerable: true,
set: undefined,
get: function self(){
return window
}
})
Object.defineProperty(window, "parent", {
configurable:false,
enumerable: true,
set: undefined,
get: function parent(){
return window
}
})
Object.defineProperty(window, "frames", {
configurable:false,
enumerable: true,
set: undefined,
get: function frames(){
return window
}
})
innerWidth = 1536
innerHeight = 715
outerWidth = 1536
outerHeight = 824
devicePixelRatio = 1.25;
screenLeft = 0;
screenX = 0;
screenTop = 0;
screenY = 0;
opener = null;
isSecureContext = true;
crypto = {
getRandomValues:getRandomValues
};
createTagProto('DOMStringMap')
createTagProto('HTMLHeadElement',['insertBefore','removeChild'])
createTagProto('HTMLBodyElement',['addEventListener','appendChild','removeChild'])
createTagProto('HTMLHtmlElement',['getAttribute'])
createTagProto('HTMLDocument')
createTagProto('Document',['browsingTopics','appendChild','querySelector','evaluate','querySelectorAll','removeChild','requestStorageAccess','requestStorageAccessFor','hasStorageAccess','getElementsByTagName','hasPrivateToken','createElement','hasRedemptionRecord','hasFocus'])
createTagProto('Node')
document = {};
document.__proto__ = HTMLDocument.prototype;
document.__proto__.__proto__ = Document.prototype;
document.__proto__.__proto__.__proto__ = Node.prototype;
document.__proto__.__proto__.__proto__.__proto__ = EventTarget.prototype;
HTMLDocument.__proto__ = Document;
HTMLDocument.__proto__.__proto__ = Node;
HTMLDocument.__proto__.__proto__.__proto__ = EventTarget;
Document.__proto__ = Node;
Document.__proto__.__proto__ = EventTarget;
Node.__proto__ = EventTarget;
createTagProto('Plugin');
createTagProto('PluginArray');
plugins0 = {
name: 'PDF Viewer',
filename: 'internal-pdf-viewer',
description:'Portable Document Format',
length: 2,
'0': {
type: 'application/pdf',
},
'1':{
type:'text/pdf'
}
}
plugins0['0'].enabledPlugin = plugins0;
plugins0['1'].enabledPlugin = plugins0;
plugins1 = {
name: 'Chrome PDF Viewer',
filename: 'internal-pdf-viewer',
description:'Portable Document Format',
length: 2,
'0': {
type:'application/pdf'
},
'1': {
type:'text/pdf'
}
}
plugins1['0'].enabledPlugin = plugins1;
plugins1['1'].enabledPlugin = plugins1;
plugins2 = {
name: 'Chromium PDF Viewer',
filename: 'internal-pdf-viewer',
description:'Portable Document Format',
length: 2,
'0': {
type:'application/pdf'
},
'1': {
type:'text/pdf'
}
}
plugins2['0'].enabledPlugin = plugins2;
plugins2['1'].enabledPlugin = plugins2;
plugins3 = {
name: 'Microsoft Edge PDF Viewer',
filename: 'internal-pdf-viewer',
description:'Portable Document Format',
length: 2,
'0':{
type:'application/pdf'
},
'1': {
type:'text/pdf'
}
}
plugins3['0'].enabledPlugin = plugins3;
plugins3['1'].enabledPlugin = plugins3;
plugins4 = {
name: 'WebKit built-in PDF',
filename: 'internal-pdf-viewer',
description:'Portable Document Format',
length: 2,
'0': {
type:'application/pdf'
},
'1':{
type:'text/pdf'
}
}
plugins4['0'].enabledPlugin = plugins4;
plugins4['1'].enabledPlugin = plugins4;
plugins = {
length: 5,
'0': plugins0,
'1': plugins1,
'2': plugins2,
'3': plugins3,
'4': plugins4,
namedItem : function (name) {
AsObj.print('Plugin-namedItem:', name)
},
item: function (index) {
AsObj.print('Plugin-item:', index)
return watch(plugins0,'item-'+index);
},
refresh: function () {
AsObj.print('Plugin-refresh:',arguments)
},
}
plugins.__proto__ = PluginArray.prototype;
MimeTypeArray = function MimeTypeArray() {
this.length = 2;
this['0'] = {
suffixes: 'pdf',
type: 'application/pdf',
description:"Portable Document Format",
enabledPlugin: plugins0
};
this['1'] = {
suffixes: 'pdf',
type: 'text/pdf',
description:"Portable Document Format",
enabledPlugin: plugins0
};
};
MimeTypeArray.prototype.toString = function () { return '[object MimeTypeArray]'; }
MimeTypeArray.toString = function () { return 'function MimeTypeArray() { [native code] }'; }
Object.defineProperties(MimeTypeArray.prototype, { [Symbol.toStringTag]: { value: 'MimeTypeArray' } })
MimeTypeArrayc = new MimeTypeArray();
MimeTypeArrayc[Symbol.iterator] = function* () {
for (let key in this) {
yield this[key];
}
}
// 创建电池管理器对象原型
const BatteryManager = {
level: 1,
charging: true,
chargingTime: 0,
dischargingTime: null,
onchargingchange: null,
onlevelchange: null,
toString: function toString() {
return `BatteryManager {
charging: ${this.charging},
level: ${this.level},
chargingTime: ${this.chargingTime},
dischargingTime: ${this.dischargingTime}
}`
}
}
window.BatteryManager = BatteryManager;
Promise2 = {
then: function () {
return this;
},
catch: function (){},
};
createTagProto('Bluetooth');
createTagProto('Navigator');
Navigator.prototype.hardwareConcurrency = 8;
Navigator.prototype.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36';
Navigator.prototype.appVersion = '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
Navigator.prototype.appName = 'Netscape';
Navigator.prototype.appCodeName = 'Mozilla';
Navigator.prototype.vendor = 'Google Inc.';
Navigator.prototype.maxTouchPoints = 10;
Navigator.prototype.platform = 'Win32';
Navigator.prototype.adAuctionComponents = function adAuctionComponents() {
AsObj.print('adAuctionComponents:::', arguments)
}
safeFunction(Navigator.prototype.adAuctionComponents)
Navigator.prototype.runAdAuction = function runAdAuction() {
AsObj.print('runAdAuction:::', arguments)
}
safeFunction(Navigator.prototype.runAdAuction)
Navigator.prototype.canLoadAdAuctionFencedFrame = makeFunction('canLoadAdAuctionFencedFrame')
Navigator.prototype.deprecatedReplaceInURN = makeFunction('deprecatedReplaceInURN')
Navigator.prototype.deprecatedURNToURL = makeFunction('deprecatedURNToURL')
Navigator.prototype.joinAdInterestGroup = makeFunction('joinAdInterestGroup')
Navigator.prototype.leaveAdInterestGroup = makeFunction('leaveAdInterestGroup')
Navigator.prototype.updateAdInterestGroups = makeFunction('updateAdInterestGroups')
Navigator.prototype.connection = watch({
downlink: 9.1,
effectiveType: '4g',
rtt: 0,
saveData: false,
},'connection')
Navigator.prototype.language = 'zh-CN';
Navigator.prototype.languages = ["zh-CN"];
Navigator.prototype.plugins = plugins;
Navigator.prototype.webdriver = false;
Navigator.prototype.cookieEnabled = true;
Navigator.prototype.onLine = true;
Navigator.prototype.doNotTrack = null;
Navigator.prototype.bluetooth = {};
Navigator.prototype.product = 'Gecko'
Navigator.prototype.deviceMemory = 8
Navigator.prototype.mediaDevices = watch({
enumerateDevices: function enumerateDevices() {
return new Promise((resolve, reject) => {
const offer = [
{deviceId: '', kind: 'audioinput', label: '', groupId: ''},
{deviceId: '', kind: 'videoinput', label: '', groupId: ''},
{deviceId: '', kind: 'audiooutput', label: '', groupId: ''},
]
resolve(offer);
});
},
getUserMedia: function getUserMedia() {
AsObj.print('getUserMedia:::', arguments)
}
},'mediaDevices')
Navigator.prototype.storage = {
estimate: function estimate() {
AsObj.print('estimate:::', arguments)
return new Promise((resolve, reject) => {
const offer = {
usage: 0, // 1GB
quota: 2147483648, // 1GB,
usageDetails: {caches: 512, indexedDB: 2855}
};
resolve(offer);
});
}
}
Navigator.prototype.webkitPersistentStorage = watch({},'webkitPersistentStorage')
Navigator.prototype.webkitTemporaryStorage = watch({
queryUsageAndQuota: function queryUsageAndQuota() {
AsObj.print('queryUsageAndQuota:::', arguments)
return new Promise((resolve, reject) => {
const offer = {
usage: 1024 * 1024 * 1024, // 1GB
quota: 1024 * 1024 * 1024, // 1GB
};
resolve(offer);
});
}
},'webkitTemporaryStorage')
Navigator.prototype.bluetooth.__proto__ = Bluetooth.prototype;
Navigator.prototype.javaEnabled = function javaEnabled() {
return false
};
safeFunction(Navigator.prototype.javaEnabled)
Navigator.prototype.getBattery = function getBattery() {
AsObj.print('getBattery:::', arguments)
return Promise.resolve({
__proto__: BatteryManager,
// 动态参数配置(示例值)
level: 1,
charging: true,
dischargingTime: null // 2小时放电时间
})
}
safeFunction(Navigator.prototype.getBattery)
Navigator.prototype.registerProtocolHandler = function registerProtocolHandler() {
AsObj.print('registerProtocolHandler:::',arguments)
}
safeFunction(Navigator.prototype.registerProtocolHandler)
Navigator.prototype.mimeTypes = watch(MimeTypeArrayc,'mimeTypes');
Navigator.prototype.geolocation = {
getCurrentPosition: function getCurrentPosition() {
return Promise2;
}
}
Navigator.prototype.pdfViewerEnabled = true;
Navigator.prototype.doNotTrack = null;
Navigator.prototype.keyboard = watch({
getLayoutMap: function getLayoutMap() {
AsObj.print('Navigator.prototype.keyboard:', arguments)
return {
then: function () {
// arguments[0](watch({
// size: 48,
// values: function () {
// return ['k', 'g', '2', '0', 'v', 'a', '`', 'l', '\\', "'", 'w', '8', 'm', 'h', '.', '7', '1', 'p', 'd', 'f', 'o', 'q', 'c', 'n', '[', 'z', 'y', '3', '6', '5', 'x', '/', '\\', ',', '-', '4', 'b', 't', '9', 's', 'i', 'u', '=', 'j', ';', 'r', ']', 'e']
// }
// }, 'navigator.keyboard.getLayoutMap.then'))
return {
catch: function () {
arguments[0]({
message:'getLayoutMap() must be called from a top-level browsing context or allowed by the permission policy.'
})
}
}
}
}
}
},'navigator.keyboard')
Navigator.prototype.permissions = watch({
query: function query() {
arg_obj = arguments[0];
if (arg_obj.name === 'audio_capture') {
return new Promise((resolve, reject) => {
const offer = {
state: 'prompt',
onchange: null,
name:arg_obj.name
};
resolve(offer);
});
}
if (arg_obj.name === 'microphone') {
return {
then: function () {
arguments[0](watch({
state: 'denied',
onchange: null,
name: 'audio_capture'
},'permissions.query.microphone'));
return {catch:function(){}}
},
catch:function(){}
}
}
if (arg_obj.name === 'camera') {
return {
then: function () {
arguments[0](watch({
state: 'prompt',
onchange: null,
name: 'video_capture'
},'permissions.query.camera'));
return {catch:function(){}}
},
catch:function(){}
}
}
AsObj.print('permissions.query:::', arguments)
}
},'permissions')
Navigator.prototype.productSub = '20030107'
Navigator.prototype.getGamepads = function getGamepads() {
AsObj.print('getGamepads:::', arguments)
return [null,null,null,null]
}
safeFunction(Navigator.prototype.getGamepads)
Navigator.prototype.sendBeacon = makeFunction('sendBeacon')
Navigator.prototype.deprecatedRunAdAuctionEnforcesKAnonymity = false
Navigator.prototype.gpu = watch({
getPreferredCanvasFormat: function getPreferredCanvasFormat() {
AsObj.print('gpu.getPreferredCanvasFormat:', arguments)
return 'bgra8unorm'
},
wgslLanguageFeatures: watch({
size: 7,
values: function values() {
debugger
AsObj.print('wgslLanguageFeatures.values')
return ['packed_4x8_integer_dot_product', 'unrestricted_pointer_parameters', 'subgroup_uniformity', 'subgroup_id', 'pointer_composite_access', 'readonly_and_readwrite_storage_textures', 'uniform_buffer_standard_layout']
},
}, 'gpu.wgslLanguageFeatures'),
requestAdapter: function requestAdapter() {
AsObj.print('gpu.requestAdapter:', arguments)
return {
then: function () {
arguments[0](watch({
features: watch({
size: 19,
values: function () {
return ['depth32float-stencil8', 'rg11b10ufloat-renderable', 'bgra8unorm-storage', 'texture-formats-tier1', 'texture-compression-bc', 'dual-source-blending', 'core-features-and-limits', 'float32-filterable', 'indirect-first-instance', 'float32-blendable', 'depth-clip-control', 'texture-compression-bc-sliced-3d', 'timestamp-query', 'texture-formats-tier2', 'clip-distances', 'shader-f16', 'primitive-index', 'texture-component-swizzle', 'subgroups']
}
}, 'gpu.requestAdapter.features'),
info: watch({ vendor: 'intel', architecture: 'gen-11', device: '', description: '', subgroupMinSize: 16 }, 'gpu.requestAdapter.info'),
limits: watch({
maxBufferSize: 2147483648,
maxStorageBufferBindingSize:2147483644
}, 'gpu.requestAdapter.limits'),
catch:function(){}
}, 'gpu.requestAdapter'));
return {
catch: function () {
return {
then: function () {
arguments[0]()
return {catch:function(){}}
}
}
}
};
},
catch: function () {
}
}
}
},'navigator.gpu')
Navigator.prototype.userAgentData = watch({
brands:[
{
"brand": "Google Chrome",
"version": "143"
},
{
"brand": "Chromium",
"version": "143"
},
{
"brand": "Not A(Brand",
"version": "24"
}
],
mobile: false,
platform: "Windows",
getHighEntropyValues: function getHighEntropyValues() {
if (arguments[0] + '' === 'architecture,bitness,model,platformVersion,uaFullVersion,wow64') {
return new Promise((resolve, reject) => {
const offer = {
"architecture": "x86",
"bitness": "64",
"brands": [
{
"brand": "Not:A-Brand",
"version": "99"
},
{
"brand": "Google Chrome",
"version": "145"
},
{
"brand": "Chromium",
"version": "145"
}
],
"mobile": false,
"model": "",
"platform": "Windows",
"platformVersion": "10.0.0",
"uaFullVersion": "145.0.7632.117",
"wow64": false
};
resolve(offer);
});
}
AsObj.print('getHighEntropyValues:::', arguments)
}
},'userAgentData')
navigator = {};
navigator.__proto__ = Navigator.prototype;
createTagProto('Location');
location = {
"ancestorOrigins": {},
"href": "https://www.neimanmarcus.com/",
"origin": "https://www.neimanmarcus.com",
"protocol": "https:",
"host": "www.neimanmarcus.com",
"hostname": "www.neimanmarcus.com",
"port": "",
"pathname": "/",
"search": "",
"hash": ""
};
location.__proto__ = Location.prototype;
location.toString = function toString() {
return this.href;
}
createTagProto('Screen');
Screen.prototype = Object.assign(Screen.prototype, {
availHeight: 824,
availLeft: 0,
availTop: 0,
availWidth: 1536,
colorDepth: 32,
height: 864,
isExtended: true,
onchange: null,
pixelDepth: 24,
width: 1536,
orientation: {
angle: 0,
type: "landscape-primary",
onchange: null
}
})
screen = {};
screen.__proto__ = Screen.prototype;
createTagProto('History',['replaceState']);
history = {};
history.__proto__ = History.prototype;
chrome = {
loadTimes: function loadTimes() { },
csi: function csi() { },
app: {
InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' },
getDetails:function getDetails(){},
getIsInstalled:function getIsInstalled(){},
installState:function installState(){},
isInstalled: false,
runningState: function runningState(){}
},
}
createTagProto('Storage');
local = {
};
localStorage = {
getItem: function getItem(key) {
AsObj.print("localStorage.getItem::", arguments);
if (!local[key]) {
return null;
}
return local[key];
},
setItem: function setItem(key, value) {
AsObj.print("localStorage.setItem::", arguments);
local[key] = value;
},
clear: function clear() {
local = {};
},
removeItem: function removeItem(key) {
AsObj.print("localStorage.removeItem::", arguments);
delete local[key];
}
}
localStorage.__proto__ = Storage.prototype;
sessionStorage = {
getItem: function getItem(key) {
AsObj.print("sessionStorage.getItem::", arguments);
if (!local[key]) {
return null;
}
return local[key];
},
setItem: function setItem(key, value) {
AsObj.print("sessionStorage.setItem::", arguments);
local[key] = value;
},
clear: function clear() {
local = {};
},
removeItem: function removeItem(key) {
AsObj.print("sessionStorage.removeItem::", arguments);
delete local[key];
}
}
sessionStorage.__proto__ = Storage.prototype;
// window = watch(window, 'window');
// global = watch(global, 'global');
// globalThis = watch(globalThis, 'globalThis');
// self = watch(self, 'self');
// crypto = watch(crypto, 'crypto');
// performance = watch(performance, 'performance');
// document = watch(document, 'document');
// navigator = watch(navigator, 'navigator');
// location = watch(location, 'location');
// screen = watch(screen, 'screen');
// history = watch(history, 'history');
// localStorage = watch(localStorage, 'localStorage');
// sessionStorage = watch(sessionStorage, 'sessionStorage');
// chrome = watch(chrome, 'chrome');
require_('./sdk_leg.js');
let input = '';
// 收集数据
process.stdin.on('data', chunk => {
input += chunk;
});
process.stdin.on('end', async () => {
var config_data = JSON.parse(input);
var cryptoManager = await CaptchaSDKCorecc();
var encryptData = await buildEncryptedVerifyRequestcc(config_data, cryptoManager);
console.log(JSON.stringify(encryptData));
process.exit(0);
})