first commit

This commit is contained in:
BF
2026-04-14 22:53:52 +08:00
commit b37cba8735
156 changed files with 773977 additions and 0 deletions

4
app/core/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
# -*- coding: UTF-8 -*-
'''
核心功能模块
'''

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

281
app/core/detect_engine.py Normal file
View File

@@ -0,0 +1,281 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :detect_engine.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/4/8 23:52
@explain : 检测引擎
'''
import time
from loguru import logger
from app.utils.database import Database
from app.detectors.rdap_detector import RDAPDetector
from app.detectors.wayback_detector import WaybackDetector
from app.detectors.baidu_detector import BaiduDetector
from app.detectors.qihu360_detector import Qihu360Detector
from app.detectors.google_detector import GoogleDetector
from app.detectors.chinaz_detector import ChinazDetector
from app.detectors.aizhan_detector import AizhanDetector
from app.detectors.juziseo_detector import JuziseoDetector
from app.detectors.jucha_detector import JuchaDetector
class DetectEngine:
"""
检测引擎
"""
def __init__(self):
"""
初始化检测引擎
"""
self.db = Database()
self.rdap_detector = RDAPDetector()
self.wayback_detector = WaybackDetector()
self.baidu_detector = BaiduDetector()
self.qihu360_detector = Qihu360Detector()
self.google_detector = GoogleDetector()
self.chinaz_detector = ChinazDetector()
self.aizhan_detector = AizhanDetector()
self.juziseo_detector = JuziseoDetector()
self.jucha_detector = JuchaDetector()
def detect_domain(self, domain_id):
"""
检测域名
:param domain_id: 域名ID
:return: bool - 是否检测成功
"""
try:
# 获取域名信息
domain_info = self.db.get_domain_by_id(domain_id)
if not domain_info:
logger.error(f"域名不存在: {domain_id}")
return False
domain = domain_info['domain']
logger.info(f"开始检测域名: {domain}")
# 更新检测状态为检测中
self.db.update_domain_detect_status(domain_id, 2) # 2 表示检测中
# 1. 基础检测
if not self._basic_detect(domain_id, domain):
logger.info(f"基础检测失败,停止后续检测: {domain}")
return False
# 2. 深度检测
if not self._deep_detect(domain_id, domain):
logger.info(f"深度检测失败: {domain}")
return False
# 更新检测状态为正常
self.db.update_domain_detect_status(domain_id, 3) # 3 表示正常
logger.info(f"域名检测完成: {domain}")
return True
except Exception as e:
logger.error(f"检测域名出错: {e}")
# 更新检测状态为检测失败
self.db.update_domain_detect_status(domain_id, 5) # 5 表示检测失败
return False
def _basic_detect(self, domain_id, domain):
"""
基础检测
:param domain_id: 域名ID
:param domain: 域名
:return: bool - 是否检测通过
"""
# 1. 检查是否为一口价域名
is_ykj = self.db.is_ykj_domain(domain_id)
# 2. 注册状态检测(一口价域名跳过)
if not is_ykj:
register_status = self.rdap_detector.check_register_status(domain)
self.db.update_domain_register_status(domain_id, register_status)
# 3. 黑名单缓存检查
if self.db.is_blacklisted(domain):
logger.info(f"域名在黑名单中: {domain}")
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
return False
# 4. 时光机快照年份采集
snapshot_years = self.wayback_detector.get_snapshot_years(domain)
if snapshot_years:
self.db.update_domain_snapshot_years(domain_id, ','.join(map(str, snapshot_years)))
# 5. 时光机正文抽样与敏感词匹配
if self.wayback_detector.has_sensitive_content(domain):
logger.info(f"域名包含敏感词: {domain}")
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain, "快照包含敏感词")
return False
return True
def _deep_detect(self, domain_id, domain):
"""
深度检测
:param domain_id: 域名ID
:param domain: 域名
:return: bool - 是否检测通过
"""
# 1. 百度历史/Site
baidu_history = self.baidu_detector.check_history(domain)
baidu_site = self.baidu_detector.check_site(domain)
# 2. 360 Site
qihu360_site = self.qihu360_detector.check_site(domain)
# 3. Google Site
google_site = self.google_detector.check_site(domain)
# 4. 站长之家
chinaz_info = self.chinaz_detector.check_domain(domain)
# 5. 爱站网
aizhan_info = self.aizhan_detector.check_domain(domain)
# 6. 桔子SEO
juziseo_info = self.juziseo_detector.check_domain(domain)
# 7. 聚查
jucha_info = self.jucha_detector.check_domain(domain)
# 检查是否有风险
if self._check_risk(domain_id, domain, baidu_history, baidu_site, qihu360_site, google_site, chinaz_info, aizhan_info, juziseo_info, jucha_info):
return False
# 保存检测结果
self.db.add_detection_result(domain_id, baidu_history, baidu_site, qihu360_site, google_site, chinaz_info, aizhan_info, juziseo_info, jucha_info)
return True
def _check_risk(self, domain_id, domain, baidu_history, baidu_site, qihu360_site, google_site, chinaz_info, aizhan_info, juziseo_info, jucha_info):
"""
检查风险
:param domain_id: 域名ID
:param domain: 域名
:param baidu_history: 百度历史
:param baidu_site: 百度site
:param qihu360_site: 360 site
:param google_site: Google site
:param chinaz_info: 站长之家信息
:param aizhan_info: 爱站网信息
:param juziseo_info: 桔子SEO信息
:param jucha_info: 聚查信息
:return: bool - 是否有风险
"""
# 检查百度历史过灰
if baidu_history and '' in str(baidu_history):
logger.info(f"百度历史过灰: {domain}")
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain, "百度历史过灰")
return True
# 检查标题敏感词
if chinaz_info and 'title' in chinaz_info:
if self._contains_sensitive_words(chinaz_info['title']):
logger.info(f"标题包含敏感词: {domain}")
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain, "标题包含敏感词")
return True
# 检查子域名
if baidu_site and 'subdomains' in baidu_site:
subdomains = baidu_site['subdomains']
# 排除 www, @, m
valid_subdomains = [sub for sub in subdomains if sub not in ['www', '@', 'm']]
if valid_subdomains:
logger.info(f"存在子域名: {domain}")
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain, "存在子域名")
return True
# 检查风险提示
if aizhan_info and 'risk' in aizhan_info:
if aizhan_info['risk'] in ['低风险', '疑似色情博彩风险', '严重影响权重']:
logger.info(f"风险提示: {domain}")
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain, f"风险提示: {aizhan_info['risk']}")
return True
# 检查WHOIS状态
if jucha_info and 'whois' in jucha_info:
if jucha_info['whois'].get('status') in ['clientHold', 'serverHold']:
logger.info(f"WHOIS状态异常: {domain}")
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain, "WHOIS状态异常")
return True
# 检查拦截检测
if jucha_info and 'intercept' in jucha_info:
if not jucha_info['intercept'].get('normal', True):
logger.info(f"拦截检测异常: {domain}")
self.db.update_domain_detect_status(domain_id, 4) # 4 表示黑名单
self.db.add_to_blacklist(domain, "拦截检测异常")
return True
return False
def _contains_sensitive_words(self, text):
"""
检查文本是否包含敏感词
:param text: 文本
:return: bool - 是否包含敏感词
"""
# 这里可以添加敏感词检查逻辑
sensitive_words = ['色情', '博彩', '赌博', '毒品', '暴力']
for word in sensitive_words:
if word in text:
return True
return False
def process_task(self, task_id):
"""
处理检测任务
:param task_id: 任务ID
:return: bool - 是否处理成功
"""
try:
# 获取任务信息
task = self.db.get_task_by_id(task_id)
if not task:
logger.error(f"任务不存在: {task_id}")
return False
domain_id = task['domain_id']
# 更新任务状态为执行中
self.db.update_task_status(task_id, 1) # 1 表示执行中
# 执行检测
success = self.detect_domain(domain_id)
# 更新任务状态
if success:
self.db.update_task_status(task_id, 2) # 2 表示完成
else:
# 增加重试次数
retry_count = task.get('retry_count', 0) + 1
if retry_count < 3:
self.db.update_task_retry_count(task_id, retry_count)
self.db.update_task_status(task_id, 0) # 0 表示待执行
else:
self.db.update_task_status(task_id, 3) # 3 表示失败
return success
except Exception as e:
logger.error(f"处理任务出错: {e}")
# 更新任务状态为失败
self.db.update_task_status(task_id, 3) # 3 表示失败
return False

View File

@@ -0,0 +1,261 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :domain_collector.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/4/8 23:50
@explain : 域名收集器
'''
import re
import tldextract
from loguru import logger
from app.utils.database import Database
from app.utils.domain_utils import normalize_domain
from app.config import config
class DomainCollector:
"""
域名收集器
"""
def __init__(self):
"""
初始化域名收集器
"""
self.db = Database(
host=config.DB_HOST,
port=config.DB_PORT,
database=config.DB_DATABASE,
user=config.DB_USER,
password=config.DB_PASSWORD
)
def add_domain(self, domain, source_type):
"""
添加域名
:param domain: 域名
:param source_type: 来源类型
:return: bool - 是否添加成功
"""
try:
# 标准化域名
normalized_domain = normalize_domain(domain)
if not normalized_domain:
logger.warning(f"无效域名: {domain}")
return False
# 提取顶级域名
ext = tldextract.extract(normalized_domain)
tld = ext.suffix
# 只保留 .com 和 .net
if tld not in ['com', 'net']:
logger.warning(f"不支持的顶级域名: {tld}")
return False
# 检查是否已存在
if self.db.domain_exists(normalized_domain):
logger.info(f"域名已存在: {normalized_domain}")
return False
# 添加域名
domain_id = self.db.add_domain(normalized_domain, tld, source_type)
if domain_id:
# 创建检测任务
self.db.create_detect_task(domain_id, 1) # 1 表示基础检测
logger.info(f"成功添加域名: {normalized_domain}")
return True
else:
logger.error(f"添加域名失败: {normalized_domain}")
return False
except Exception as e:
logger.error(f"添加域名出错: {e}")
return False
def add_domains_batch(self, domains, source_type, batch_size=1000, dry_run=False):
"""
批量添加域名
:param domains: 域名列表
:param source_type: 来源类型
:param batch_size: 批量大小
:param dry_run: 是否仅进行干运行(不实际添加域名)
:return: dict - 统计信息
"""
try:
# 统计信息
stats = {
'total': len(domains),
'valid': 0,
'added': 0,
'exists': 0,
'invalid': 0,
'failed': 0
}
# 处理大规模数据时,分批进行标准化和过滤
normalized_domains = []
batch_domains = []
for i, domain in enumerate(domains):
normalized = normalize_domain(domain)
if not normalized:
stats['invalid'] += 1
continue
# 提取顶级域名
ext = tldextract.extract(normalized)
tld = ext.suffix
# 只保留 .com 和 .net
if tld not in ['com', 'net']:
stats['invalid'] += 1
continue
normalized_domains.append((normalized, tld))
batch_domains.append(normalized)
stats['valid'] += 1
# 每1000个域名检查一次避免内存占用过高
if (i + 1) % 1000 == 0:
logger.info(f"已处理 {i + 1}/{len(domains)} 个域名")
logger.info(f"域名标准化完成,有效域名: {stats['valid']}")
# 提取所有域名
all_domains = [domain for domain, tld in normalized_domains]
# 批量检查域名是否存在
existing_domains = self.db.check_domains_exist(all_domains)
existing_set = set(existing_domains)
# 准备批量添加数据
batch_data = []
for domain, tld in normalized_domains:
if domain not in existing_set:
batch_data.append((domain, tld, source_type))
stats['exists'] = len(existing_domains)
stats['valid'] = len(normalized_domains)
# 干运行模式下直接返回统计信息
if dry_run:
stats['added'] = len(batch_data)
logger.info(f"干运行模式:准备添加 {len(batch_data)} 个新域名")
return stats
logger.info(f"准备添加 {len(batch_data)} 个新域名")
# 分批次添加
for i in range(0, len(batch_data), batch_size):
batch = batch_data[i:i+batch_size]
added_count = self.db.add_domains_batch(batch)
stats['added'] += added_count
stats['failed'] += len(batch) - added_count
# 每处理一批,记录一次进度
if (i + len(batch)) % (batch_size * 10) == 0:
logger.info(f"已添加 {i + len(batch)}/{len(batch_data)} 个域名")
logger.info(f"批量添加域名完成: {stats}")
return stats
except Exception as e:
logger.error(f"批量添加域名出错: {e}")
return {
'total': len(domains),
'valid': 0,
'added': 0,
'exists': 0,
'invalid': 0,
'failed': len(domains)
}
def import_from_file(self, file_path, source_type, batch_size=1000):
"""
从文件导入域名
:param file_path: 文件路径
:param source_type: 来源类型
:param batch_size: 批量大小
:return: dict - 导入统计信息
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
domains = f.readlines()
# 提取域名
domain_list = []
for domain in domains:
domain = domain.strip()
if domain:
domain_list.append(domain)
# 批量添加域名
stats = self.add_domains_batch(domain_list, source_type, batch_size)
logger.info(f"从文件导入完成: {stats}")
return stats
except Exception as e:
logger.error(f"从文件导入出错: {e}")
return {
'total': 0,
'valid': 0,
'added': 0,
'exists': 0,
'invalid': 0,
'failed': 0
}
def collect_from_juming(self, type_='一口价'):
"""
从聚名网收集域名
:param type_: 类型,一口价或过期删除
:return: int - 收集到的域名数量
"""
# 这里可以添加从聚名网收集域名的逻辑
logger.info(f"从聚名网收集 {type_} 域名")
# 模拟收集结果
return 0
def collect_from_search_engine(self, keyword, limit=100):
"""
从搜索引擎收集域名
:param keyword: 关键词
:param limit: 限制数量
:return: int - 收集到的域名数量
"""
# 这里可以添加从搜索引擎收集域名的逻辑
logger.info(f"从搜索引擎收集域名,关键词: {keyword}, 限制: {limit}")
# 模拟收集结果
return 0
def collect_from_enterprise_directory(self, url, limit=100):
"""
从企业目录收集域名
:param url: 企业目录URL
:param limit: 限制数量
:return: int - 收集到的域名数量
"""
# 这里可以添加从企业目录收集域名的逻辑
logger.info(f"从企业目录收集域名URL: {url}, 限制: {limit}")
# 模拟收集结果
return 0
def collect_from_zone_file(self, file_path):
"""
从Zone File收集域名
:param file_path: Zone File路径
:return: int - 收集到的域名数量
"""
# 这里可以添加从Zone File收集域名的逻辑
logger.info(f"从Zone File收集域名文件: {file_path}")
# 模拟收集结果
return 0

View File

@@ -0,0 +1,150 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :domain_processor.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/4/8 23:51
@explain : 域名处理器
'''
import re
import tldextract
from loguru import logger
from app.utils.database import Database
from app.utils.domain_utils import normalize_domain
class DomainProcessor:
"""
域名处理器
"""
def __init__(self):
"""
初始化域名处理器
"""
self.db = Database()
def process_domain(self, domain):
"""
处理域名
:param domain: 域名
:return: dict - 处理结果
"""
result = {
'original': domain,
'normalized': None,
'tld': None,
'valid': False,
'reason': ''
}
try:
# 标准化域名
normalized = normalize_domain(domain)
if not normalized:
result['reason'] = '无效域名格式'
return result
# 提取顶级域名
ext = tldextract.extract(normalized)
tld = ext.suffix
# 检查顶级域名
if tld not in ['com', 'net']:
result['reason'] = '不支持的顶级域名'
return result
result['normalized'] = normalized
result['tld'] = tld
result['valid'] = True
return result
except Exception as e:
logger.error(f"处理域名出错: {e}")
result['reason'] = f"处理出错: {str(e)}"
return result
def batch_process(self, domains):
"""
批量处理域名
:param domains: 域名列表
:return: list - 处理结果列表
"""
results = []
for domain in domains:
result = self.process_domain(domain)
results.append(result)
return results
def filter_valid_domains(self, domains):
"""
过滤有效的域名
:param domains: 域名列表
:return: list - 有效的域名列表
"""
valid_domains = []
for domain in domains:
result = self.process_domain(domain)
if result['valid']:
valid_domains.append(result['normalized'])
return valid_domains
def update_domain_status(self, domain_id, status_type, status_value):
"""
更新域名状态
:param domain_id: 域名ID
:param status_type: 状态类型
:param status_value: 状态值
:return: bool - 是否更新成功
"""
try:
if status_type == 'use_status':
return self.db.update_domain_use_status(domain_id, status_value)
elif status_type == 'detect_status':
return self.db.update_domain_detect_status(domain_id, status_value)
elif status_type == 'register_status':
return self.db.update_domain_register_status(domain_id, status_value)
else:
logger.error(f"未知的状态类型: {status_type}")
return False
except Exception as e:
logger.error(f"更新域名状态出错: {e}")
return False
def blacklist_domain(self, domain_id, reason):
"""
将域名加入黑名单
:param domain_id: 域名ID
:param reason: 黑名单原因
:return: bool - 是否操作成功
"""
try:
# 更新检测状态为黑名单
if self.db.update_domain_detect_status(domain_id, 4): # 4 表示黑名单
# 添加到黑名单表
domain = self.db.get_domain_by_id(domain_id)
if domain:
return self.db.add_to_blacklist(domain['domain'], reason)
return False
except Exception as e:
logger.error(f"将域名加入黑名单出错: {e}")
return False
def get_domain_statistics(self):
"""
获取域名统计信息
:return: dict - 统计信息
"""
try:
return self.db.get_domain_statistics()
except Exception as e:
logger.error(f"获取域名统计信息出错: {e}")
return {}

273
app/core/export_manager.py Normal file
View File

@@ -0,0 +1,273 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :export_manager.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/4/8 23:54
@explain : 导出管理器
'''
import os
from loguru import logger
from app.utils.database import Database
class ExportManager:
"""
导出管理器
"""
def __init__(self):
"""
初始化导出管理器
"""
self.db = Database()
def export_domains(self, filter_conditions, output_file):
"""
导出域名
:param filter_conditions: 筛选条件
:param output_file: 输出文件路径
:return: int - 导出的域名数量
"""
try:
# 查询符合条件的域名
domains = self.db.get_domains_by_conditions(filter_conditions)
if not domains:
logger.warning("没有符合条件的域名")
return 0
# 导出到文件
with open(output_file, 'w', encoding='utf-8') as f:
for domain in domains:
f.write(domain['domain'] + '\n')
logger.info(f"成功导出 {len(domains)} 个域名到 {output_file}")
return len(domains)
except Exception as e:
logger.error(f"导出域名出错: {e}")
return 0
def export_with_details(self, filter_conditions, output_file):
"""
导出域名及其详细信息
:param filter_conditions: 筛选条件
:param output_file: 输出文件路径
:return: int - 导出的域名数量
"""
try:
# 查询符合条件的域名及其详细信息
domains = self.db.get_domains_with_details(filter_conditions)
if not domains:
logger.warning("没有符合条件的域名")
return 0
# 导出到文件
with open(output_file, 'w', encoding='utf-8') as f:
# 写入表头
f.write('域名,注册状态,使用状态,检测状态,备案历史,备案年份,快照年份,友情链接数量\n')
# 写入数据
for domain in domains:
line = f"{domain['domain']},{domain['register_status']},{domain['use_status']},{domain['detect_status']},{domain['has_beian']},{domain['beian_year']},{domain['snapshot_years']},{domain['backlink_count']}\n"
f.write(line)
logger.info(f"成功导出 {len(domains)} 个域名及其详细信息到 {output_file}")
return len(domains)
except Exception as e:
logger.error(f"导出域名详细信息出错: {e}")
return 0
def batch_update_status(self, domain_ids, status_type, status_value):
"""
批量更新域名状态
:param domain_ids: 域名ID列表
:param status_type: 状态类型
:param status_value: 状态值
:return: int - 更新成功的域名数量
"""
try:
success_count = 0
for domain_id in domain_ids:
if self.db.update_domain_status(domain_id, status_type, status_value):
success_count += 1
logger.info(f"成功更新 {success_count} 个域名的状态")
return success_count
except Exception as e:
logger.error(f"批量更新域名状态出错: {e}")
return 0
def get_filtered_domains(self, filter_conditions, limit=1000):
"""
获取符合条件的域名
:param filter_conditions: 筛选条件
:param limit: 限制数量
:return: list - 域名列表
"""
try:
domains = self.db.get_domains_by_conditions(filter_conditions, limit)
logger.info(f"获取到 {len(domains)} 个符合条件的域名")
return domains
except Exception as e:
logger.error(f"获取符合条件的域名出错: {e}")
return []
def generate_report(self, output_file):
"""
生成统计报告
:param output_file: 输出文件路径
:return: bool - 是否生成成功
"""
try:
# 获取域名统计信息
domain_stats = self.db.get_domain_statistics()
# 获取任务统计信息
task_stats = self.db.get_task_statistics()
# 生成报告
with open(output_file, 'w', encoding='utf-8') as f:
f.write('域名库系统统计报告\n')
f.write('=' * 50 + '\n')
f.write('\n域名统计:\n')
f.write(f'总域名数: {domain_stats.get("total", 0)}\n')
f.write(f'可注册域名: {domain_stats.get("available", 0)}\n')
f.write(f'已注册域名: {domain_stats.get("registered", 0)}\n')
f.write(f'黑名单域名: {domain_stats.get("blacklisted", 0)}\n')
f.write('\n任务统计:\n')
f.write(f'总任务数: {task_stats.get("total", 0)}\n')
f.write(f'待执行任务: {task_stats.get("pending", 0)}\n')
f.write(f'执行中任务: {task_stats.get("running", 0)}\n')
f.write(f'已完成任务: {task_stats.get("completed", 0)}\n')
f.write(f'失败任务: {task_stats.get("failed", 0)}\n')
logger.info(f"成功生成统计报告到 {output_file}")
return True
except Exception as e:
logger.error(f"生成统计报告出错: {e}")
return False
def export_to_excel(self, domains, output_file):
"""
导出域名到Excel文件
:param domains: 域名列表
:param output_file: 输出文件路径
:return: int - 导出的域名数量
"""
try:
# 尝试导入openpyxl
try:
from openpyxl import Workbook
except ImportError:
# 如果没有安装openpyxl使用CSV格式作为替代
logger.warning("openpyxl库未安装将使用CSV格式导出")
return self.export_to_csv(domains, output_file.replace('.xlsx', '.csv'))
# 创建工作簿
wb = Workbook()
ws = wb.active
# 写入表头
headers = ['域名', '注册状态', '使用状态', '检测状态', '人工复核状态', '过期时间', '单位性质', '网站首页网址', '检测时间', '备案历史', '备案年份', '快照年份', '百度历史', '百度Site', '是否中文标题', '360 Site', 'Google Site', '友情链接数量']
ws.append(headers)
# 写入数据
for domain in domains:
row = [
domain.get('domain', ''),
domain.get('register_status', ''),
domain.get('use_status', ''),
domain.get('detect_status', ''),
domain.get('review_status', ''),
domain.get('expire_date', ''),
domain.get('company_type', ''),
domain.get('website_url', ''),
domain.get('detect_time', ''),
domain.get('has_beian', ''),
domain.get('beian_year', ''),
domain.get('snapshot_years', ''),
domain.get('baidu_history', ''),
domain.get('baidu_site', ''),
domain.get('is_chinese_title', ''),
domain.get('qihu360_site', ''),
domain.get('google_site', ''),
domain.get('backlink_count', '')
]
ws.append(row)
# 保存文件
wb.save(output_file)
logger.info(f"成功导出 {len(domains)} 个域名到Excel文件: {output_file}")
return len(domains)
except Exception as e:
logger.error(f"导出Excel文件出错: {e}")
# 尝试使用CSV格式作为替代
try:
csv_file = output_file.replace('.xlsx', '.csv')
logger.info(f"尝试使用CSV格式导出到: {csv_file}")
return self.export_to_csv(domains, csv_file)
except Exception as e2:
logger.error(f"导出CSV文件也失败: {e2}")
raise
def export_to_csv(self, domains, output_file):
"""
导出域名到CSV文件
:param domains: 域名列表
:param output_file: 输出文件路径
:return: int - 导出的域名数量
"""
try:
import csv
# 写入文件
with open(output_file, 'w', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
# 写入表头
headers = ['域名', '注册状态', '使用状态', '检测状态', '人工复核状态', '过期时间', '单位性质', '网站首页网址', '检测时间', '备案历史', '备案年份', '快照年份', '百度历史', '百度Site', '是否中文标题', '360 Site', 'Google Site', '友情链接数量']
writer.writerow(headers)
# 写入数据
for domain in domains:
row = [
domain.get('domain', ''),
domain.get('register_status', ''),
domain.get('use_status', ''),
domain.get('detect_status', ''),
domain.get('review_status', ''),
domain.get('expire_date', ''),
domain.get('company_type', ''),
domain.get('website_url', ''),
domain.get('detect_time', ''),
domain.get('has_beian', ''),
domain.get('beian_year', ''),
domain.get('snapshot_years', ''),
domain.get('baidu_history', ''),
domain.get('baidu_site', ''),
domain.get('is_chinese_title', ''),
domain.get('qihu360_site', ''),
domain.get('google_site', ''),
domain.get('backlink_count', '')
]
writer.writerow(row)
logger.info(f"成功导出 {len(domains)} 个域名到CSV文件: {output_file}")
return len(domains)
except Exception as e:
logger.error(f"导出CSV文件出错: {e}")
raise

162
app/core/task_scheduler.py Normal file
View File

@@ -0,0 +1,162 @@
# -*- coding: UTF-8 -*-
'''
@Project :domainScanDemo
@File :task_scheduler.py
@IDE :PyCharm
@Author :梦伴
@Date :2026/4/8 23:53
@explain : 任务调度器
'''
import time
import threading
from loguru import logger
from app.utils.database import Database
from app.core.detect_engine import DetectEngine
class TaskScheduler:
"""
任务调度器
"""
def __init__(self):
"""
初始化任务调度器
"""
self.db = Database()
self.detect_engine = DetectEngine()
self.running = False
self.threads = []
self.max_threads = 10
def start(self):
"""
启动任务调度器
"""
if self.running:
logger.info("任务调度器已经在运行中")
return
self.running = True
logger.info("启动任务调度器")
# 启动多个线程处理任务
for i in range(self.max_threads):
thread = threading.Thread(target=self._process_tasks, daemon=True)
thread.start()
self.threads.append(thread)
logger.info(f"启动任务处理线程 {i+1}")
def stop(self):
"""
停止任务调度器
"""
self.running = False
logger.info("停止任务调度器")
# 等待线程结束
for thread in self.threads:
thread.join(timeout=5)
self.threads.clear()
logger.info("任务调度器已停止")
def _process_tasks(self):
"""
处理任务
"""
while self.running:
try:
# 获取待执行的任务
task = self.db.get_pending_task()
if not task:
# 没有任务,休眠一段时间
time.sleep(1)
continue
task_id = task['id']
domain_id = task['domain_id']
logger.info(f"处理任务: {task_id}, 域名ID: {domain_id}")
# 执行任务
success = self.detect_engine.process_task(task_id)
if success:
logger.info(f"任务处理成功: {task_id}")
else:
logger.warning(f"任务处理失败: {task_id}")
# 短暂休眠,避免过于频繁的数据库操作
time.sleep(0.1)
except Exception as e:
logger.error(f"处理任务出错: {e}")
# 休眠一段时间,避免出错后无限循环
time.sleep(5)
def add_task(self, domain_id, task_type=1, priority=0):
"""
添加任务
:param domain_id: 域名ID
:param task_type: 任务类型1-基础检测2-深度检测
:param priority: 优先级0-低1-中2-高
:return: int - 任务ID
"""
try:
task_id = self.db.create_detect_task(domain_id, task_type, priority)
logger.info(f"添加任务成功: {task_id}, 域名ID: {domain_id}")
return task_id
except Exception as e:
logger.error(f"添加任务失败: {e}")
return None
def get_task_stats(self):
"""
获取任务统计信息
:return: dict - 任务统计信息
"""
try:
return self.db.get_task_statistics()
except Exception as e:
logger.error(f"获取任务统计信息出错: {e}")
return {}
def retry_failed_tasks(self):
"""
重试失败的任务
:return: int - 重试的任务数量
"""
try:
tasks = self.db.get_failed_tasks()
retry_count = 0
for task in tasks:
task_id = task['id']
self.db.update_task_status(task_id, 0) # 0 表示待执行
self.db.update_task_retry_count(task_id, 0) # 重置重试次数
retry_count += 1
logger.info(f"重试 {retry_count} 个失败的任务")
return retry_count
except Exception as e:
logger.error(f"重试失败任务出错: {e}")
return 0
def clear_completed_tasks(self, days=7):
"""
清理已完成的任务
:param days: 保留天数
:return: int - 清理的任务数量
"""
try:
count = self.db.clear_completed_tasks(days)
logger.info(f"清理 {count} 个已完成的任务")
return count
except Exception as e:
logger.error(f"清理已完成任务出错: {e}")
return 0