Files
domainCheck/detect/chinaz.py
2026-04-14 22:53:52 +08:00

242 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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}")