first commit
This commit is contained in:
4
app/ui/__init__.py
Normal file
4
app/ui/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
UI 模块
|
||||
'''
|
||||
BIN
app/ui/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
app/ui/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
app/ui/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/domain_filter.cpython-311.pyc
Normal file
BIN
app/ui/__pycache__/domain_filter.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/domain_filter.cpython-39.pyc
Normal file
BIN
app/ui/__pycache__/domain_filter.cpython-39.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/domain_import.cpython-311.pyc
Normal file
BIN
app/ui/__pycache__/domain_import.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/domain_import.cpython-39.pyc
Normal file
BIN
app/ui/__pycache__/domain_import.cpython-39.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/juming_crawler.cpython-311.pyc
Normal file
BIN
app/ui/__pycache__/juming_crawler.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/juming_crawler.cpython-39.pyc
Normal file
BIN
app/ui/__pycache__/juming_crawler.cpython-39.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/main_window.cpython-311.pyc
Normal file
BIN
app/ui/__pycache__/main_window.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/main_window.cpython-39.pyc
Normal file
BIN
app/ui/__pycache__/main_window.cpython-39.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/sensitive_words.cpython-311.pyc
Normal file
BIN
app/ui/__pycache__/sensitive_words.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/sensitive_words.cpython-39.pyc
Normal file
BIN
app/ui/__pycache__/sensitive_words.cpython-39.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/system_settings.cpython-311.pyc
Normal file
BIN
app/ui/__pycache__/system_settings.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/ui/__pycache__/system_settings.cpython-39.pyc
Normal file
BIN
app/ui/__pycache__/system_settings.cpython-39.pyc
Normal file
Binary file not shown.
1335
app/ui/domain_filter.py
Normal file
1335
app/ui/domain_filter.py
Normal file
File diff suppressed because it is too large
Load Diff
510
app/ui/domain_import.py
Normal file
510
app/ui/domain_import.py
Normal file
@@ -0,0 +1,510 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :domain_import.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/4/8 23:47
|
||||
@explain : 域名导入界面
|
||||
'''
|
||||
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTextEdit, QFileDialog, QProgressBar
|
||||
from PySide6.QtCore import Qt, QThread, Signal
|
||||
from loguru import logger
|
||||
|
||||
from app.core.domain_collector import DomainCollector
|
||||
|
||||
|
||||
class ImportThread(QThread):
|
||||
"""
|
||||
导入线程
|
||||
"""
|
||||
progress_updated = Signal(int)
|
||||
finished = Signal(bool, str)
|
||||
|
||||
def __init__(self, domain_list, source_type):
|
||||
"""
|
||||
初始化导入线程
|
||||
|
||||
:param domain_list: 域名列表
|
||||
:param source_type: 来源类型
|
||||
"""
|
||||
super().__init__()
|
||||
self.domain_list = domain_list
|
||||
self.source_type = source_type
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
运行导入线程
|
||||
"""
|
||||
try:
|
||||
collector = DomainCollector()
|
||||
total = len(self.domain_list)
|
||||
|
||||
# 实时更新进度:开始
|
||||
self.progress_updated.emit(0)
|
||||
|
||||
# 标准化域名和检查是否存在(占30%进度)
|
||||
normalized_domains = []
|
||||
for i, domain in enumerate(self.domain_list):
|
||||
from app.utils.domain_utils import normalize_domain
|
||||
import tldextract
|
||||
normalized = normalize_domain(domain)
|
||||
if normalized:
|
||||
# 提取顶级域名
|
||||
ext = tldextract.extract(normalized)
|
||||
tld = ext.suffix
|
||||
normalized_domains.append((normalized, tld))
|
||||
|
||||
# 更新进度
|
||||
progress = int((i + 1) / total * 30)
|
||||
self.progress_updated.emit(progress)
|
||||
|
||||
# 批量检查域名是否存在
|
||||
batch_data = []
|
||||
existing_domains = []
|
||||
if normalized_domains:
|
||||
all_domains = [domain for domain, tld in normalized_domains]
|
||||
existing_domains = collector.db.check_domains_exist(all_domains)
|
||||
existing_set = set(existing_domains)
|
||||
|
||||
# 准备批量添加数据
|
||||
for domain, tld in normalized_domains:
|
||||
if domain not in existing_set:
|
||||
batch_data.append((domain, tld, self.source_type))
|
||||
|
||||
# 分批次添加域名(占70%进度)
|
||||
batch_size = 1000
|
||||
total_batches = len(batch_data)
|
||||
for i in range(0, len(batch_data), batch_size):
|
||||
batch = batch_data[i:i+batch_size]
|
||||
collector.db.add_domains_batch(batch)
|
||||
|
||||
# 更新进度
|
||||
processed = min(i + len(batch), total_batches)
|
||||
progress = 30 + int(processed / total_batches * 70)
|
||||
self.progress_updated.emit(progress)
|
||||
|
||||
# 完成导入
|
||||
self.progress_updated.emit(100)
|
||||
|
||||
# 计算统计信息
|
||||
stats = {
|
||||
'total': total,
|
||||
'valid': len(normalized_domains),
|
||||
'added': len(batch_data),
|
||||
'exists': len(existing_domains),
|
||||
'invalid': total - len(normalized_domains),
|
||||
'failed': 0
|
||||
}
|
||||
|
||||
# 根据统计信息生成消息
|
||||
if stats['added'] > 0:
|
||||
message = f"导入完成: 总域名数 {stats['total']}, 有效域名数 {stats['valid']}, 新增域名数 {stats['added']}, 已存在域名数 {stats['exists']}, 无效域名数 {stats['invalid']}"
|
||||
else:
|
||||
message = f"导入完成: 所有域名已存在,未添加新域名"
|
||||
|
||||
self.finished.emit(True, message)
|
||||
except Exception as e:
|
||||
logger.error(f"导入失败: {e}")
|
||||
self.finished.emit(False, f"导入失败: {str(e)}")
|
||||
|
||||
|
||||
class ImportFileThread(QThread):
|
||||
"""
|
||||
文件导入线程,用于处理大文件
|
||||
"""
|
||||
progress_updated = Signal(int)
|
||||
finished = Signal(bool, str)
|
||||
|
||||
def __init__(self, file_path, source_type):
|
||||
"""
|
||||
初始化文件导入线程
|
||||
|
||||
:param file_path: 文件路径
|
||||
:param source_type: 来源类型
|
||||
"""
|
||||
super().__init__()
|
||||
self.file_path = file_path
|
||||
self.source_type = source_type
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
运行文件导入线程
|
||||
"""
|
||||
try:
|
||||
collector = DomainCollector()
|
||||
|
||||
# 首先计算文件中的域名数量
|
||||
total = 0
|
||||
encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb2312', 'cp936', 'latin-1', 'ascii']
|
||||
encoding = 'utf-8' # 默认编码
|
||||
|
||||
# 尝试不同的编码格式计算域名数量
|
||||
for enc in encodings:
|
||||
try:
|
||||
with open(self.file_path, 'r', encoding=enc) as f:
|
||||
total = sum(1 for line in f if line.strip())
|
||||
encoding = enc
|
||||
break
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
if total == 0:
|
||||
# 尝试使用二进制模式读取
|
||||
try:
|
||||
import chardet
|
||||
with open(self.file_path, 'rb') as f:
|
||||
raw_data = f.read()
|
||||
result = chardet.detect(raw_data)
|
||||
encoding = result['encoding']
|
||||
if encoding:
|
||||
total = sum(1 for line in raw_data.decode(encoding).split('\n') if line.strip())
|
||||
else:
|
||||
# 最后尝试使用 replace 模式读取
|
||||
with open(self.file_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
total = sum(1 for line in f if line.strip())
|
||||
encoding = 'utf-8'
|
||||
except Exception:
|
||||
# 最后尝试使用 replace 模式读取
|
||||
with open(self.file_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
total = sum(1 for line in f if line.strip())
|
||||
encoding = 'utf-8'
|
||||
|
||||
# 实时更新进度:开始
|
||||
self.progress_updated.emit(0)
|
||||
|
||||
# 逐行读取文件并处理域名
|
||||
normalized_domains = []
|
||||
processed = 0
|
||||
|
||||
with open(self.file_path, 'r', encoding=encoding, errors='replace') as f:
|
||||
for line in f:
|
||||
domain = line.strip()
|
||||
if domain:
|
||||
from app.utils.domain_utils import normalize_domain
|
||||
import tldextract
|
||||
normalized = normalize_domain(domain)
|
||||
if normalized:
|
||||
# 提取顶级域名
|
||||
ext = tldextract.extract(normalized)
|
||||
tld = ext.suffix
|
||||
normalized_domains.append((normalized, tld))
|
||||
|
||||
processed += 1
|
||||
# 更新进度(占30%)
|
||||
progress = int(processed / total * 30)
|
||||
self.progress_updated.emit(progress)
|
||||
|
||||
# 批量检查域名是否存在
|
||||
batch_data = []
|
||||
existing_domains = []
|
||||
if normalized_domains:
|
||||
all_domains = [domain for domain, tld in normalized_domains]
|
||||
existing_domains = collector.db.check_domains_exist(all_domains)
|
||||
existing_set = set(existing_domains)
|
||||
|
||||
# 准备批量添加数据
|
||||
for domain, tld in normalized_domains:
|
||||
if domain not in existing_set:
|
||||
batch_data.append((domain, tld, self.source_type))
|
||||
|
||||
# 分批次添加域名(占70%进度)
|
||||
batch_size = 1000
|
||||
total_batches = len(batch_data)
|
||||
for i in range(0, len(batch_data), batch_size):
|
||||
batch = batch_data[i:i+batch_size]
|
||||
collector.db.add_domains_batch(batch)
|
||||
|
||||
# 更新进度
|
||||
processed_batches = min(i + len(batch), total_batches)
|
||||
progress = 30 + int(processed_batches / total_batches * 70)
|
||||
self.progress_updated.emit(progress)
|
||||
|
||||
# 完成导入
|
||||
self.progress_updated.emit(100)
|
||||
|
||||
# 计算统计信息
|
||||
stats = {
|
||||
'total': total,
|
||||
'valid': len(normalized_domains),
|
||||
'added': len(batch_data),
|
||||
'exists': len(existing_domains),
|
||||
'invalid': total - len(normalized_domains),
|
||||
'failed': 0
|
||||
}
|
||||
|
||||
# 根据统计信息生成消息
|
||||
if stats['added'] > 0:
|
||||
message = f"导入完成: 总域名数 {stats['total']}, 有效域名数 {stats['valid']}, 新增域名数 {stats['added']}, 已存在域名数 {stats['exists']}, 无效域名数 {stats['invalid']}"
|
||||
else:
|
||||
message = f"导入完成: 所有域名已存在,未添加新域名"
|
||||
|
||||
self.finished.emit(True, message)
|
||||
except Exception as e:
|
||||
logger.error(f"导入失败: {e}")
|
||||
self.finished.emit(False, f"导入失败: {str(e)}")
|
||||
|
||||
|
||||
class DomainImportWidget(QWidget):
|
||||
"""
|
||||
域名导入界面
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化域名导入界面
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
# 创建布局
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
|
||||
|
||||
# 创建文本编辑框
|
||||
self.text_edit = QTextEdit()
|
||||
self.text_edit.setPlaceholderText("请输入域名,一行一个")
|
||||
self.text_edit.setStyleSheet("""
|
||||
QTextEdit {
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 300px;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.text_edit)
|
||||
|
||||
# 创建按钮布局
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
# 导入文件按钮
|
||||
self.import_file_btn = QPushButton("导入文件")
|
||||
self.import_file_btn.clicked.connect(self.import_file)
|
||||
self.import_file_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #0b7dda;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
""")
|
||||
button_layout.addWidget(self.import_file_btn)
|
||||
|
||||
# 开始导入按钮
|
||||
self.start_import_btn = QPushButton("开始导入")
|
||||
self.start_import_btn.clicked.connect(self.start_import)
|
||||
self.start_import_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
""")
|
||||
button_layout.addWidget(self.start_import_btn)
|
||||
button_layout.setContentsMargins(0, 15, 0, 15)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 创建进度条
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setVisible(False)
|
||||
self.progress_bar.setStyleSheet("""
|
||||
QProgressBar {
|
||||
height: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
background-color: #f0f0f0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #4CAF50;
|
||||
border-radius: 10px;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.progress_bar)
|
||||
|
||||
# 创建状态标签
|
||||
self.status_label = QLabel("")
|
||||
self.status_label.setAlignment(Qt.AlignCenter)
|
||||
self.status_label.setStyleSheet("font-size: 14px; color: #333; padding: 10px; background-color: #f0f8ff; border-radius: 4px;")
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
logger.info("域名导入界面创建完成")
|
||||
|
||||
def import_file(self):
|
||||
"""
|
||||
导入文件
|
||||
"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "文本文件 (*.txt)")
|
||||
if file_path:
|
||||
try:
|
||||
# 尝试不同的编码格式
|
||||
encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb2312', 'cp936', 'latin-1', 'ascii']
|
||||
domain_count = 0
|
||||
|
||||
# 尝试使用不同编码读取并计数
|
||||
for encoding in encodings:
|
||||
try:
|
||||
with open(file_path, 'r', encoding=encoding) as f:
|
||||
domain_count = sum(1 for line in f if line.strip())
|
||||
logger.info(f"使用编码 {encoding} 成功读取文件")
|
||||
break
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
# 如果仍然失败,尝试使用二进制模式读取并猜测编码
|
||||
if domain_count == 0:
|
||||
try:
|
||||
import chardet
|
||||
with open(file_path, 'rb') as f:
|
||||
raw_data = f.read()
|
||||
result = chardet.detect(raw_data)
|
||||
encoding = result['encoding']
|
||||
if encoding:
|
||||
domain_count = sum(1 for line in raw_data.decode(encoding).split('\n') if line.strip())
|
||||
logger.info(f"使用 chardet 检测到编码 {encoding} 并成功读取文件")
|
||||
else:
|
||||
raise Exception("无法识别文件编码")
|
||||
except Exception as e:
|
||||
logger.warning(f"chardet 检测失败: {e}")
|
||||
# 最后尝试使用 replace 模式读取
|
||||
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
domain_count = sum(1 for line in f if line.strip())
|
||||
logger.info("使用 utf-8 replace 模式读取文件")
|
||||
|
||||
# 对于大文件,不显示所有域名,只显示文件路径和域名数量
|
||||
if domain_count > 1000:
|
||||
self.text_edit.setText(f"文件路径: {file_path}\n域名数量: {domain_count}\n\n提示: 由于文件较大,仅显示文件信息,不显示具体域名。")
|
||||
# 保存文件路径,用于后续导入
|
||||
self.imported_file_path = file_path
|
||||
else:
|
||||
# 对于小文件,显示所有域名
|
||||
domains = []
|
||||
for encoding in encodings:
|
||||
try:
|
||||
with open(file_path, 'r', encoding=encoding) as f:
|
||||
domains = f.readlines()
|
||||
break
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
if not domains:
|
||||
# 尝试使用二进制模式读取
|
||||
try:
|
||||
import chardet
|
||||
with open(file_path, 'rb') as f:
|
||||
raw_data = f.read()
|
||||
result = chardet.detect(raw_data)
|
||||
encoding = result['encoding']
|
||||
if encoding:
|
||||
domains = raw_data.decode(encoding).split('\n')
|
||||
else:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
domains = f.readlines()
|
||||
except Exception:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
domains = f.readlines()
|
||||
domains = [domain.strip() for domain in domains if domain.strip()]
|
||||
self.text_edit.setText('\n'.join(domains))
|
||||
# 清除文件路径,使用文本框中的域名
|
||||
self.imported_file_path = None
|
||||
|
||||
self.status_label.setText(f"成功读取 {domain_count} 个域名")
|
||||
logger.info(f"成功读取文件: {file_path}, 共 {domain_count} 个域名")
|
||||
except Exception as e:
|
||||
self.status_label.setText(f"读取文件失败: {str(e)}")
|
||||
logger.error(f"读取文件失败: {e}")
|
||||
|
||||
def start_import(self):
|
||||
"""
|
||||
开始导入
|
||||
"""
|
||||
# 检查是否有导入的文件路径
|
||||
if hasattr(self, 'imported_file_path') and self.imported_file_path:
|
||||
# 大文件导入,使用文件路径
|
||||
file_path = self.imported_file_path
|
||||
|
||||
# 显示进度条
|
||||
self.progress_bar.setVisible(True)
|
||||
self.progress_bar.setValue(0)
|
||||
self.status_label.setText("正在导入...")
|
||||
|
||||
# 禁用按钮
|
||||
self.import_file_btn.setEnabled(False)
|
||||
self.start_import_btn.setEnabled(False)
|
||||
|
||||
# 创建并启动导入线程
|
||||
self.import_thread = ImportFileThread(file_path, 7) # 7 表示 TXT 导入
|
||||
self.import_thread.progress_updated.connect(self.update_progress)
|
||||
self.import_thread.finished.connect(self.import_finished)
|
||||
self.import_thread.start()
|
||||
|
||||
logger.info(f"开始从文件导入: {file_path}")
|
||||
else:
|
||||
# 小文件或手动输入的域名
|
||||
domains = self.text_edit.toPlainText().split('\n')
|
||||
domains = [domain.strip() for domain in domains if domain.strip()]
|
||||
|
||||
if not domains:
|
||||
self.status_label.setText("请输入域名")
|
||||
return
|
||||
|
||||
# 显示进度条
|
||||
self.progress_bar.setVisible(True)
|
||||
self.progress_bar.setValue(0)
|
||||
self.status_label.setText("正在导入...")
|
||||
|
||||
# 禁用按钮
|
||||
self.import_file_btn.setEnabled(False)
|
||||
self.start_import_btn.setEnabled(False)
|
||||
|
||||
# 创建并启动导入线程
|
||||
self.import_thread = ImportThread(domains, 7) # 7 表示 TXT 导入
|
||||
self.import_thread.progress_updated.connect(self.update_progress)
|
||||
self.import_thread.finished.connect(self.import_finished)
|
||||
self.import_thread.start()
|
||||
|
||||
logger.info(f"开始导入 {len(domains)} 个域名")
|
||||
|
||||
def update_progress(self, progress):
|
||||
"""
|
||||
更新进度
|
||||
|
||||
:param progress: 进度值
|
||||
"""
|
||||
self.progress_bar.setValue(progress)
|
||||
|
||||
def import_finished(self, success, message):
|
||||
"""
|
||||
导入完成
|
||||
|
||||
:param success: 是否成功
|
||||
:param message: 消息
|
||||
"""
|
||||
self.status_label.setText(message)
|
||||
self.progress_bar.setVisible(False)
|
||||
|
||||
# 启用按钮
|
||||
self.import_file_btn.setEnabled(True)
|
||||
self.start_import_btn.setEnabled(True)
|
||||
|
||||
logger.info(f"导入完成: {message}")
|
||||
577
app/ui/juming_crawler.py
Normal file
577
app/ui/juming_crawler.py
Normal file
@@ -0,0 +1,577 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :juming_crawler.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/4/9 15:00
|
||||
@explain : 聚名网爬取页面
|
||||
'''
|
||||
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTextEdit, QProgressBar, QLineEdit, QComboBox, QDateEdit, QCheckBox
|
||||
from PySide6.QtCore import Qt, QThread, Signal, QDate
|
||||
from PySide6.QtGui import QIntValidator
|
||||
from loguru import logger
|
||||
import re
|
||||
import datetime
|
||||
import time
|
||||
|
||||
from app.core.domain_collector import DomainCollector
|
||||
from detect.juming import JM
|
||||
|
||||
|
||||
class JumingCrawlThread(QThread):
|
||||
"""
|
||||
聚名爬取线程
|
||||
"""
|
||||
progress_updated = Signal(int)
|
||||
status_updated = Signal(str)
|
||||
finished = Signal(bool, str)
|
||||
|
||||
def __init__(self, crawl_type, page_start=1, page_size=50, crawl_date=None, auto_date=True):
|
||||
"""
|
||||
初始化聚名爬取线程
|
||||
|
||||
:param crawl_type: 爬取类型 (1: 一口价, 2: 删除列表)
|
||||
:param page_start: 起始页码
|
||||
:param page_size: 每页数量
|
||||
:param crawl_date: 爬取日期(删除列表用)
|
||||
:param auto_date: 是否自动新增日期
|
||||
"""
|
||||
super().__init__()
|
||||
self.crawl_type = crawl_type
|
||||
self.page_start = page_start
|
||||
self.page_size = page_size
|
||||
self.crawl_date = crawl_date
|
||||
self.auto_date = auto_date
|
||||
self.is_paused = False
|
||||
self.is_stopped = False
|
||||
self.current_page = 0
|
||||
self.total_count = 0
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
运行聚名爬取线程
|
||||
"""
|
||||
try:
|
||||
# 初始化聚名客户端
|
||||
jm = JM()
|
||||
|
||||
# 加载 Cookie
|
||||
jm.load_cookies()
|
||||
logger.info("已加载 Cookie")
|
||||
self.status_updated.emit("已加载 Cookie")
|
||||
|
||||
# 直接开始爬取,不需要登录,因为 Cookie 已经在系统设置页面加载了
|
||||
self.progress_updated.emit(10)
|
||||
|
||||
if self.crawl_type == 1: # 一口价
|
||||
self.progress_updated.emit(30)
|
||||
logger.info("开始获取一口价域名")
|
||||
self.status_updated.emit("开始获取一口价域名")
|
||||
|
||||
# 自动爬取多页
|
||||
page = self.page_start
|
||||
while not self.is_stopped:
|
||||
if self.is_paused:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
self.current_page = page
|
||||
self.status_updated.emit(f"正在爬取第 {page} 页")
|
||||
logger.info(f"正在爬取第 {page} 页")
|
||||
|
||||
# 获取当前页
|
||||
success, html = jm.ykj_get_list(page=page, page_size=self.page_size)
|
||||
if success:
|
||||
pattern_ym = r"<a class='yda1 ydz' ym='([^']*)'"
|
||||
results = re.findall(pattern_ym, html)
|
||||
domains = [domain.strip() for domain in results if domain.strip()]
|
||||
domain_count = len(domains)
|
||||
self.total_count += domain_count
|
||||
|
||||
self.status_updated.emit(f"第 {page} 页找到 {domain_count} 个域名,累计 {self.total_count} 个")
|
||||
logger.info(f"第 {page} 页找到 {domain_count} 个域名,累计 {self.total_count} 个")
|
||||
|
||||
# 自动入库
|
||||
if domains:
|
||||
collector = DomainCollector()
|
||||
stats = collector.add_domains_batch(domains, 1) # 1 表示一口价
|
||||
logger.info(f"自动入库完成: {stats}")
|
||||
self.status_updated.emit(f"自动入库完成: 成功添加 {stats['added']} 个域名")
|
||||
|
||||
# 如果返回的数量小于指定的数量,停止爬取
|
||||
if domain_count < self.page_size:
|
||||
logger.info(f"返回数量小于指定数量,停止爬取")
|
||||
self.status_updated.emit("返回数量小于指定数量,停止爬取")
|
||||
break
|
||||
|
||||
# 增加页码
|
||||
page += 1
|
||||
|
||||
# 模拟网络延迟
|
||||
time.sleep(1)
|
||||
else:
|
||||
logger.error(f"获取一口价域名失败: {html}")
|
||||
self.status_updated.emit(f"获取一口价域名失败: {html}")
|
||||
break
|
||||
|
||||
elif self.crawl_type == 2: # 删除列表
|
||||
self.progress_updated.emit(30)
|
||||
logger.info("开始获取删除域名列表")
|
||||
self.status_updated.emit("开始获取删除域名列表")
|
||||
|
||||
# 使用传入的日期或默认今天
|
||||
start_date_str = self.crawl_date if self.crawl_date else datetime.date.today().strftime("%Y-%m-%d")
|
||||
start_date = datetime.datetime.strptime(start_date_str, "%Y-%m-%d").date()
|
||||
# 计算结束日期:今天 + 4天
|
||||
end_date = datetime.date.today() + datetime.timedelta(days=4)
|
||||
|
||||
if self.auto_date:
|
||||
# 自动新增日期,从起始日期到今天+4天
|
||||
current_date = start_date
|
||||
while current_date <= end_date and not self.is_stopped:
|
||||
if self.is_paused:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
crawl_date = current_date.strftime("%Y-%m-%d")
|
||||
self.status_updated.emit(f"正在爬取 {crawl_date} 的删除域名")
|
||||
logger.info(f"正在爬取 {crawl_date} 的删除域名")
|
||||
|
||||
deleted_domains = jm.new_cha_del(crawl_date)
|
||||
domains = [domain.strip() for domain in deleted_domains if domain.strip()]
|
||||
domain_count = len(domains)
|
||||
self.total_count += domain_count
|
||||
|
||||
self.status_updated.emit(f"{crawl_date} 找到 {domain_count} 个删除域名,累计 {self.total_count} 个")
|
||||
logger.info(f"{crawl_date} 找到 {domain_count} 个删除域名,累计 {self.total_count} 个")
|
||||
|
||||
# 自动入库
|
||||
if domains:
|
||||
collector = DomainCollector()
|
||||
stats = collector.add_domains_batch(domains, 2) # 2 表示删除列表
|
||||
logger.info(f"{crawl_date} 自动入库完成: {stats}")
|
||||
self.status_updated.emit(f"{crawl_date} 自动入库完成: 成功添加 {stats['added']} 个域名")
|
||||
|
||||
# 增加日期
|
||||
current_date = current_date + datetime.timedelta(days=1)
|
||||
|
||||
# 模拟网络延迟
|
||||
time.sleep(1)
|
||||
else:
|
||||
# 只爬取指定日期
|
||||
crawl_date = start_date_str
|
||||
self.status_updated.emit(f"正在爬取 {crawl_date} 的删除域名")
|
||||
logger.info(f"正在爬取 {crawl_date} 的删除域名")
|
||||
|
||||
deleted_domains = jm.new_cha_del(crawl_date)
|
||||
domains = [domain.strip() for domain in deleted_domains if domain.strip()]
|
||||
domain_count = len(domains)
|
||||
self.total_count = domain_count
|
||||
|
||||
self.status_updated.emit(f"找到 {domain_count} 个删除域名")
|
||||
logger.info(f"找到 {domain_count} 个删除域名")
|
||||
|
||||
# 自动入库
|
||||
if domains:
|
||||
collector = DomainCollector()
|
||||
stats = collector.add_domains_batch(domains, 2) # 2 表示删除列表
|
||||
logger.info(f"自动入库完成: {stats}")
|
||||
self.status_updated.emit(f"自动入库完成: 成功添加 {stats['added']} 个域名")
|
||||
|
||||
self.progress_updated.emit(90)
|
||||
|
||||
self.progress_updated.emit(100)
|
||||
self.finished.emit(True, f"成功获取 {self.total_count} 个域名")
|
||||
except Exception as e:
|
||||
logger.error(f"从聚名网爬取失败: {e}")
|
||||
self.status_updated.emit(f"爬取失败: {str(e)}")
|
||||
self.finished.emit(False, f"爬取失败: {str(e)}")
|
||||
|
||||
def pause(self):
|
||||
"""
|
||||
暂停爬取
|
||||
"""
|
||||
self.is_paused = True
|
||||
logger.info("爬取已暂停")
|
||||
self.status_updated.emit("爬取已暂停")
|
||||
|
||||
def resume(self):
|
||||
"""
|
||||
恢复爬取
|
||||
"""
|
||||
self.is_paused = False
|
||||
logger.info("爬取已恢复")
|
||||
self.status_updated.emit("爬取已恢复")
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
停止爬取
|
||||
"""
|
||||
self.is_stopped = True
|
||||
logger.info("爬取已停止")
|
||||
self.status_updated.emit("爬取已停止")
|
||||
|
||||
|
||||
class JumingCrawlerWidget(QWidget):
|
||||
"""
|
||||
聚名网爬取页面
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化聚名网爬取页面
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
# 创建布局
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
|
||||
|
||||
# 爬取类型选择
|
||||
type_layout = QHBoxLayout()
|
||||
type_label = QLabel("爬取类型:")
|
||||
type_label.setStyleSheet("font-size: 14px; color: #666; min-width: 80px;")
|
||||
self.type_combo = QComboBox()
|
||||
self.type_combo.addItem("一口价域名", 1)
|
||||
self.type_combo.addItem("删除列表域名", 2)
|
||||
self.type_combo.setStyleSheet("font-size: 14px; padding: 5px; border: 1px solid #ddd; border-radius: 4px;")
|
||||
# 监听类型变化
|
||||
self.type_combo.currentIndexChanged.connect(self.on_type_changed)
|
||||
type_layout.addWidget(type_label)
|
||||
type_layout.addWidget(self.type_combo)
|
||||
type_layout.setContentsMargins(0, 0, 0, 15)
|
||||
layout.addLayout(type_layout)
|
||||
|
||||
# 页码和每页数量设置
|
||||
page_layout = QHBoxLayout()
|
||||
|
||||
# 起始页码
|
||||
page_start_label = QLabel("起始页码:")
|
||||
page_start_label.setStyleSheet("font-size: 14px; color: #666; min-width: 80px;")
|
||||
self.page_start_edit = QLineEdit("1")
|
||||
# 移除所有限制,允许输入任意正整数
|
||||
self.page_start_edit.setStyleSheet("font-size: 14px; padding: 5px; border: 1px solid #ddd; border-radius: 4px; width: 120px;")
|
||||
page_layout.addWidget(page_start_label)
|
||||
page_layout.addWidget(self.page_start_edit)
|
||||
|
||||
# 每页数量
|
||||
page_size_label = QLabel("每页数量:")
|
||||
page_size_label.setStyleSheet("font-size: 14px; color: #666; min-width: 80px; margin-left: 20px;")
|
||||
self.page_size_edit = QLineEdit("500")
|
||||
self.page_size_edit.setValidator(QIntValidator(1, 1000))
|
||||
self.page_size_edit.setStyleSheet("font-size: 14px; padding: 5px; border: 1px solid #ddd; border-radius: 4px; width: 80px;")
|
||||
page_layout.addWidget(page_size_label)
|
||||
page_layout.addWidget(self.page_size_edit)
|
||||
page_layout.setContentsMargins(0, 0, 0, 20)
|
||||
layout.addLayout(page_layout)
|
||||
|
||||
# 日期设置(删除列表用)
|
||||
date_container = QWidget()
|
||||
date_layout = QHBoxLayout(date_container)
|
||||
|
||||
# 起始日期
|
||||
date_label = QLabel("起始日期:")
|
||||
date_label.setStyleSheet("font-size: 14px; color: #666; min-width: 80px;")
|
||||
self.date_edit = QDateEdit()
|
||||
self.date_edit.setDate(QDate.currentDate())
|
||||
self.date_edit.setCalendarPopup(True)
|
||||
self.date_edit.setStyleSheet("font-size: 14px; padding: 5px; border: 1px solid #ddd; border-radius: 4px; width: 150px;")
|
||||
# 设置最大日期为今天+4天,最小日期为今天的前4天
|
||||
max_date = QDate.currentDate().addDays(4)
|
||||
min_date = QDate.currentDate().addDays(-4)
|
||||
self.date_edit.setMinimumDate(min_date)
|
||||
self.date_edit.setMaximumDate(max_date)
|
||||
date_layout.addWidget(date_label)
|
||||
date_layout.addWidget(self.date_edit)
|
||||
|
||||
# 自动新增日期选项
|
||||
auto_date_checkbox = QCheckBox("自动新增日期")
|
||||
auto_date_checkbox.setChecked(True)
|
||||
auto_date_checkbox.setStyleSheet("font-size: 14px; margin-left: 20px;")
|
||||
self.auto_date_checkbox = auto_date_checkbox
|
||||
date_layout.addWidget(auto_date_checkbox)
|
||||
|
||||
date_layout.setContentsMargins(0, 0, 0, 20)
|
||||
self.date_container = date_container
|
||||
layout.addWidget(date_container)
|
||||
# 默认隐藏日期输入框
|
||||
self.date_container.setVisible(False)
|
||||
|
||||
# 按钮布局
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
# 创建开始按钮
|
||||
self.start_btn = QPushButton("开始爬取")
|
||||
self.start_btn.clicked.connect(self.start_crawl)
|
||||
self.start_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
""")
|
||||
button_layout.addWidget(self.start_btn)
|
||||
|
||||
# 创建暂停按钮
|
||||
self.pause_btn = QPushButton("暂停爬取")
|
||||
self.pause_btn.clicked.connect(self.pause_crawl)
|
||||
self.pause_btn.setEnabled(False)
|
||||
self.pause_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #ff9800;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #f57c00;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
""")
|
||||
button_layout.addWidget(self.pause_btn)
|
||||
|
||||
# 创建停止按钮
|
||||
self.stop_btn = QPushButton("停止爬取")
|
||||
self.stop_btn.clicked.connect(self.stop_crawl)
|
||||
self.stop_btn.setEnabled(False)
|
||||
self.stop_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
QPushButton:disabled {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
""")
|
||||
button_layout.addWidget(self.stop_btn)
|
||||
button_layout.setContentsMargins(0, 0, 0, 20)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 创建日志显示区域
|
||||
self.log_edit = QTextEdit()
|
||||
self.log_edit.setPlaceholderText("爬取日志将显示在这里")
|
||||
self.log_edit.setReadOnly(True)
|
||||
self.log_edit.setStyleSheet("""
|
||||
QTextEdit {
|
||||
font-size: 13px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.log_edit)
|
||||
|
||||
# 创建进度条
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setVisible(False)
|
||||
self.progress_bar.setStyleSheet("""
|
||||
QProgressBar {
|
||||
height: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
background-color: #f0f0f0;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #4CAF50;
|
||||
border-radius: 10px;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.progress_bar)
|
||||
|
||||
# 创建状态标签
|
||||
self.status_label = QLabel("")
|
||||
self.status_label.setAlignment(Qt.AlignCenter)
|
||||
self.status_label.setStyleSheet("font-size: 14px; color: #333; margin-top: 10px; padding: 8px; background-color: #f0f8ff; border-radius: 4px;")
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
# 存储爬取线程
|
||||
self.crawl_thread = None
|
||||
|
||||
logger.info("聚名网爬取页面创建完成")
|
||||
|
||||
def start_crawl(self):
|
||||
"""
|
||||
开始爬取
|
||||
"""
|
||||
crawl_type = self.type_combo.currentData()
|
||||
|
||||
# 获取起始页码和每页数量
|
||||
try:
|
||||
page_start = int(self.page_start_edit.text())
|
||||
page_size = int(self.page_size_edit.text())
|
||||
if page_start < 1:
|
||||
self.status_label.setText("起始页码必须大于0")
|
||||
return
|
||||
if page_size < 1:
|
||||
self.status_label.setText("每页数量必须大于0")
|
||||
return
|
||||
except ValueError:
|
||||
self.status_label.setText("请输入有效的页码和每页数量")
|
||||
return
|
||||
|
||||
# 显示进度条
|
||||
self.progress_bar.setVisible(True)
|
||||
self.progress_bar.setValue(0)
|
||||
self.status_label.setText("正在爬取域名...")
|
||||
|
||||
# 清空日志
|
||||
self.log_edit.clear()
|
||||
|
||||
# 启用/禁用按钮
|
||||
self.start_btn.setEnabled(False)
|
||||
self.pause_btn.setEnabled(True)
|
||||
self.stop_btn.setEnabled(True)
|
||||
|
||||
# 获取爬取日期和自动新增日期选项(删除列表用)
|
||||
crawl_date = None
|
||||
auto_date = False
|
||||
if crawl_type == 2: # 删除列表
|
||||
crawl_date = self.date_edit.date().toString("yyyy-MM-dd")
|
||||
auto_date = self.auto_date_checkbox.isChecked()
|
||||
|
||||
# 创建并启动爬取线程
|
||||
self.crawl_thread = JumingCrawlThread(crawl_type, page_start, page_size, crawl_date, auto_date)
|
||||
self.crawl_thread.progress_updated.connect(self.update_progress)
|
||||
self.crawl_thread.status_updated.connect(self.update_status)
|
||||
self.crawl_thread.finished.connect(self.crawl_finished)
|
||||
self.crawl_thread.start()
|
||||
|
||||
if crawl_type == 2: # 删除列表
|
||||
auto_date_str = "是" if auto_date else "否"
|
||||
logger.info(f"开始爬取聚名网域名, 类型: {crawl_type}, 爬取日期: {crawl_date}, 自动新增日期: {auto_date_str}")
|
||||
self.log_edit.append(f"开始爬取聚名网域名, 类型: {crawl_type}, 爬取日期: {crawl_date}, 自动新增日期: {auto_date_str}")
|
||||
else: # 一口价
|
||||
logger.info(f"开始爬取聚名网域名, 类型: {crawl_type}, 起始页码: {page_start}, 每页数量: {page_size}")
|
||||
self.log_edit.append(f"开始爬取聚名网域名, 类型: {crawl_type}, 起始页码: {page_start}, 每页数量: {page_size}")
|
||||
|
||||
def pause_crawl(self):
|
||||
"""
|
||||
暂停爬取
|
||||
"""
|
||||
if self.crawl_thread:
|
||||
if self.crawl_thread.is_paused:
|
||||
self.crawl_thread.resume()
|
||||
self.pause_btn.setText("暂停爬取")
|
||||
else:
|
||||
self.crawl_thread.pause()
|
||||
self.pause_btn.setText("恢复爬取")
|
||||
|
||||
def stop_crawl(self):
|
||||
"""
|
||||
停止爬取
|
||||
"""
|
||||
if self.crawl_thread:
|
||||
self.crawl_thread.stop()
|
||||
|
||||
def update_progress(self, progress):
|
||||
"""
|
||||
更新进度
|
||||
|
||||
:param progress: 进度值
|
||||
"""
|
||||
self.progress_bar.setValue(progress)
|
||||
|
||||
def update_status(self, status):
|
||||
"""
|
||||
更新状态
|
||||
|
||||
:param status: 状态消息
|
||||
"""
|
||||
self.status_label.setText(status)
|
||||
self.log_edit.append(status)
|
||||
|
||||
def crawl_finished(self, success, message):
|
||||
"""
|
||||
爬取完成
|
||||
|
||||
:param success: 是否成功
|
||||
:param message: 消息
|
||||
"""
|
||||
self.status_label.setText(message)
|
||||
self.log_edit.append(message)
|
||||
|
||||
# 启用/禁用按钮
|
||||
self.start_btn.setEnabled(True)
|
||||
self.pause_btn.setEnabled(False)
|
||||
self.pause_btn.setText("暂停爬取")
|
||||
self.stop_btn.setEnabled(False)
|
||||
|
||||
self.progress_bar.setVisible(False)
|
||||
|
||||
logger.info(f"聚名网爬取完成: {message}")
|
||||
|
||||
def on_type_changed(self, index):
|
||||
"""
|
||||
爬取类型变化时的处理
|
||||
|
||||
:param index: 选择的索引
|
||||
"""
|
||||
crawl_type = self.type_combo.currentData()
|
||||
if crawl_type == 2: # 删除列表
|
||||
self.date_container.setVisible(True)
|
||||
else: # 一口价
|
||||
self.date_container.setVisible(False)
|
||||
|
||||
|
||||
class ImportThread(QThread):
|
||||
"""
|
||||
导入线程
|
||||
"""
|
||||
progress_updated = Signal(int)
|
||||
finished = Signal(bool, str)
|
||||
|
||||
def __init__(self, domain_list, source_type):
|
||||
"""
|
||||
初始化导入线程
|
||||
|
||||
:param domain_list: 域名列表
|
||||
:param source_type: 来源类型
|
||||
"""
|
||||
super().__init__()
|
||||
self.domain_list = domain_list
|
||||
self.source_type = source_type
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
运行导入线程
|
||||
"""
|
||||
try:
|
||||
collector = DomainCollector()
|
||||
total = len(self.domain_list)
|
||||
for i, domain in enumerate(self.domain_list):
|
||||
collector.add_domain(domain, self.source_type)
|
||||
progress = int((i + 1) / total * 100)
|
||||
self.progress_updated.emit(progress)
|
||||
self.finished.emit(True, f"成功导入 {total} 个域名")
|
||||
except Exception as e:
|
||||
logger.error(f"导入失败: {e}")
|
||||
self.finished.emit(False, f"导入失败: {str(e)}")
|
||||
117
app/ui/main_window.py
Normal file
117
app/ui/main_window.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :main_window.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/4/8 23:46
|
||||
@explain : 主窗口
|
||||
'''
|
||||
|
||||
from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTabWidget, QLabel
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QIcon
|
||||
import os
|
||||
from loguru import logger
|
||||
|
||||
from app.ui.domain_import import DomainImportWidget
|
||||
from app.ui.domain_filter import DomainFilterWidget
|
||||
from app.ui.sensitive_words import SensitiveWordsWidget
|
||||
from app.ui.juming_crawler import JumingCrawlerWidget
|
||||
from app.ui.system_settings import SystemSettingsWidget
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
"""
|
||||
主窗口
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化主窗口
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
# 设置窗口标题和大小
|
||||
self.setWindowTitle("域名工具")
|
||||
self.setGeometry(100, 100, 2000, 800)
|
||||
|
||||
# 设置窗口图标
|
||||
icon_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "favicon.ico")
|
||||
if os.path.exists(icon_path):
|
||||
self.setWindowIcon(QIcon(icon_path))
|
||||
logger.info(f"设置窗口图标成功: {icon_path}")
|
||||
else:
|
||||
logger.warning(f"窗口图标文件不存在: {icon_path}")
|
||||
|
||||
# 创建中央部件
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
|
||||
# 创建主布局
|
||||
main_layout = QVBoxLayout(central_widget)
|
||||
|
||||
# 创建标签页
|
||||
self.tab_widget = QTabWidget()
|
||||
self.tab_widget.setStyleSheet("""
|
||||
QTabWidget {
|
||||
font-size: 14px;
|
||||
}
|
||||
QTabBar::tab {
|
||||
padding: 10px 20px;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #ddd;
|
||||
border-bottom: none;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
QTabBar::tab:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
QTabBar::tab:selected {
|
||||
background-color: white;
|
||||
color: #4CAF50;
|
||||
font-weight: bold;
|
||||
border-color: #4CAF50;
|
||||
}
|
||||
QTabWidget::pane {
|
||||
border: 1px solid #ddd;
|
||||
border-top: none;
|
||||
border-radius: 0 0 4px 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
""")
|
||||
main_layout.addWidget(self.tab_widget)
|
||||
|
||||
# 创建标签页内容
|
||||
self.create_tabs()
|
||||
|
||||
# 记录日志
|
||||
logger.info("主窗口创建完成")
|
||||
|
||||
def create_tabs(self):
|
||||
"""
|
||||
创建标签页
|
||||
"""
|
||||
# 聚名爬取标签页
|
||||
juming_widget = JumingCrawlerWidget()
|
||||
self.tab_widget.addTab(juming_widget, "聚名爬取")
|
||||
|
||||
# 域名筛选标签页
|
||||
filter_widget = DomainFilterWidget()
|
||||
self.tab_widget.addTab(filter_widget, "域名筛选")
|
||||
|
||||
# 域名导入标签页
|
||||
import_widget = DomainImportWidget()
|
||||
self.tab_widget.addTab(import_widget, "域名导入")
|
||||
|
||||
# 敏感词配置标签页
|
||||
sensitive_widget = SensitiveWordsWidget()
|
||||
self.tab_widget.addTab(sensitive_widget, "敏感词配置")
|
||||
|
||||
# 系统设置标签页
|
||||
settings_widget = SystemSettingsWidget()
|
||||
self.tab_widget.addTab(settings_widget, "系统设置")
|
||||
|
||||
logger.info("标签页创建完成")
|
||||
284
app/ui/sensitive_words.py
Normal file
284
app/ui/sensitive_words.py
Normal file
@@ -0,0 +1,284 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
'''
|
||||
@Project :domainScanDemo
|
||||
@File :sensitive_words.py
|
||||
@IDE :PyCharm
|
||||
@Author :梦伴
|
||||
@Date :2026/4/8 23:49
|
||||
@explain : 敏感词配置界面
|
||||
'''
|
||||
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTextEdit, QFileDialog
|
||||
from PySide6.QtCore import Qt, QThread, Signal
|
||||
from loguru import logger
|
||||
from app.utils.database import Database
|
||||
|
||||
|
||||
class SaveWordsThread(QThread):
|
||||
"""
|
||||
保存敏感词线程
|
||||
"""
|
||||
finished = Signal(bool, str, int)
|
||||
|
||||
def __init__(self, words):
|
||||
super().__init__()
|
||||
self.words = words
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
db = Database()
|
||||
|
||||
# 先清空现有敏感词
|
||||
db.execute("DELETE FROM sensitive_words")
|
||||
|
||||
# 批量添加敏感词
|
||||
word_tuples = [(word, 'default', 1) for word in self.words]
|
||||
if word_tuples:
|
||||
db.batch_add_sensitive_words(word_tuples)
|
||||
|
||||
db.close()
|
||||
self.finished.emit(True, "成功保存敏感词", len(self.words))
|
||||
except Exception as e:
|
||||
self.finished.emit(False, str(e), 0)
|
||||
|
||||
|
||||
class LoadWordsThread(QThread):
|
||||
"""
|
||||
加载敏感词线程
|
||||
"""
|
||||
finished = Signal(bool, list, str)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
db = Database()
|
||||
sensitive_words = db.get_sensitive_words()
|
||||
words = [word['word'] for word in sensitive_words]
|
||||
db.close()
|
||||
self.finished.emit(True, words, f"成功加载 {len(words)} 个敏感词")
|
||||
except Exception as e:
|
||||
self.finished.emit(False, [], str(e))
|
||||
|
||||
|
||||
class SensitiveWordsWidget(QWidget):
|
||||
"""
|
||||
敏感词配置界面
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化敏感词配置界面
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
# 创建布局
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
|
||||
|
||||
# 创建文本编辑框
|
||||
self.text_edit = QTextEdit()
|
||||
self.text_edit.setPlaceholderText("请输入敏感词,一行一个")
|
||||
self.text_edit.setStyleSheet("""
|
||||
QTextEdit {
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 300px;
|
||||
}
|
||||
""")
|
||||
layout.addWidget(self.text_edit)
|
||||
|
||||
# 创建按钮布局
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
# 导入按钮
|
||||
self.import_btn = QPushButton("导入")
|
||||
self.import_btn.clicked.connect(self.import_words)
|
||||
self.import_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #0b7dda;
|
||||
}
|
||||
""")
|
||||
button_layout.addWidget(self.import_btn)
|
||||
|
||||
# 导出按钮
|
||||
self.export_btn = QPushButton("导出")
|
||||
self.export_btn.clicked.connect(self.export_words)
|
||||
self.export_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #ff9800;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #f57c00;
|
||||
}
|
||||
""")
|
||||
button_layout.addWidget(self.export_btn)
|
||||
|
||||
# 保存按钮
|
||||
self.save_btn = QPushButton("保存")
|
||||
self.save_btn.clicked.connect(self.save_words)
|
||||
self.save_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
""")
|
||||
button_layout.addWidget(self.save_btn)
|
||||
|
||||
# 加载按钮
|
||||
self.load_btn = QPushButton("加载")
|
||||
self.load_btn.clicked.connect(self.load_words)
|
||||
self.load_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #9c27b0;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #7b1fa2;
|
||||
}
|
||||
""")
|
||||
button_layout.addWidget(self.load_btn)
|
||||
button_layout.setContentsMargins(0, 15, 0, 15)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 创建状态标签
|
||||
self.status_label = QLabel("")
|
||||
self.status_label.setAlignment(Qt.AlignCenter)
|
||||
self.status_label.setStyleSheet("font-size: 14px; color: #333; padding: 10px; background-color: #f0f8ff; border-radius: 4px;")
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
# 初始化线程
|
||||
self.save_thread = None
|
||||
self.load_thread = None
|
||||
|
||||
# 加载敏感词
|
||||
self.load_words()
|
||||
|
||||
logger.info("敏感词配置界面创建完成")
|
||||
|
||||
def import_words(self):
|
||||
"""
|
||||
导入敏感词
|
||||
"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "文本文件 (*.txt)")
|
||||
if file_path:
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
words = f.readlines()
|
||||
words = [word.strip() for word in words if word.strip()]
|
||||
self.text_edit.setText('\n'.join(words))
|
||||
self.status_label.setText(f"成功导入 {len(words)} 个敏感词")
|
||||
logger.info(f"成功导入敏感词文件: {file_path}, 共 {len(words)} 个敏感词")
|
||||
except Exception as e:
|
||||
self.status_label.setText(f"导入失败: {str(e)}")
|
||||
logger.error(f"导入敏感词失败: {e}")
|
||||
|
||||
def export_words(self):
|
||||
"""
|
||||
导出敏感词
|
||||
"""
|
||||
words = self.text_edit.toPlainText().split('\n')
|
||||
words = [word.strip() for word in words if word.strip()]
|
||||
|
||||
if not words:
|
||||
self.status_label.setText("没有敏感词可导出")
|
||||
return
|
||||
|
||||
file_path, _ = QFileDialog.getSaveFileName(self, "保存文件", "", "文本文件 (*.txt)")
|
||||
if file_path:
|
||||
try:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
for word in words:
|
||||
f.write(word + '\n')
|
||||
self.status_label.setText(f"成功导出 {len(words)} 个敏感词")
|
||||
logger.info(f"成功导出 {len(words)} 个敏感词到 {file_path}")
|
||||
except Exception as e:
|
||||
self.status_label.setText(f"导出失败: {str(e)}")
|
||||
logger.error(f"导出敏感词失败: {e}")
|
||||
|
||||
def save_words(self):
|
||||
"""
|
||||
保存敏感词
|
||||
"""
|
||||
words = self.text_edit.toPlainText().split('\n')
|
||||
words = [word.strip() for word in words if word.strip()]
|
||||
|
||||
# 禁用按钮,防止重复点击
|
||||
self.save_btn.setEnabled(False)
|
||||
self.status_label.setText("正在保存敏感词...")
|
||||
|
||||
# 创建并启动保存线程
|
||||
self.save_thread = SaveWordsThread(words)
|
||||
self.save_thread.finished.connect(self.on_save_finished)
|
||||
self.save_thread.start()
|
||||
|
||||
def on_save_finished(self, success, message, count):
|
||||
"""
|
||||
保存完成的回调函数
|
||||
"""
|
||||
if success:
|
||||
self.status_label.setText(f"成功保存 {count} 个敏感词")
|
||||
logger.info(f"成功保存 {count} 个敏感词到数据库")
|
||||
else:
|
||||
self.status_label.setText(f"保存失败: {message}")
|
||||
logger.error(f"保存敏感词失败: {message}")
|
||||
|
||||
# 重新启用按钮
|
||||
self.save_btn.setEnabled(True)
|
||||
|
||||
def load_words(self):
|
||||
"""
|
||||
加载敏感词
|
||||
"""
|
||||
# 禁用按钮,防止重复点击
|
||||
self.load_btn.setEnabled(False)
|
||||
self.status_label.setText("正在加载敏感词...")
|
||||
|
||||
# 创建并启动加载线程
|
||||
self.load_thread = LoadWordsThread()
|
||||
self.load_thread.finished.connect(self.on_load_finished)
|
||||
self.load_thread.start()
|
||||
|
||||
def on_load_finished(self, success, words, message):
|
||||
"""
|
||||
加载完成的回调函数
|
||||
"""
|
||||
if success:
|
||||
self.text_edit.setText('\n'.join(words))
|
||||
self.status_label.setText(message)
|
||||
logger.info(message)
|
||||
else:
|
||||
self.status_label.setText(f"加载失败: {message}")
|
||||
logger.error(f"加载敏感词失败: {message}")
|
||||
|
||||
# 重新启用按钮
|
||||
self.load_btn.setEnabled(True)
|
||||
1072
app/ui/system_settings.py
Normal file
1072
app/ui/system_settings.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user