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

1948 lines
88 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 :detect_worker.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/4/10 22:40
@explain : 域名检测端程序
'''
import os
import sys
import json
import time
import threading
import schedule
from datetime import datetime
from loguru import logger
from queue import Queue
from PySide6.QtWidgets import QApplication, QMainWindow, QTextEdit, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QLabel, QProgressBar, QGroupBox
from PySide6.QtCore import Qt, QThread, Signal, QMetaObject, Q_ARG, QCoreApplication, QEvent, QTimer
from PySide6.QtGui import QIcon
from app.utils.database import Database
from app.config import config
import redis
from detect import aizhan, baidu, c360, chinaz, register, jucha, juziseo
class DetectThread(QThread):
"""
检测线程
"""
log_signal = Signal(str)
progress_signal = Signal(int, int)
finished_signal = Signal()
thread_count_signal = Signal(int, int)
def __init__(self, worker):
super().__init__()
self.worker = worker
# 设置线程名称
self.setObjectName("DetectThread")
def run(self):
# 将信号传递给worker
self.worker.detect_thread = self
self.worker.start_detection()
self.finished_signal.emit()
class DetectMainWindow(QMainWindow):
"""
检测端主窗口
"""
def __init__(self):
super().__init__()
self.setWindowTitle("域名检测端")
self.setGeometry(100, 100, 800, 1000)
# 设置窗口大小固定,不可调整
self.setFixedSize(800, 1000)
# 设置窗口图标
# 打印当前文件路径以便诊断
current_file = os.path.abspath(__file__)
current_dir = os.path.dirname(current_file)
logger.info(f"当前文件路径: {current_file}")
logger.info(f"当前目录: {current_dir}")
# 获取PyInstaller打包后的临时目录
if hasattr(sys, '_MEIPASS'):
# 打包后运行
base_dir = sys._MEIPASS
logger.info(f"PyInstaller临时目录: {base_dir}")
else:
# 开发环境运行
base_dir = os.path.dirname(os.path.dirname(current_dir))
logger.info(f"开发环境目录: {base_dir}")
# 构建图标路径
icon_path = os.path.join(base_dir, "new_logo.svg")
logger.info(f"SVG图标路径: {icon_path}")
if os.path.exists(icon_path):
logger.info(f"SVG图标文件存在: {icon_path}")
try:
icon = QIcon(icon_path)
if icon.isNull():
logger.warning(f"SVG图标加载失败图标为空: {icon_path}")
else:
self.setWindowIcon(icon)
logger.info(f"设置SVG窗口图标成功: {icon_path}")
except Exception as e:
logger.error(f"加载SVG图标时出错: {e}")
else:
logger.warning(f"SVG图标文件不存在: {icon_path}")
# 如果SVG加载失败使用ico文件作为备用
ico_icon_path = os.path.join(base_dir, "favicon2.ico")
logger.info(f"ICO图标路径: {ico_icon_path}")
if os.path.exists(ico_icon_path):
logger.info(f"ICO图标文件存在: {ico_icon_path}")
try:
icon = QIcon(ico_icon_path)
if icon.isNull():
logger.warning(f"ICO图标加载失败图标为空: {ico_icon_path}")
else:
self.setWindowIcon(icon)
logger.info(f"设置ICO窗口图标成功: {ico_icon_path}")
except Exception as e:
logger.error(f"加载ICO图标时出错: {e}")
else:
logger.warning(f"ICO图标文件不存在: {ico_icon_path}")
# 设置窗口样式
self.setStyleSheet(""
"QMainWindow {"
" background-color: #f5f5f5;"
" font-family: 'Microsoft YaHei', Arial, sans-serif;"
"}"
"QLabel {"
" font-size: 14px;"
" color: #333;"
" padding: 5px 0;"
"}"
"QGroupBox {"
" font-size: 16px;"
" font-weight: bold;"
" border: 1px solid #ddd;"
" border-radius: 8px;"
" margin-top: 15px;"
" padding: 15px;"
" background-color: #ffffff;"
"}"
"QGroupBox::title {"
" subcontrol-origin: margin;"
" subcontrol-position: top left;"
" padding: 0 15px;"
" background-color: #4CAF50;"
" color: white;"
" border-radius: 4px;"
" font-size: 14px;"
"}"
"QProgressBar {"
" border: 1px solid #ddd;"
" border-radius: 6px;"
" text-align: center;"
" background-color: #f0f0f0;"
" height: 25px;"
"}"
"QProgressBar::chunk {"
" background-color: #4CAF50;"
" border-radius: 6px;"
"}"
"QTextEdit {"
" font-family: Consolas, 'Courier New', monospace;"
" font-size: 12px;"
" border: 1px solid #ddd;"
" border-radius: 6px;"
" background-color: #2d2d2d;"
" color: #e0e0e0;"
" padding: 10px;"
"}"
"QPushButton {"
" font-family: 'PingFang SC', 'Helvetica Neue', 'Microsoft YaHei', sans-serif;"
" font-size: 12px;"
" font-weight: 600;"
" padding: 6px 12px;"
" border: none;"
" border-radius: 4px;"
" color: white;"
" min-width: 80px;"
" min-height: 28px;"
" text-align: center;"
"}"
"QPushButton:hover {"
" opacity: 0.9;"
"}"
"QPushButton:disabled {"
" opacity: 0.5;"
"}"
"QPushButton#start_button {"
" background-color: #4CAF50;"
"}"
"QPushButton#stop_button {"
" background-color: #f44336;"
"}"
"QPushButton#exit_button {"
" background-color: #2196F3;"
"}"
"")
# 创建中心部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 创建布局
layout = QVBoxLayout(central_widget)
layout.setSpacing(10)
layout.setContentsMargins(20, 20, 20, 20)
# 创建状态标签
self.status_label = QLabel("状态: 就绪")
self.status_label.setStyleSheet("font-family: 'PingFang SC', 'Helvetica Neue', 'Microsoft YaHei', sans-serif; font-size: 14px; font-weight: 600; color: #2c3e50;")
layout.addWidget(self.status_label)
# 创建配置信息显示区域
config_group = QGroupBox("配置信息")
config_group.setStyleSheet("""
QGroupBox {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
border: 1px solid #e2e8f0;
border-radius: 10px;
margin-top: 20px;
padding: 0;
background-color: #ffffff;
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top left;
padding: 8px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 10px 10px 0 0;
font-family: 'PingFang SC', 'Helvetica Neue', 'Microsoft YaHei', sans-serif;
font-size: 14px;
font-weight: 600;
}
""")
config_layout = QVBoxLayout()
config_layout.setSpacing(12)
config_layout.setContentsMargins(20, 20, 20, 20)
# 创建配置标签
self.config_labels = {
'detect_options': QLabel("检测选项: 加载中..."),
'proxy_config': QLabel("代理配置: 加载中..."),
'thread_count': QLabel("检测线程数: 加载中...")
}
# 添加配置标签到布局
for label in self.config_labels.values():
label.setStyleSheet("""
QLabel {
font-family: 'PingFang SC', 'Helvetica Neue', 'Microsoft YaHei', sans-serif;
font-size: 13px;
font-weight: 400;
color: #4a5568;
background-color: #f7fafc;
padding: 12px 18px;
border-radius: 8px;
border-left: 4px solid #667eea;
margin: 0;
min-height: 45px;
white-space: normal;
border: 1px solid #e2e8f0;
}
QLabel:hover {
background-color: #edf2f7;
border-left-color: #764ba2;
}
""")
label.setWordWrap(True) # 启用自动换行
config_layout.addWidget(label)
config_group.setLayout(config_layout)
layout.addWidget(config_group)
# 创建进度条
self.progress_bar = QProgressBar()
self.progress_bar.setValue(0)
layout.addWidget(self.progress_bar)
# 创建日志文本框
self.log_text = QTextEdit()
self.log_text.setReadOnly(True)
self.log_text.setStyleSheet("font-family: Consolas, monospace; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; background-color: #2d2d2d; color: #e0e0e0;")
self.log_text.setMinimumHeight(500) # 设置最小高度为500像素
layout.addWidget(self.log_text, 1)
# 创建按钮布局
button_layout = QHBoxLayout()
button_layout.setSpacing(10)
# 按钮样式表
start_button_style = """
QPushButton {
background-color: #28a745;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
padding: 10px 20px;
min-width: 120px;
transition: all 0.3s ease;
}
QPushButton:hover {
background-color: #218838;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
QPushButton:pressed {
background-color: #1e7e34;
transform: translateY(1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
QPushButton:disabled {
background-color: #6c757d;
color: #adb5bd;
transform: none;
box-shadow: none;
}
"""
stop_button_style = """
QPushButton {
background-color: #dc3545;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
padding: 10px 20px;
min-width: 120px;
transition: all 0.3s ease;
}
QPushButton:hover {
background-color: #c82333;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
QPushButton:pressed {
background-color: #a71e2a;
transform: translateY(1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
QPushButton:disabled {
background-color: #6c757d;
color: #adb5bd;
transform: none;
box-shadow: none;
}
"""
exit_button_style = """
QPushButton {
background-color: #007bff;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
padding: 10px 20px;
min-width: 120px;
transition: all 0.3s ease;
}
QPushButton:hover {
background-color: #0069d9;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
QPushButton:pressed {
background-color: #0056b3;
transform: translateY(1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
QPushButton:disabled {
background-color: #6c757d;
color: #adb5bd;
transform: none;
box-shadow: none;
}
"""
# 创建开始检测按钮
self.start_button = QPushButton("开始检测")
self.start_button.setObjectName("start_button")
self.start_button.clicked.connect(self.start_detection)
self.start_button.setFixedHeight(50)
self.start_button.setStyleSheet(start_button_style)
button_layout.addWidget(self.start_button)
# 创建停止按钮
self.stop_button = QPushButton("停止检测")
self.stop_button.setObjectName("stop_button")
self.stop_button.clicked.connect(self.stop_detection)
self.stop_button.setEnabled(False)
self.stop_button.setFixedHeight(50)
self.stop_button.setStyleSheet(stop_button_style)
button_layout.addWidget(self.stop_button)
# 创建退出按钮
self.exit_button = QPushButton("退出")
self.exit_button.setObjectName("exit_button")
self.exit_button.clicked.connect(self.close)
self.exit_button.setFixedHeight(50)
self.exit_button.setStyleSheet(exit_button_style)
button_layout.addWidget(self.exit_button)
layout.addLayout(button_layout)
# 初始化检测端
self.worker = DetectWorker(self)
# 启动Redis订阅线程如果使用Redis
if self.worker.use_redis:
self.worker.running = True
self.worker.redis_sub_thread = threading.Thread(target=self.worker.start_redis_subscription)
self.worker.redis_sub_thread.daemon = True
self.worker.redis_sub_thread.start()
logger.debug("Redis配置更新订阅已启动")
# 连接信号
self.worker.connect_signals()
# 初始化检测线程
self.detect_thread = None
# 初始化日志队列和处理线程
self.log_queue = Queue()
self.log_processing = True
# 启动日志处理线程
self.log_thread = threading.Thread(target=self.process_log_queue)
self.log_thread.daemon = True
self.log_thread.start()
# 配置日志
logger.add(self.enqueue_log_message, level="INFO")
# 启动定时任务
self.scheduler_thread = threading.Thread(target=self.start_scheduler)
self.scheduler_thread.daemon = True
self.scheduler_thread.start()
def enqueue_log_message(self, message):
"""
日志回调函数,将日志消息放入队列
"""
self.log_queue.put(message)
def process_log_queue(self):
"""
处理日志队列中的消息
"""
while self.log_processing:
try:
message = self.log_queue.get(timeout=1)
# 提取时间和消息部分
# 日志格式: 2026-04-13 00:22:05.365 | INFO | __main__:load_thread_count:846 - 从Redis加载检测线程数成功: 500
import re
match = re.match(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \| .*? \| .*? - (.*)', message)
if match:
time_str = match.group(1)
msg_str = match.group(2)
log_text = f"{time_str} | {msg_str}"
else:
log_text = message.replace("\n", "")
# 使用QMetaObject.invokeMethod确保线程安全的GUI更新
QMetaObject.invokeMethod(self.log_text, "append", Qt.QueuedConnection, Q_ARG(str, log_text))
QMetaObject.invokeMethod(self.log_text.verticalScrollBar(), "setValue", Qt.QueuedConnection, Q_ARG(int, self.log_text.verticalScrollBar().maximum()))
except Exception as e:
pass
def log_message(self, message):
"""
日志回调函数(保留用于信号连接)
"""
self.enqueue_log_message(message)
def start_detection(self):
"""
开始检测
"""
self.status_label.setText("状态: 检测中...")
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
# 创建并启动检测线程
self.detect_thread = DetectThread(self.worker)
self.detect_thread.log_signal.connect(self.log_message)
self.detect_thread.progress_signal.connect(self.update_progress)
self.detect_thread.thread_count_signal.connect(self.update_thread_count_display)
self.detect_thread.finished_signal.connect(self.detection_finished)
self.detect_thread.start()
def stop_detection(self):
"""
停止检测
"""
self.status_label.setText("状态: 停止中...")
self.worker.stop()
self.stop_button.setEnabled(False)
def detection_finished(self):
"""
检测完成
"""
self.status_label.setText("状态: 检测完成")
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
def update_progress(self, current, total):
"""
更新进度条
"""
# 直接在当前线程执行避免使用QMetaObject.invokeMethod
try:
if total > 0:
progress = int((current / total) * 100)
self.progress_bar.setValue(progress)
except Exception as e:
print(f"更新进度失败: {e}")
def update_thread_count_display(self, active_count, total_count):
"""
更新线程数量显示
:param active_count: 当前活跃线程数量
:param total_count: 总线程数量
"""
try:
# 检查是否有配置标签
if hasattr(self, 'config_labels') and 'thread_count' in self.config_labels:
self.config_labels['thread_count'].setText(f"检测线程数: {total_count} (当前: {active_count})")
except Exception as e:
print(f"更新线程数量显示失败: {e}")
def start_scheduler(self):
"""
启动定时任务
"""
# 每天凌晨1点执行检测
schedule.every().day.at("01:00").do(self.scheduled_detection)
# 循环执行定时任务
while True:
schedule.run_pending()
time.sleep(60)
def scheduled_detection(self):
"""
定时检测任务
"""
self.log_message(f"[{datetime.now()}] 开始定时检测任务")
self.start_detection()
def refresh_config(self):
"""
刷新配置
"""
try:
# 重新加载配置
new_detect_options = self.worker.load_detect_options()
new_proxy_config = self.worker.load_proxy_config()
new_thread_count = self.worker.load_thread_count()
self.worker.load_cookies_from_remote()
# 检查配置是否发生变化
config_changed = False
if new_detect_options != self.worker.detect_options:
self.worker.detect_options = new_detect_options
config_changed = True
if new_proxy_config != self.worker.proxy_config:
self.worker.proxy_config = new_proxy_config
config_changed = True
if new_thread_count != self.worker.thread_count:
self.worker.thread_count = new_thread_count
config_changed = True
# 如果配置发生变化,更新标签
if config_changed:
self.worker.update_config_labels()
self.log_message(f"[{datetime.now()}] 配置已更新")
except Exception as e:
self.log_message(f"[{datetime.now()}] 刷新配置失败: {e}")
def event(self, event):
"""
处理事件
"""
if event.type() == ConfigUpdateEvent.Type:
self.update_config_labels()
return True
return super().event(event)
def update_config_labels(self):
"""
更新GUI配置标签
"""
logger.debug("开始更新GUI配置标签")
try:
if self.worker:
try:
logger.debug(f"worker对象存在当前配置: {self.worker.detect_options}, {self.worker.proxy_config}, {self.worker.thread_count}")
# 准备更新数据
detect_options = self.worker.detect_options
options_display = []
option_names = {
'detect_register': '1.检测注册',
'detect_chinaz': '2.站长之家查询',
'detect_aizhan': '3.爱站网查询',
'detect_baidu_site': '4.百度site查询',
'detect_360_site': '5.360的site查询',
'detect_baidu_security': '6.百度网页安全中心查询',
'detect_whois': '7.聚查中的WHOIS查询',
'detect_beian': '8.聚查中的备案相关查询',
'detect_intercept': '9.聚查中的拦截检测相关查询',
'detect_juziseo': '10.桔子历史',
'detect_juziseo_outlink': '11.桔子外链'
}
for key, name in option_names.items():
if key in detect_options and detect_options[key]:
options_display.append(name)
detect_options_str = " | ".join(options_display)
if not options_display:
detect_options_str = ""
detect_options_text = f"检测选项: {detect_options_str}"
# 准备代理配置文本
proxy_enable = self.worker.proxy_config.get('proxy_enable', False)
proxy_url = self.worker.proxy_config.get('proxy_url', '')
proxy_display = f"启用: {'' if proxy_enable else ''}"
if proxy_url:
proxy_display += f", URL: {proxy_url}"
proxy_text = f"代理配置: {proxy_display}"
# 准备线程数文本
thread_count_text = f"检测线程数: {self.worker.thread_count}"
logger.debug(f"准备更新标签: {detect_options_text}, {proxy_text}, {thread_count_text}")
# 检查配置标签是否存在
if hasattr(self, 'config_labels'):
# 使用QMetaObject.invokeMethod确保在主线程中更新UI
from PySide6.QtCore import QMetaObject, Qt, Q_ARG
# 更新检测选项标签
if 'detect_options' in self.config_labels:
QMetaObject.invokeMethod(self.config_labels['detect_options'], "setText",
Qt.QueuedConnection,
Q_ARG(str, detect_options_text))
# 更新代理配置标签
if 'proxy_config' in self.config_labels:
QMetaObject.invokeMethod(self.config_labels['proxy_config'], "setText",
Qt.QueuedConnection,
Q_ARG(str, proxy_text))
# 更新线程数标签
if 'thread_count' in self.config_labels:
QMetaObject.invokeMethod(self.config_labels['thread_count'], "setText",
Qt.QueuedConnection,
Q_ARG(str, thread_count_text))
logger.debug("GUI配置标签更新完成")
else:
logger.debug("配置标签不存在,跳过更新")
except Exception as e:
logger.error(f"更新GUI标签失败: {e}")
import traceback
logger.error(traceback.format_exc())
else:
logger.debug("worker对象不存在")
except Exception as e:
logger.error(f"执行update_config_labels失败: {e}")
import traceback
logger.error(traceback.format_exc())
from PySide6.QtCore import QObject, Signal, QEvent
class ConfigUpdateEvent(QEvent):
"""
配置更新事件类
"""
Type = QEvent.Type(QEvent.User + 1)
def __init__(self):
super().__init__(ConfigUpdateEvent.Type)
class ConfigUpdateSignal(QObject):
"""
配置更新信号类
"""
config_updated = Signal()
class DetectWorker:
"""
域名检测端工作类
"""
def __init__(self, main_window=None):
"""
初始化检测端
:param main_window: 主窗口实例用于更新GUI
"""
self.main_window = main_window
self.running = False
self.detect_threads = []
self.detect_thread = None # 检测线程实例
# 初始化数据库连接
self.db = Database()
# 初始化Redis连接
try:
self.redis_client = redis.Redis(
host=config.REDIS_HOST,
port=config.REDIS_PORT,
password=config.REDIS_PASSWORD,
db=config.REDIS_DB,
decode_responses=True,
socket_connect_timeout=10,
socket_timeout=10,
retry_on_timeout=True,
health_check_interval=30
)
# 测试连接
self.redis_client.ping()
logger.info(f"Redis 连接成功: {config.REDIS_HOST}:{config.REDIS_PORT}")
self.use_redis = True
except Exception as e:
logger.warning(f"Redis 连接失败: {e},将使用本地配置")
self.redis_client = None
self.use_redis = False
# 初始化配置更新信号
self.config_signal = ConfigUpdateSignal()
# 加载配置
self.detect_options = self.load_detect_options()
self.proxy_config = self.load_proxy_config()
self.thread_count = self.load_thread_count() # 从配置文件加载线程数
# 初始化代理池
self.proxy_pool = []
self.proxy_pool_lock = threading.Lock()
# 加载敏感词(只加载一次,所有线程共享)
try:
self.sensitive_words = self.db.get_all_sensitive_words()
logger.info(f"加载敏感词完成,共 {len(self.sensitive_words)} 个敏感词")
except Exception as e:
logger.error(f"加载敏感词失败: {e}")
import traceback
logger.error(traceback.format_exc())
self.sensitive_words = []
# 初始化运行状态
self.running = False
# 加载cookies
self.load_cookies_from_remote()
# 初始化JC实例暂不设置代理在开始检测时再设置
self.jc_instance = jucha.JC(proxies=None)
self.jc_instance.load_juming_cookies()
self.jc_instance.load_cookies()
# 初始化Juziseo实例暂不设置代理在开始检测时再设置
self.juziseo_instance = juziseo.Juziseo(proxies=None)
self.juziseo_instance.load_cookies()
# 显示所有配置信息
logger.info("检测端配置信息:")
logger.info(f"检测选项: {self.detect_options}")
logger.info(f"代理配置: {self.proxy_config}")
logger.info(f"检测线程数: {self.thread_count}")
# Redis订阅线程将在start方法中启动
def connect_signals(self):
"""
连接信号到槽函数
"""
if self.main_window:
try:
# 连接信号到槽函数
self.config_signal.config_updated.connect(self.main_window.update_config_labels)
logger.debug("信号连接成功")
# 连接后立即更新一次配置
self.update_config_labels()
except Exception as e:
logger.error(f"连接信号失败: {e}")
def load_detect_options(self):
"""
加载检测选项
"""
try:
# 从Redis获取配置
if self.use_redis:
detect_options_str = self.redis_client.get('domain_tool:detect_options')
if detect_options_str:
detect_options = json.loads(detect_options_str)
logger.info(f"从Redis加载检测选项成功: {detect_options}")
return detect_options
# 从本地文件获取配置
if os.path.exists('detect_options.json'):
with open('detect_options.json', 'r', encoding='utf-8') as f:
detect_options = json.load(f)
logger.info(f"从本地文件加载检测选项成功: {detect_options}")
return detect_options
else:
# 默认值:全部启用
default_options = {
'detect_register': True,
'detect_chinaz': True,
'detect_aizhan': True,
'detect_baidu_site': True,
'detect_360_site': True,
'detect_baidu_security': True,
'detect_whois': True,
'detect_beian': True,
'detect_intercept': True,
'detect_juziseo': True,
'detect_juziseo_outlink': True
}
logger.info(f"使用默认检测选项: {default_options}")
return default_options
except Exception as e:
logger.error(f"加载检测选项失败: {e}")
default_options = {
'detect_register': True,
'detect_chinaz': True,
'detect_aizhan': True,
'detect_baidu_site': True,
'detect_360_site': True,
'detect_baidu_security': True,
'detect_whois': True,
'detect_beian': True,
'detect_intercept': True,
'detect_juziseo': True,
'detect_juziseo_outlink': True
}
logger.info(f"使用默认检测选项: {default_options}")
return default_options
def load_proxy_config(self):
"""
加载代理配置
"""
try:
# 从Redis获取配置
if self.use_redis:
proxy_config_str = self.redis_client.get('domain_tool:proxy_config')
if proxy_config_str:
proxy_config = json.loads(proxy_config_str)
logger.info(f"从Redis加载代理配置成功: {proxy_config}")
return proxy_config
# 从本地文件获取配置
if os.path.exists('proxy_config.json'):
with open('proxy_config.json', 'r', encoding='utf-8') as f:
proxy_config = json.load(f)
logger.info(f"从本地文件加载代理配置成功: {proxy_config}")
# 如果启用了代理且提供了代理URL尝试获取代理
if proxy_config.get('proxy_enable', False) and proxy_config.get('proxy_url', ''):
try:
import requests
response = requests.get(proxy_config['proxy_url'], timeout=10)
if response.status_code == 200:
proxy_data = response.json()
logger.info(f"从代理API获取代理成功: {proxy_data}")
# 构建代理URL
if 'ip' in proxy_data and 'port' in proxy_data:
ip = proxy_data['ip']
port = proxy_data['port']
username = proxy_data.get('username', '')
password = proxy_data.get('password', '')
if username and password:
proxy_url = f"http://{username}:{password}@{ip}:{port}"
else:
proxy_url = f"http://{ip}:{port}"
proxy_config['proxy_url'] = proxy_url
logger.info(f"构建代理URL成功: {proxy_url}")
except Exception as e:
logger.error(f"获取代理失败: {e}")
return proxy_config
else:
default_config = {'proxy_enable': False, 'proxy_url': ''}
logger.info(f"使用默认代理配置: {default_config}")
return default_config
except Exception as e:
logger.error(f"加载代理配置失败: {e}")
default_config = {'proxy_enable': False, 'proxy_url': ''}
logger.info(f"使用默认代理配置: {default_config}")
return default_config
def load_thread_count(self):
"""
加载检测线程数
"""
try:
# 从Redis获取配置
if self.use_redis:
thread_count_str = self.redis_client.get('domain_tool:thread_count')
if thread_count_str:
# 转换为整数,确保线程数有效
thread_count = max(1, int(thread_count_str))
logger.info(f"从Redis加载检测线程数成功: {thread_count}")
return thread_count
# 从本地文件获取配置
if os.path.exists('thread_count.json'):
with open('thread_count.json', 'r', encoding='utf-8') as f:
thread_config = json.load(f)
thread_count = thread_config.get('thread_count', '100')
# 转换为整数,确保线程数有效
thread_count = max(1, int(thread_count))
logger.info(f"从本地文件加载检测线程数成功: {thread_count}")
return thread_count
else:
default_thread_count = 4 # 默认线程数,降低线程数量以减轻系统负担
logger.info(f"使用默认检测线程数: {default_thread_count}")
return default_thread_count
except Exception as e:
logger.error(f"加载检测线程数失败: {e}")
default_thread_count = 4 # 默认线程数,降低线程数量以减轻系统负担
logger.info(f"使用默认检测线程数: {default_thread_count}")
return default_thread_count
def test_proxy(self, proxy_item, result_queue):
"""
测试单个代理的可用性
:param proxy_item: 代理信息
:param result_queue: 结果队列
"""
try:
if 'ip' in proxy_item and 'port' in proxy_item:
ip = proxy_item['ip']
port = proxy_item['port']
username = proxy_item.get('username', '')
password = proxy_item.get('password', '')
if username and password:
proxy_url = f"http://{username}:{password}@{ip}:{port}"
else:
proxy_url = f"http://{ip}:{port}"
# 检查代理是否可用
import requests
test_proxies = {
'http': proxy_url,
'https': proxy_url
}
test_response = requests.get('https://m.baidu.com', proxies=test_proxies, timeout=5)
if test_response.status_code == 200:
# logger.info(f"代理可用性检查通过: {proxy_url}")
result_queue.put(test_proxies)
else:
logger.warning(f"代理可用性检查失败,状态码: {test_response.status_code}, 代理: {proxy_url}")
except Exception as e:
# logger.error(f"代理可用性检查失败: {e}, 代理: {proxy_item}")
pass
finally:
# 确保队列中添加一个标记,表示该线程已完成
result_queue.put(None)
def refresh_proxy_pool(self):
"""
刷新代理池
"""
if not self.proxy_config.get('proxy_enable', False):
return
try:
proxy_api_url = self.proxy_config.get('proxy_url', '')
if proxy_api_url:
import requests
import threading
from queue import Queue
response = requests.get(proxy_api_url, timeout=10)
if response.status_code == 200:
proxy_data = response.json()
# logger.info(f"从代理API获取代理列表成功: {proxy_data}")
# 检查是否返回了代理列表
if 'list' in proxy_data and isinstance(proxy_data['list'], list):
proxy_list = proxy_data['list']
result_queue = Queue()
threads = []
# 创建线程进行并行检测
for proxy_item in proxy_list:
thread = threading.Thread(target=self.test_proxy, args=(proxy_item, result_queue))
thread.daemon = True
thread.start()
threads.append(thread)
# 收集检测结果
new_proxies = []
completed = 0
# 设置超时,避免队列阻塞
import time
start_time = time.time()
timeout = 30 # 30秒超时
while completed < len(threads) and time.time() - start_time < timeout:
try:
result = result_queue.get(timeout=1)
if result is not None:
# 为每个代理添加使用次数计数
new_proxies.append({'proxy': result, 'usage_count': 0})
completed += 1
except:
# 队列超时,继续循环
pass
# 等待所有线程完成,但设置超时
for thread in threads:
try:
thread.join(timeout=5)
except:
# 线程超时,继续处理
pass
with self.proxy_pool_lock:
self.proxy_pool = new_proxies
logger.info(f"代理池刷新完成,共 {len(new_proxies)} 个可用代理")
except Exception as e:
logger.error(f"刷新代理池失败: {e}")
def remove_proxy(self, proxy):
"""
从代理池中移除失效的代理
:param proxy: 失效的代理
"""
with self.proxy_pool_lock:
# 检查代理池中的代理结构
if self.proxy_pool and isinstance(self.proxy_pool[0], dict) and 'proxy' in self.proxy_pool[0]:
# 新的代理池结构
for i, proxy_item in enumerate(self.proxy_pool):
if proxy_item['proxy'] == proxy:
self.proxy_pool.pop(i)
logger.info(f"从代理池移除失效代理: {proxy}")
break
else:
# 旧的代理池结构
if proxy in self.proxy_pool:
self.proxy_pool.remove(proxy)
logger.info(f"从代理池移除失效代理: {proxy}")
# 如果代理池中的代理数量少于当前设定的线程数量,自动刷新代理池
if len(self.proxy_pool) < self.thread_count:
# logger.info(f"代理池代理数量不足(当前 {len(self.proxy_pool)} 个,需要至少 {self.thread_count} 个),刷新代理池")
# 释放锁后再刷新代理池,避免死锁
import threading
threading.Thread(target=self.refresh_proxy_pool, daemon=True).start()
def get_proxies(self):
"""
获取代理配置
"""
if self.proxy_config.get('proxy_enable', False):
# 检查代理池是否为空或代理数量不足,如果是则刷新
with self.proxy_pool_lock:
is_empty = not self.proxy_pool
is_insufficient = len(self.proxy_pool) < self.thread_count
if is_empty or is_insufficient:
if is_empty:
logger.info("代理池为空,刷新代理池")
else:
# logger.info(f"代理池代理数量不足(当前 {len(self.proxy_pool)} 个,需要至少 {self.thread_count} 个),刷新代理池")
pass
# 在锁外刷新代理池,避免死锁
self.refresh_proxy_pool()
# 再次获取锁,检查代理池并选择代理
with self.proxy_pool_lock:
# 从代理池中选择一个代理FIFO优先使用最早进入池的IP
if self.proxy_pool:
# 检查代理池中的代理结构
if isinstance(self.proxy_pool[0], dict) and 'proxy' in self.proxy_pool[0]:
# 从开头取出代理实现FIFO
proxy_item = self.proxy_pool.pop(0)
proxy = proxy_item['proxy']
usage_count = proxy_item.get('usage_count', 0)
# logger.info(f"从代理池选择代理: {proxy},已使用次数: {usage_count}")
# 增加使用次数
usage_count += 1
# 如果使用次数小于3次将代理放回池尾
if usage_count < 3:
proxy_item['usage_count'] = usage_count
self.proxy_pool.append(proxy_item)
# logger.info(f"代理使用次数更新为: {usage_count},放回代理池")
else:
# logger.info(f"代理使用次数达到3次丢弃代理: {proxy}")
pass
else:
# 兼容旧的代理池结构
proxy = self.proxy_pool.pop(0)
logger.info(f"从代理池选择代理: {proxy}")
# 将代理放回池尾
self.proxy_pool.append(proxy)
return proxy
logger.info("未使用代理,使用直接连接")
return None
def detect_domain(self, domain_id, domain):
"""
检测单个域名
:param domain_id: 域名ID
:param domain: 域名对象包含id、domain、source_type等字段
"""
import threading
thread_id = threading.current_thread().ident
# 从域名对象中获取域名字符串
domain_name = domain.get('domain', '')
logger.info(f"线程 {thread_id} 开始检测域名: {domain_name}")
try:
# 检查是否需要停止
if not self.running:
logger.info(f"检测已停止,跳过域名: {domain_name}")
return
logger.info(f"开始检测域名: {domain_name}")
# 使用共享的敏感词列表
sensitive_words = self.sensitive_words
logger.debug(f"线程 {thread_id} 使用共享敏感词,共 {len(sensitive_words)} 个敏感词")
# 1. 检测注册
if self.detect_options.get('detect_register', True):
# 检查是否为一口价域名从域名对象中获取source_type1表示聚名一口价
is_ykj = domain.get('source_type', 0) == 1
if not is_ykj:
logger.info(f"检测注册状态: {domain_name}")
try:
tld = domain_name.split('.')[-1]
proxy = self.get_proxies()
# 检测代理是否可用
if proxy:
status, expire_date = register.check_register(domain_name, tld, proxy)
else:
# 代理不可用,直接检测
status, expire_date = register.check_register(domain_name, tld, None)
# 当status不是-1时才更新数据
if status != -1:
self.db.update_domain_register_status(domain_id, status)
if expire_date:
# 将expire_date增加75天
from datetime import datetime, timedelta
try:
# 处理可能包含时间部分的日期字符串
# 只取日期部分,忽略时间
date_part = expire_date.split(' ')[0]
expire_date_obj = datetime.strptime(date_part, "%Y-%m-%d")
new_expire_date = expire_date_obj + timedelta(days=75)
new_expire_date_str = new_expire_date.strftime("%Y-%m-%d")
self.db.update_domain_expire_date(domain_id, new_expire_date_str)
except Exception as e:
logger.error(f"处理过期日期失败: {e}")
# 如果处理失败,使用原始日期
self.db.update_domain_expire_date(domain_id, expire_date)
logger.info(f"注册状态检测完成: {domain_name}, 状态: {status}, 过期日期: {expire_date}")
else:
logger.warning(f"注册状态检测失败,不更新数据: {domain_name}")
# 如果使用了代理且检测失败,可能是代理失效,将其从代理池中移除
if proxy:
self.remove_proxy(proxy)
except Exception as e:
logger.error(f"注册状态检测失败: {domain_name}, 错误: {e}")
# 如果使用了代理且检测失败,可能是代理失效,将其从代理池中移除
if 'proxy' in locals() and locals()['proxy']:
self.remove_proxy(locals()['proxy'])
else:
logger.info(f"一口价域名跳过注册状态检测: {domain_name}")
# 2. 站长之家查询
if self.detect_options.get('detect_chinaz', True):
logger.info(f"检测站长之家: {domain_name}")
try:
# 使用chinaz模块的check_title方法
proxy = self.get_proxies()
success, message, seo_data = chinaz.check_title(domain_name, sensitive_words, proxy)
if not success:
logger.warning(f"站长之家检测未通过: {domain_name}, 原因: {message}")
# 如果是 'failure' 则不拉黑,继续执行下一个检测
if message != 'failure':
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, message)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
else:
logger.info(f"站长之家检测返回 failure不拉黑域名: {domain_name}")
logger.info(f"站长之家检测完成: {domain_name}")
except Exception as e:
logger.error(f"站长之家检测失败: {domain_name}, 错误: {e}")
# 如果使用了代理且检测失败,可能是代理失效,将其从代理池中移除
if 'proxy' in locals() and locals()['proxy']:
self.remove_proxy(locals()['proxy'])
# 3. 爱站网查询
if self.detect_options.get('detect_aizhan', True):
logger.info(f"检测爱站网: {domain_name}")
try:
# 使用aizhan模块的check_aizhan方法
proxy = self.get_proxies()
success, message = aizhan.check_aizhan(domain_name, sensitive_words, proxy)
if not success:
logger.warning(f"爱站网检测未通过: {domain_name}, 原因: {message}")
# 如果是 'failure' 则不拉黑,继续执行下一个检测
if message != 'failure':
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, message)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
else:
logger.info(f"爱站网检测返回 failure不拉黑域名: {domain_name}")
logger.info(f"爱站网检测完成: {domain_name}")
except Exception as e:
logger.error(f"爱站网检测失败: {domain_name}, 错误: {e}")
# 如果使用了代理且检测失败,可能是代理失效,将其从代理池中移除
if 'proxy' in locals() and locals()['proxy']:
self.remove_proxy(locals()['proxy'])
# 4. 百度site查询
if self.detect_options.get('detect_baidu_site', True):
logger.info(f"检测百度: {domain_name}")
try:
# 使用baidu模块的check_site方法
proxy = self.get_proxies()
success, message = baidu.check_site(domain_name, sensitive_words, proxy)
if not success:
logger.warning(f"百度site检测未通过: {domain_name}, 原因: {message}")
# 如果是代理导致的失败,尝试不使用代理重新检测
if 'proxy' in locals() and locals()['proxy'] and ('timeout' in message.lower() or 'connection' in message.lower()):
logger.info(f"代理检测失败,尝试不使用代理重新检测: {domain_name}")
success, message = baidu.check_site(domain_name, sensitive_words, None)
if success:
logger.info(f"不使用代理检测百度成功: {domain_name}")
else:
logger.warning(f"不使用代理检测百度仍未通过: {domain_name}, 原因: {message}")
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, message)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
else:
# 其他原因导致的失败,直接加入黑名单
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, message)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
logger.info(f"百度site检测完成: {domain_name}")
except Exception as e:
logger.error(f"百度site检测失败: {domain_name}, 错误: {e}")
# 如果使用了代理且检测失败,可能是代理失效,将其从代理池中移除
if 'proxy' in locals() and locals()['proxy']:
self.remove_proxy(locals()['proxy'])
# 尝试不使用代理重新检测
try:
logger.info(f"尝试不使用代理重新检测百度: {domain_name}")
success, message = baidu.check_site(domain_name, sensitive_words, None)
if success:
logger.info(f"不使用代理检测百度成功: {domain_name}")
else:
logger.warning(f"不使用代理检测百度仍未通过: {domain_name}, 原因: {message}")
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, message)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
except Exception as e2:
logger.error(f"不使用代理检测百度也失败: {domain_name}, 错误: {e2}")
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, str(e2))
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
# 5. 360的site查询
if self.detect_options.get('detect_360_site', True):
logger.info(f"检测360: {domain_name}")
try:
passed, message = c360.check_domain(domain_name, sensitive_words, self.get_proxies())
if not passed:
logger.warning(f"360检测未通过: {domain_name}, 原因: {message}")
# 如果是 'failure' 则不拉黑,继续执行下一个检测
if message != 'failure':
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, message)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
else:
logger.info(f"360检测返回 failure不拉黑域名: {domain_name}")
logger.info(f"360检测完成: {domain_name}")
except Exception as e:
logger.error(f"360检测失败: {domain_name}, 错误: {e}")
# 6. 百度网址安全中心查询
if self.detect_options.get('detect_baidu_security', True):
logger.info(f"检测百度网页安全中心: {domain_name}")
try:
# 使用baidu模块的baidu函数
security_status = baidu.baidu(domain_name, self.get_proxies())
if security_status in ['风险网站提示', '高危网站提示']:
logger.warning(f"百度网页安全中心检测未通过: {domain_name}, 原因: {security_status}")
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, security_status)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
logger.info(f"百度网页安全中心检测完成: {domain_name}, 状态: {security_status}")
except Exception as e:
logger.error(f"百度网页安全中心检测失败: {domain_name}, 错误: {e}")
# 7. 聚查中的WHOIS查询
if self.detect_options.get('detect_whois', True):
logger.info(f"检测聚查WHOIS: {domain_name}")
try:
# 使用jc_instance的check_whois_domain方法
success, message, whois_info = self.jc_instance.check_whois_domain(domain_name)
if not success:
logger.warning(f"聚查WHOIS查询失败: {domain_name}, 原因: {message}")
else:
logger.info(f"WHOIS查询结果: {success}, {message}, {whois_info}")
# 检查域名状态是否包含clientHold或serverHold
if 'clientHold' in whois_info or 'serverHold' in whois_info:
logger.warning(f"聚查WHOIS检测未通过: {domain_name}, 原因: 域名状态包含clientHold或serverHold")
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, "域名状态包含clientHold或serverHold")
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
logger.info(f"聚查WHOIS检测完成: {domain_name}")
except Exception as e:
logger.error(f"聚查WHOIS检测失败: {domain_name}, 错误: {e}")
# 8. 聚查中的备案相关查询
if self.detect_options.get('detect_beian', True):
logger.info(f"检测聚查备案: {domain_name}")
try:
# 使用jc_instance的beian_check_domain方法
success, message, beian_info = self.jc_instance.beian_check_domain(domain_name)
if not success:
logger.warning(f"聚查备案查询失败: {domain_name}, 原因: {message}")
else:
logger.info(f"备案查询结果: {success}, {message}, {beian_info}")
# 解析备案信息并更新数据库
if isinstance(beian_info, tuple) and len(beian_info) == 4:
beian_time, company_type, website_url, has_beian = beian_info
# 处理是否备案字段
# 1:待检测;2:有备案记录;3:没有备案记录
has_beian_flag = 2 if has_beian == '当前存在' else 3
# 计算备案年份
beian_year = None
if beian_time:
try:
beian_year = beian_time.split('-')[0]
except:
pass
# 更新数据库
self.db.update_domain_beian_info(domain_id, company_type, website_url, has_beian_flag, beian_year)
logger.info(f"聚查备案检测完成: {domain_name}")
except Exception as e:
logger.error(f"聚查备案检测失败: {domain_name}, 错误: {e}")
# 9. 聚查中的拦截检测相关查询
if self.detect_options.get('detect_intercept', True):
logger.info(f"检测聚查拦截: {domain_name}")
try:
# 使用jc_instance的safe_check_domain方法
success, message, safe_info = self.jc_instance.safe_check_domain(domain_name)
if not success:
logger.warning(f"聚查安全检测失败: {domain_name}, 原因: {message}")
else:
logger.info(f"安全检测结果: {success}, {message}, {safe_info}")
# 检测项顺序
check_items = [
'QQ检测',
'微信检测',
'抖音检测',
'被墙检测',
'百度检测',
'谷歌检测',
'火狐检测'
]
# 检查是否有检测项返回3拦截或状态码为2且消息为拦截
blacklist_reason = []
if isinstance(safe_info, tuple):
for i, item in enumerate(safe_info):
if isinstance(item, tuple):
if len(item) >= 1 and item[0] == 3:
if i < len(check_items):
blacklist_reason.append(f"{check_items[i]}: 拦截")
elif len(item) >= 2 and item[0] == 2 and '拦截' in item[1]:
if i < len(check_items):
blacklist_reason.append(f"{check_items[i]}: 拦截")
if blacklist_reason:
reason_str = "".join(blacklist_reason)
logger.warning(f"聚查安全检测未通过: {domain_name}, 原因: {reason_str}")
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, reason_str)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
logger.info(f"聚查拦截检测完成: {domain_name}")
except Exception as e:
logger.error(f"聚查拦截检测失败: {domain_name}, 错误: {e}")
# 10. 桔子历史
if self.detect_options.get('detect_juziseo', True):
logger.info(f"检测桔子历史: {domain_name}")
try:
# 使用juziseo_instance的check_history方法
success, message = self.juziseo_instance.check_history(domain_name, sensitive_words)
if not success:
logger.warning(f"桔子历史检测未通过: {domain_name}, 原因: {message}")
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, message)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
logger.info(f"桔子历史检测完成: {domain_name}")
except Exception as e:
logger.error(f"桔子历史检测失败: {domain_name}, 错误: {e}")
# 11. 桔子外链
if self.detect_options.get('detect_juziseo_outlink', True):
logger.info(f"检测桔子外链: {domain_name}")
try:
# 使用juziseo_instance的check_external_link方法
success, message = self.juziseo_instance.check_external_link(domain_name, sensitive_words)
if not success:
logger.warning(f"桔子外链检测未通过: {domain_name}, 原因: {message}")
# 将域名加入黑名单
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain_name, message)
logger.info(f"域名已加入黑名单: {domain_name}")
return # 检测未通过,直接返回
logger.info(f"桔子外链检测完成: {domain_name}")
except Exception as e:
logger.error(f"桔子外链检测失败: {domain_name}, 错误: {e}")
# 更新检测状态为已完成
self.db.update_domain_detect_status(domain_id, 1) # 1 表示检测完成
# 检查是否满足特定条件如果满足且expire_date不为空则将其置空
try:
# 获取域名的详细信息
domain_info = self.db.get_domain_by_id(domain_id)
if domain_info:
use_status = domain_info.get('use_status', 0)
register_status = domain_info.get('register_status', 0)
detect_status = domain_info.get('detect_status', 0)
expire_date = domain_info.get('expire_date')
# 检查是否满足条件将expire_date置空
if use_status == 0 and register_status == 2 and expire_date:
# 将expire_date置空
self.db.update_domain_expire_date(domain_id, None)
logger.info(f"域名 {domain_name} 满足条件已将expire_date置空")
# 检查是否满足人工复核条件:没有被拉黑且注册状态为可注册
if detect_status != 3 and register_status == 2:
# 更新人工复核状态为1待人工复核
self.db.update_domain_review_status(domain_id, 1)
logger.info(f"域名 {domain_name} 满足条件,已将人工复核状态设置为待人工复核")
except Exception as e:
logger.error(f"处理expire_date置空和人工复核状态失败: {e}")
logger.info(f"域名检测完成: {domain_name}")
except Exception as e:
logger.error(f"检测域名出错: {domain_name}, 错误: {e}")
# 更新检测状态为失败
self.db.update_domain_detect_status(domain_id, 3) # 3 表示检测失败
def start_detection(self):
"""
开始检测
"""
logger.info("开始执行域名检测任务")
# 重新加载配置,确保获取最新的配置
try:
self.detect_options = self.load_detect_options()
self.proxy_config = self.load_proxy_config()
self.thread_count = self.load_thread_count()
logger.info(f"检测线程数设置为: {self.thread_count}")
self.load_cookies_from_remote()
except Exception as e:
logger.error(f"加载配置失败: {e}")
import traceback
logger.error(traceback.format_exc())
return
# 重新加载cookies
self.load_cookies_from_remote()
# 重新加载JC实例的cookies
if hasattr(self, 'jc_instance'):
self.jc_instance.load_cookies()
self.jc_instance.load_juming_cookies()
# 重新加载Juziseo实例的cookies
if hasattr(self, 'juziseo_instance'):
self.juziseo_instance.load_cookies()
# 刷新代理池
if self.proxy_config.get('proxy_enable', False):
logger.info("开始检测,刷新代理池")
self.refresh_proxy_pool()
# 更新JC和Juziseo实例的代理设置
if hasattr(self, 'jc_instance'):
self.jc_instance.proxies = self.get_proxies()
if hasattr(self, 'juziseo_instance'):
self.juziseo_instance.proxies = self.get_proxies()
# 重新加载敏感词,确保获取最新的敏感词列表
try:
self.sensitive_words = self.db.get_all_sensitive_words()
logger.info(f"重新加载敏感词完成,共 {len(self.sensitive_words)} 个敏感词")
except Exception as e:
logger.error(f"重新加载敏感词失败: {e}")
import traceback
logger.error(traceback.format_exc())
self.sensitive_words = []
# 更新配置标签
self.update_config_labels()
try:
batch_size = 1000
total_processed = 0
while self.running:
# 获取需要检测的域名
domains = self.db.get_domains_to_detect(limit=batch_size)
current_batch_size = len(domains)
logger.info(f"获取到 {current_batch_size} 个需要检测的域名")
if not domains:
logger.info("没有需要检测的域名")
break
# 检查是否需要停止获取域名当获取的数量少于1000时
should_stop = current_batch_size < batch_size
# 创建线程池,限制同时运行的线程数量
active_threads = []
max_threads = self.thread_count
try:
logger.info(f"开始创建线程,当前批次域名数: {current_batch_size},最大线程数: {max_threads}")
for i, domain in enumerate(domains):
# 检查是否需要停止
if not self.running:
logger.info("检测已停止,停止创建新线程")
break
# 等待线程数量降到最大值以下
while len([t for t in active_threads if t.is_alive()]) >= max_threads:
# 清理已完成的线程
active_threads = [t for t in active_threads if t.is_alive()]
# 短暂休眠避免CPU占用过高
import time
time.sleep(0.1)
domain_id = domain['id']
domain_name = domain['domain']
# 更新进度
if self.detect_thread:
try:
# 通过信号发送进度更新
self.detect_thread.progress_signal.emit(total_processed + i, total_processed + current_batch_size)
except Exception as e:
logger.error(f"发送进度更新信号失败: {e}")
import traceback
logger.error(traceback.format_exc())
# 创建线程
try:
thread = threading.Thread(target=self.detect_domain, args=(domain_id, domain))
active_threads.append(thread)
logger.debug(f"创建线程 {i+1} 成功,域名: {domain_name}")
except Exception as e:
logger.error(f"创建线程失败: {e}")
import traceback
logger.error(traceback.format_exc())
continue
# 启动线程
try:
thread.start()
logger.debug(f"启动线程 {i+1} 成功,域名: {domain_name}")
except Exception as e:
logger.error(f"启动线程失败: {e}")
import traceback
logger.error(traceback.format_exc())
active_threads.remove(thread)
continue
# 显示当前实际线程数量
current_active = len([t for t in active_threads if t.is_alive()])
logger.info(f"当前实际线程数量: {current_active}/{max_threads}")
# 更新GUI显示
if self.detect_thread:
try:
# 通过信号发送线程数量更新
self.detect_thread.thread_count_signal.emit(current_active, max_threads)
except Exception as e:
logger.error(f"发送线程数量更新信号失败: {e}")
import traceback
logger.error(traceback.format_exc())
except Exception as e:
logger.error(f"创建或启动线程失败: {e}")
import traceback
logger.error(traceback.format_exc())
# 等待所有线程完成
logger.info(f"等待剩余 {len(active_threads)} 个线程完成")
for i, thread in enumerate(active_threads):
# 检查是否需要停止
if not self.running:
logger.info("检测已停止,停止等待线程完成")
break
try:
thread.join(timeout=30) # 添加超时,避免线程阻塞
# 更新进度
if self.detect_thread:
try:
# 通过信号发送进度更新
self.detect_thread.progress_signal.emit(total_processed + i + 1, total_processed + current_batch_size)
except Exception as e:
logger.error(f"发送进度更新信号失败: {e}")
import traceback
logger.error(traceback.format_exc())
except Exception as e:
logger.error(f"等待线程完成失败: {e}")
import traceback
logger.error(traceback.format_exc())
# 发送线程数量更新信号
if self.detect_thread:
try:
active_count = threading.active_count()
self.detect_thread.thread_count_signal.emit(active_count, max_threads)
except Exception as e:
logger.error(f"发送线程数量更新信号失败: {e}")
import traceback
logger.error(traceback.format_exc())
# 完成当前批次进度
if self.detect_thread:
try:
# 通过信号发送进度更新
self.detect_thread.progress_signal.emit(total_processed + current_batch_size, total_processed + current_batch_size)
except Exception as e:
logger.error(f"发送进度更新信号失败: {e}")
import traceback
logger.error(traceback.format_exc())
total_processed += current_batch_size
# 如果当前批次数量少于batch_size检测完成后停止获取域名
if should_stop:
logger.info(f"当前批次域名数量 ({current_batch_size}) 少于 {batch_size},停止获取域名")
break
logger.info(f"当前批次检测完成,累计处理 {total_processed} 个域名")
# 完成进度
if self.detect_thread:
try:
# 通过信号发送进度更新
self.detect_thread.progress_signal.emit(100, 100)
except Exception as e:
logger.error(f"发送进度更新信号失败: {e}")
import traceback
logger.error(traceback.format_exc())
logger.info("域名检测任务完成")
except Exception as e:
logger.error(f"执行检测任务出错: {e}")
def run_daily_task(self):
"""
执行每日检测任务
"""
logger.info(f"[{datetime.now()}] 开始每日检测任务")
self.start_detection()
logger.info(f"[{datetime.now()}] 每日检测任务完成")
def start_scheduler(self):
"""
启动定时任务
"""
# 每天凌晨1点执行检测
schedule.every().day.at("01:00").do(self.run_daily_task)
logger.info("定时任务已启动每天凌晨1点执行检测")
# 立即执行一次检测
self.run_daily_task()
# 循环执行定时任务
while self.running:
schedule.run_pending()
time.sleep(60)
def start(self):
"""
启动检测端
"""
logger.info("启动域名检测端")
self.running = True
# 启动定时任务
scheduler_thread = threading.Thread(target=self.start_scheduler)
scheduler_thread.daemon = True
scheduler_thread.start()
# 保持程序运行
try:
while self.running:
time.sleep(1)
except KeyboardInterrupt:
logger.info("检测端已停止")
self.running = False
def load_cookies_from_remote(self):
"""
从远程加载cookies
"""
try:
if self.use_redis:
# 加载聚名cookies
juming_cookies_str = self.redis_client.get('domain_tool:juming_cookies')
if juming_cookies_str:
# 将cookies保存到本地文件
import pickle
from requests.cookies import RequestsCookieJar
# 创建cookie jar
cookie_jar = RequestsCookieJar()
# 解析cookies字符串
try:
import ast
cookies_dict = ast.literal_eval(juming_cookies_str)
for name, value in cookies_dict.items():
cookie_jar.set(name, value)
except Exception as e:
logger.error(f"解析聚名cookies失败: {e}")
# 保存到本地文件
try:
with open('juming_cookies.pkl', 'wb') as f:
pickle.dump(cookie_jar, f)
logger.info("从Redis加载聚名cookies成功并保存到本地")
except Exception as e:
logger.error(f"保存聚名cookies到本地失败: {e}")
# 加载聚查cookies
jucha_cookies_str = self.redis_client.get('domain_tool:jucha_cookies')
if jucha_cookies_str:
# 将cookies保存到本地文件
import pickle
from requests.cookies import RequestsCookieJar
# 创建cookie jar
cookie_jar = RequestsCookieJar()
# 解析cookies字符串
try:
import ast
cookies_dict = ast.literal_eval(jucha_cookies_str)
for name, value in cookies_dict.items():
cookie_jar.set(name, value)
except Exception as e:
logger.error(f"解析聚查cookies失败: {e}")
# 保存到本地文件
try:
with open('jucha_cookies.pkl', 'wb') as f:
pickle.dump(cookie_jar, f)
logger.info("从Redis加载聚查cookies成功并保存到本地")
except Exception as e:
logger.error(f"保存聚查cookies到本地失败: {e}")
# 加载桔子SEO cookies
juziseo_cookies_str = self.redis_client.get('domain_tool:juziseo_cookies')
if juziseo_cookies_str:
# 将cookies保存到本地文件
import pickle
from requests.cookies import RequestsCookieJar
# 创建cookie jar
cookie_jar = RequestsCookieJar()
# 解析cookies字符串
try:
import ast
cookies_dict = ast.literal_eval(juziseo_cookies_str)
for name, value in cookies_dict.items():
cookie_jar.set(name, value)
except Exception as e:
logger.error(f"解析桔子SEO cookies失败: {e}")
# 保存到本地文件
try:
with open('juziseo_cookies.pkl', 'wb') as f:
pickle.dump(cookie_jar, f)
logger.info("从Redis加载桔子SEO cookies成功并保存到本地")
except Exception as e:
logger.error(f"保存桔子SEO cookies到本地失败: {e}")
except Exception as e:
logger.error(f"从远程加载cookies失败: {e}")
def update_config_labels(self):
"""
更新GUI配置标签
"""
# 使用QCoreApplication.postEvent发送配置更新事件这是最安全的方法
try:
logger.debug("发送配置更新事件")
# 确保主窗口存在
if self.main_window:
from PySide6.QtCore import QCoreApplication
event = ConfigUpdateEvent()
QCoreApplication.postEvent(self.main_window, event)
logger.debug("配置更新事件已发送")
else:
logger.debug("主窗口不存在,跳过配置更新")
except Exception as e:
logger.error(f"发送配置更新事件失败: {e}")
def start_redis_subscription(self):
"""
启动Redis订阅监听配置更新
"""
while self.running:
try:
# 创建新的Redis客户端用于订阅
redis_sub_client = redis.Redis(
host=config.REDIS_HOST,
port=config.REDIS_PORT,
password=config.REDIS_PASSWORD,
db=config.REDIS_DB,
decode_responses=True,
socket_connect_timeout=30, # 增加连接超时时间
socket_timeout=60, # 增加读取超时时间
retry_on_timeout=True,
health_check_interval=30
)
# 订阅配置更新频道
pubsub = redis_sub_client.pubsub()
pubsub.subscribe('domain_tool:config_update')
# 使用logger.debug输出到文件日志不输出到GUI日志框
logger.debug("开始监听配置更新...")
# 循环监听消息
for message in pubsub.listen():
if not self.running:
break
if message['type'] == 'message':
config_type = message['data']
# 使用logger.debug输出到文件日志不输出到GUI日志框
logger.debug(f"收到配置更新消息: {config_type}")
# 重新加载配置
self.detect_options = self.load_detect_options()
self.proxy_config = self.load_proxy_config()
self.thread_count = self.load_thread_count()
self.load_cookies_from_remote()
# 更新GUI标签
self.update_config_labels()
# 使用logger.debug输出到文件日志不输出到GUI日志框
logger.debug("配置已更新")
except Exception as e:
logger.debug(f"Redis订阅失败: {e}")
logger.debug("3秒后尝试重新连接...")
time.sleep(3) # 增加重试间隔
def stop(self):
"""
停止检测端
"""
logger.info("停止域名检测端")
self.running = False
if __name__ == "__main__":
try:
# 配置日志
logger.add("detect_worker.log", rotation="1 day", level="DEBUG")
logger.info("程序开始运行")
# 创建应用程序
app = QApplication([])
logger.info("创建应用程序成功")
# 创建并显示主窗口
window = DetectMainWindow()
logger.info("创建主窗口成功")
window.show()
logger.info("显示主窗口成功")
# 运行应用程序
logger.info("开始运行应用程序")
app.exec()
logger.info("应用程序运行结束")
# 当窗口关闭时,停止检测端
if window.worker:
logger.info("停止检测端")
window.worker.stop()
except Exception as e:
logger.error(f"程序运行出错: {e}")
import traceback
logger.error(traceback.format_exc())
import time
time.sleep(10) # 等待10秒以便查看错误信息