searchdomain.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import whois
  2. from concurrent.futures import ThreadPoolExecutor
  3. import logging,os
  4. import argparse
  5. from . import db
  6. class SearchDomain(object):
  7. """search avaliable domain and save result"""
  8. def __init__(self, params: dict, debug=False, export_all=True, log_callback=None):
  9. '''
  10. 初始化
  11. debug 调试模式
  12. export_all 是否导出所有域名,默认导出可用域名
  13. log_callback 日志回调函数,用于GUI模式下显示日志
  14. return:
  15. '''
  16. super(SearchDomain, self).__init__()
  17. self.params = params
  18. self.export_all=export_all
  19. self.input=params["input"]
  20. self.output=params["output"]
  21. self.log_callback = log_callback
  22. self._output_file_checked = False # 标记输出文件是否已检查
  23. self._cancelled = False # 取消标志
  24. # 获取当前模块的日志记录器
  25. self.logger = logging.getLogger(__name__)
  26. # 配置日志系统
  27. if log_callback:
  28. # 如果有日志回调函数,创建自定义处理器
  29. # 创建自定义处理器,将日志输出到回调函数
  30. class CallbackHandler(logging.Handler):
  31. def __init__(self, callback):
  32. super().__init__()
  33. self.callback = callback
  34. def emit(self, record):
  35. msg = self.format(record)
  36. if self.callback:
  37. self.callback(msg)
  38. self.logger.setLevel(logging.INFO)
  39. # 检查是否已经添加了回调处理器,避免重复添加
  40. has_callback_handler = any(isinstance(h, CallbackHandler) for h in self.logger.handlers)
  41. if not has_callback_handler:
  42. callback_handler = CallbackHandler(log_callback)
  43. callback_handler.setLevel(logging.INFO)
  44. formatter = logging.Formatter('%(message)s')
  45. callback_handler.setFormatter(formatter)
  46. self.logger.addHandler(callback_handler)
  47. elif debug == True:
  48. logging.basicConfig(level=logging.DEBUG, format='%(message)s', force=True)
  49. else:
  50. logging.basicConfig(level=logging.INFO, format='%(message)s', force=True)
  51. def cancel(self):
  52. """取消搜索任务"""
  53. self._cancelled = True
  54. self.logger.info("搜索任务已取消")
  55. def crawl(self, domain: str, index:int) -> None:
  56. '''
  57. 检测域名是否可用
  58. :params domain 域名:
  59. :return true or false'''
  60. # 检查是否已取消
  61. if self._cancelled:
  62. return
  63. res = False
  64. try:
  65. whi = whois.whois(domain)
  66. res = False
  67. self.logger.info(str(index) + ": searching domain:"+ domain + " is unavaliable.")
  68. except Exception as e:
  69. error_str = str(e)
  70. # 检查是否是域名未注册的错误
  71. if "No match" in error_str or "No match for" in error_str:
  72. res = True
  73. self.logger.info(str(index) + ": searching domain:"+ domain +" is avaliable.")
  74. else:
  75. res = False
  76. self.logger.error(f"Error checking {domain}: {error_str}")
  77. # 再次检查是否已取消
  78. if self._cancelled:
  79. return
  80. if self.export_all:
  81. self.saveRes(domain, res)
  82. else:
  83. if res:
  84. self.saveRes(domain, res)
  85. def saveRes(self, domain: str, res: bool):
  86. """ save result to file """
  87. # db.Mysql().save()
  88. output_path = os.path.join(self.params["app_path"], self.output)
  89. # 检查输出文件是否存在,不存在则创建并警告(只检查一次)
  90. if not self._output_file_checked:
  91. if not os.path.exists(output_path):
  92. # 确保目录存在
  93. dir_path = os.path.dirname(output_path)
  94. if dir_path and not os.path.exists(dir_path):
  95. os.makedirs(dir_path, exist_ok=True)
  96. # 创建文件
  97. with open(output_path, 'w', encoding='utf-8') as f:
  98. pass
  99. self.logger.warning(f"警告:输出文件不存在,已创建: {output_path}")
  100. self._output_file_checked = True
  101. db.File().save(output_path, domain + " " + str(res))
  102. def run(self):
  103. '''begin search domain'''
  104. # 先创建输出文件(如果不存在),在保存结果前创建
  105. output_path = os.path.join(self.params["app_path"], self.output)
  106. if not os.path.exists(output_path):
  107. # 确保目录存在
  108. dir_path = os.path.dirname(output_path)
  109. if dir_path and not os.path.exists(dir_path):
  110. os.makedirs(dir_path, exist_ok=True)
  111. # 创建文件
  112. with open(output_path, 'w', encoding='utf-8') as f:
  113. pass
  114. self.logger.warning(f"警告:输出文件不存在,已创建: {output_path}")
  115. # 支持 input 为完整路径或相对路径
  116. input_path = self.input if os.path.isabs(self.input) else os.path.join(self.params["app_path"], self.input)
  117. with open(input_path, "r", encoding="utf8", errors="ignore") as file:
  118. with ThreadPoolExecutor(max_workers=5) as pool:
  119. index = 0
  120. futures = []
  121. for line in file.readlines():
  122. # 检查是否已取消
  123. if self._cancelled:
  124. break
  125. domain = line.strip()
  126. if domain: # 跳过空行
  127. index = index + 1
  128. future = pool.submit(self.crawl, domain, index)
  129. futures.append(future)
  130. # 等待所有任务完成,但如果已取消则取消未完成的任务
  131. if self._cancelled:
  132. # 取消所有未完成的任务
  133. for future in futures:
  134. future.cancel()
  135. self.logger.info("已取消所有未完成的任务")
  136. else:
  137. # 等待所有任务完成
  138. for future in futures:
  139. try:
  140. future.result()
  141. except Exception as e:
  142. if not self._cancelled:
  143. self.logger.error(f"任务执行出错: {str(e)}")
  144. if __name__ == '__main__':
  145. sd = SearchDomain()
  146. sd.run()