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

577 lines
22 KiB
Python
Raw Blame History

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