一、实现原理
控制调度器主要是产生并启动 URL 管理进程、数据提取进程和数据存储进程,同时维护4个队列保持进程间的通信,分别为 url_q、result_q、conn_q、store_q。4个队列说明如下:
- url_q:队列是 URL 管理进程将 URL 传递给爬虫节点的通道。
- result_q:队列是爬虫节点将数据返回给数据提取进程的通道。
- conn_q:队列是数据提取进程将新的 URL 数据提交给 URL 管理进程的通道。
- store_q:队列是数据提取进程将获取到的数据交给数据存储进程的通道。
二、代码如下
1 from multiprocessing.managers import BaseManager 2 from multiprocessing import Queue, Process 3 from DataOutput import DataOutput 4 from UrlManager import UrlManager 5 import time 6 7 8 class NodeManager: 9 def start_manager(self, url_q, result_q): 10 """ 11 创建一个分布式管理器 12 :param url_q: url 队列 13 :param result_q: 结果队列 14 :return: BaseManager 15 """ 16 # 把创建的两个队列注册在网络上,利用 register 方法,callable 参数关联了 Queue 对象 17 # 将 Queue 对象在网络中暴露 18 BaseManager.register('get_task_queue', callable=lambda:url_q) 19 BaseManager.register('get_result_queue', callable=lambda:result_q) 20 # 绑定端口 8001,设置验证口令"douban",相当于对象的初始化并返回 21 return BaseManager(address=('', 8001), authkey='douban'.encode('utf-8')) 22 23 def url_manager_proc(self, url_q, conn_q, root_url): 24 """ 25 url 管理进程 26 :param url_q: url 队列 27 :param conn_q: 解析得到的 url 队列 28 :param root_url: 起始 url 29 :return: None 30 """ 31 url_manage = UrlManager() 32 url_manage.add_new_url(root_url) 33 while True: 34 while url_manage.has_new_url(): 35 print('old_urls={}'.format(url_manage.old_urls_size())) 36 new_url = url_manage.get_new_url() 37 url_q.put(new_url) 38 urls = conn_q.get() 39 url_manage.add_new_urls(urls) 40 else: 41 url_q.put('end') 42 print('控制节点发起结束通知') 43 url_manage.save_progress('old_urls.txt', url_manage.old_urls) 44 url_manage.save_progress('new_urls.txt', url_manage.new_urls) 45 return 46 47 def result_solve_proc(self, result_q, conn_q, store_q): 48 """ 49 数据提取进程 50 :param result_q: 未处理数据队列 51 :param conn_q: 解析得到的 url 队列 52 :param store_q: 解析后的数据队列 53 :return: 54 """ 55 while True: 56 try: 57 if not result_q.empty(): 58 content = result_q.get() 59 if content['new_urls'] == 'end': 60 print('结果分析进程接收通知然后结束') 61 store_q.put('end') 62 return 63 64 conn_q.put(content['new_urls']) 65 store_q.put(content['data']) 66 else: 67 time.sleep(0.1) 68 except: 69 time.sleep(0.1) 70 71 def store_proc(self, store_q): 72 """ 73 数据存储进程 74 :param store_q: 解析后的数据队列 75 :return: 76 """ 77 output = DataOutput() 78 while True: 79 if not store_q.empty(): 80 data = store_q.get() 81 82 if data == 'end': 83 print('存储进程接收结束通知然后结束') 84 return 85 86 for item in data: 87 output.output_csv(item) 88 else: 89 time.sleep(0.1) 90 91 92 if __name__ == '__main__': 93 # 初始化 4 个队列 94 url_q = Queue() 95 result_q = Queue() 96 conn_q = Queue() 97 store_q = Queue() 98 # 创建分布式管理器 99 node = NodeManager() 100 manager = node.start_manager(url_q, result_q) 101 # 创建 url 管理进程、数据提取进程和数据存储进程 102 url = 'https://movie.douban.com/top250?start=0' 103 url_manager_proc = Process(target=node.url_manager_proc, args=(url_q, conn_q, url,)) 104 result_solve_proc = Process(target=node.result_solve_proc, args=(result_q, conn_q, store_q,)) 105 store_proc = Process(target=node.store_proc, args=(store_q,)) 106 # 启动 3 个进程和分布式管理器 107 url_manager_proc.start() 108 result_solve_proc.start() 109 store_proc.start() 110 manager.get_server().serve_forever()