Python 使用APScheduler实现并发定时Dig域名解析任务
一、概念澄清
-
APScheduler
APScheduler是Python的一个定时任务框架,用于执行周期或者定时任务,可以基于日期、时间间隔,及类似于Linux系统上的定时任务crontab类型的定时任务,因此可以将其作为Linux系统crontab或windows计划任务程序的替换,值得注意的是,APScheduler不是守护进程,不带有任何命令行工具,仅仅是作为提供调度器或者调度服务的基础模块,使用者需要根据具体需求引入使用。
-
触发器: 触发器包含调度逻辑, 描述一个任务何时被触发, 有按日期、按时间间隔、按cronpb 描述式三种触发方式。每作业都有它自己的触发器, 除了初始配置之外, 触发器是完全无状态的。
-
作业存储器:作业存储器指定了作业被存放的位置, 默认的作业存储器是内存, 也可以将作业保存在各种数据库中。
-
执行器:执行器是将指定的作业( 调用函数) 提交到线程池或进程池中运行, 当任务完成时, 执行器通知调度器触发相应的事件。
-
调度器:任务调度器, 控制器角色, 通过它配置作业存储器、执行器和触发器, 添加、修改和删除任务。
-
常用调度器
BlockingScheduler:适用于调度程序是进程中唯一运行的进程,调用start函数会阻塞当前线程,不能立即返回。 BackgroundScheduler:适用于调度程序在应用程序的后台运行,调用start后主线程不会阻塞。 AsyncIOScheduler:适用于使用了asyncio模块的应用程序。 GeventScheduler:适用于使用gevent模块的应用程序。 TwistedScheduler:适用于构建Twisted的应用程序。 QtScheduler:适用于构建Qt的应用程序。
-
-
-
threating
python提供Thread类实现线程功能,其基本结构和参数和进程一致,如下:
-
结构
class Thread(group=None, target=None, name=None, args=(), kwargs={})
-
参数
- target: 调用对象,一般为函数、类,是进程的执行实体
- name: 进程的别名
- args:调用对象的位置参数元组
- kwargs:调用对象的字典
- group:基本不使用,忽略
-
Process类提供的方法
- is_alive(): 返回进程是否激活
- join(]): 阻塞进程,直到进程执行完成或者终止
- run(): 进程运行的函数,可以被重写
- start(): 激活进程,进程开始运行
- terminate(): 进程终止
-
二、基本示例
-
APScheduler
-
基本示例
from datetime import datetime import os from apscheduler.schedulers.blocking import BlockingScheduler def tick(): print('Tick! The time is: %s' % datetime.now()) if __name__ == '__main__': scheduler = BlockingScheduler() scheduler.add_job(tick, 'interval', seconds=3) print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C ')) try: scheduler.start() except (KeyboardInterrupt, SystemExit): pass
-
间隔任务功能示例
from datetime import datetime import os from apscheduler.schedulers.blocking import BlockingScheduler def tick(): print('Tick! The time is: %s' % datetime.now()) if __name__ == '__main__': scheduler = BlockingScheduler() scheduler.add_job(tick, 'cron', hour=19,minute=23) print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C ')) try: scheduler.start() except (KeyboardInterrupt, SystemExit): pass hour =19 , minute =23 hour ='19', minute ='23' minute = '*/3' 表示每 5 分钟执行一次 hour ='19-21', minute= '23' 表示 19:23、 20:23、 21:23 各执行一次任务
-
-
threating
三、插件实现
-
功能:实现指定dns域名的dig解析探测: dig domain @nam_server A / AAAA
-
使用包:
"dnspython" "numpy" "datetime" "IPy" "apscheduler" "requests"
-
域名解析模块【支持v4和v6】
# ------------------------------------- dig模块 ------------------------------------- # 域名解析,根据域名和nameserver进行解析,返回解析结果集数组 def dns_resolve(domains, name_server, detect_type): for domain in domains: num = 0 ips = [] ttl_value = 0 if name_server == "": continue try: ans = resolver.resolve(domain, detect_type) # 指定查询类型为A记录 ans.nameserver = [].append(name_server) for i in ans.response.answer: # 通过response.answer方法获取查询信息 for ip in i.items: # 遍历结果集信息 ips.append(ip) num = num + 1 ttl_value = ans.rrset.ttl except: ips = ["unknown"] num = 0 ttl_value = 0 finally: print("采集结果: ", domain + '@' + name_server + '@' + str(num) + '@' + str( ttl_value) + '@' + ';'.join( '%s' % a for a in ips) + '\n')
-
多线程模块
# ------------------------------------- 任务处理模块 ------------------------------------- # 继承一个Thread类,在run方法中进行需要重复的单个函数操作 class Detect(threading.Thread): def __init__(self, queue, lock, num): # 传递一个队列queue和线程锁,并行数 threading.Thread.__init__(self) self.queue = queue self.lock = lock self.num = num def run(self): with self.num: # 同时并行指定的线程数量,执行完毕一个则死掉一个线程 # 以下为需要重复的单次函数操作 target_info = self.queue.get() # 等待队列进入 for task_name, task_info in target_info.items(): urls = [task_info["domain"]] # self.lock.acquire() # 锁住线程,防止同时输出造成混乱 dns_resolve(urls, task_info["name_server_v4"], 'A', ) dns_resolve(urls, task_info["name_server_v6"], 'AAAA') # self.lock.release() # print(datetime.datetime.now().strftime('%Y/%m/%d/%H/%M/%S'), '任务线程:', self.name, '任务详情:', target_info) self.queue.task_done() # 发出此队列完成信号
-
任务队列示例
def task_queue(que): target_info = {} domain_info = {"domain": "www.baidu.com", "name_server_v4": "8.8.8.8", "name_server_v6": ""} target_info['your_task_name'] = domain_info que.put(target_info) # 模拟执行函数的逐个不同输入 def detect_core(): threads = [] que = queue.Queue() # 初始化任务队列 lock = threading.Lock() num = threading.Semaphore(600) # 设置同时执行的线程数 # 启动所有线程,隔3秒执行一次 task_queue(que) for i in range(que.qsize()): # 总共需要执行的次数 t = Detect(que, lock, num) t.start() threads.append(t) # 等待线程执行完毕 for t in threads: t.join() que.join() # 等待队列执行完毕才继续执行,否则下面语句会在线程未接受就开始执行
-
调度器
# ------------------------------------- 主函数入口 ------------------------------------- if __name__ == '__main__': print("任务执行") # 初始化调度器模块 scheduler = BlockingScheduler() # 添加任务 scheduler.add_job(detect_core, 'interval', seconds=3, id="1", coalesce=True, max_instances=2) # 启动任务 scheduler.start()
-
运行结果
任务执行 采集结果: www.baidu.com@8.8.8.8@3@260@www.a.shifen.com.;163.177.151.109;163.177.151.110 采集结果: www.baidu.com@8.8.8.8@0@0@unknown 采集结果: www.baidu.com@8.8.8.8@3@258@www.a.shifen.com.;163.177.151.109;163.177.151.110 采集结果: www.baidu.com@8.8.8.8@3@255@www.a.shifen.com.;163.177.151.109;163.177.151.110 ...
备注:【本文代码基本部分来源于书籍《Python自动化运维快速入门》,有兴趣的可以自行购买查阅】