# -*- 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"> 开头, 结尾,中间的内容 match = re.search(r'id="site_title">([^<]+)', 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}")