1948 lines
88 KiB
Python
1948 lines
88 KiB
Python
# -*- 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_type,1表示聚名一口价)
|
||
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秒,以便查看错误信息 |