爬虫基础--IO多路复用单线程异步非阻塞

最近一直的学习爬虫  ,进行基础的学习

性能相关 参考

https://www.cnblogs.com/wupeiqi/p/6229292.html

 

  1 # 目标:单线程实现并发HTTP请求
  2 #
  3 # socket
  4 # IO多路复用
  5 # HTTP协议
  6 #
  7 # 流程
  8 # http://www.163.com/new/
  9 # 1. sk连接  IP 禾端口进行连接
 10 # 2.请求信息
 11 # 请求头
 12 # k=v\r\n
 13 # k=v\r\n
 14 # k=v\r\n
 15 # \r\n\r\n
 16 # 请求体
 17 
 18 import select
 19 import socket
 20 import time
 21 
 22 
 23 class AsyncTimeoutException(TimeoutError):
 24     """
 25     请求超时异常类
 26     """
 27 
 28     def __init__(self, msg):
 29         self.msg = msg
 30         super(AsyncTimeoutException, self).__init__(msg)
 31 
 32 
 33 class HttpContext(object):
 34     """封装请求和相应的基本数据"""
 35 
 36     def __init__(self, sock, host, port, method, url, data, callback, timeout=5):
 37         """
 38         sock: 请求的客户端socket对象
 39         host: 请求的主机名
 40         port: 请求的端口
 41         method: 请求方式
 42         url: 请求的URL
 43         data: 请求时请求体中的数据
 44         callback: 请求完成后的回调函数
 45         timeout: 请求的超时时间
 46         """
 47         self.sock = sock   #sock: 请求的客户端socket对象
 48         self.callback = callback  #callback: 请求完成后的回调函数
 49         self.host = host   #host: 请求的主机名
 50         self.port = port  # port: 请求的端口
 51         self.method = method #method: 请求方式
 52         self.url = url  #url: 请求的URL
 53         self.data = data  #data: 请求时请求体中的数据
 54 
 55         self.timeout = timeout   #timeout: 请求的超时时间
 56 
 57         self.__start_time = time.time()  #当前时间
 58         self.__buffer = []  #在buffer中写入响应内容
 59 
 60     def is_timeout(self):
 61         """当前请求是否已经超时"""
 62         current_time = time.time()
 63         if (self.__start_time + self.timeout) < current_time:
 64             return True
 65 
 66     def fileno(self):
 67         """请求sockect对象的文件描述符,用于select监听"""
 68         return self.sock.fileno()
 69 
 70     def write(self, data):
 71         """在buffer中写入响应内容"""
 72         self.__buffer.append(data)
 73 
 74     def finish(self, exc=None):
 75         """在buffer中写入响应内容完成,执行请求的回调函数"""
 76         if not exc:
 77             response = b''.join(self.__buffer)
 78             self.callback(self, response, exc)
 79         else:
 80             self.callback(self, None, exc)
 81 
 82     def send_request_data(self):  #发送请求 伪造请求头 请求体
 83         content = """%s %s HTTP/1.0\r\nHost: %s\r\n\r\n%s""" % (
 84             # 请求方式          请求的URL  请求的主机名  请求时请求体中的数据
 85             self.method.upper(), self.url, self.host, self.data,)
 86 
 87         return content.encode(encoding='utf8')
 88 
 89 class AsyncRequest(object):
 90     def __init__(self):
 91         self.fds = []  #用于存放  连接有返回值的请求
 92         self.connections = []#用于存放需要连接的请求
 93 
 94     def add_request(self, host, port, method, url, data, callback, timeout):
 95         """创建一个要请求"""
 96         client = socket.socket()
 97         client.setblocking(False)
 98         try:
 99             client.connect((host, port))
100         except BlockingIOError as e:
101             pass
102             # print('已经向远程发送连接的请求')
103         req = HttpContext(client, host, port, method, url, data, callback, timeout)
104         self.connections.append(req)
105         self.fds.append(req)
106 
107     def check_conn_timeout(self):
108         """检查所有的请求,是否有已经连接超时,如果有则终止"""
109         timeout_list = [] #超时列表
110         for context in self.connections:
111             if context.is_timeout(): #进行超时检测 如果是超时
112                 timeout_list.append(context) #加入超时列表
113         for context in timeout_list: #进行超时处理
114             context.finish(AsyncTimeoutException('请求超时'))
115             self.fds.remove(context) #进行移除 请求 待返回列表
116             self.connections.remove(context) #进行移除 请求 待发送列表
117 
118     def running(self):
119         """事件循环,用于检测请求的socket是否已经就绪,从而执行相关操作"""
120         while True:
121             if not self.fds: #如果没有请求 直接返回
122                 return
123             r, w, e = select.select(self.fds, self.connections, self.fds, 0.05)  #监测socket对象的变化
124 
125             for context in r:
126                 sock = context.sock #接收请求 连接
127                 while True:
128                     try:
129                         data = sock.recv(8096)# 取返回值
130                         if not data:#如果没有返回值
131                             self.fds.remove(context)  #移除等待返回值 的请求
132                             context.finish()#完成请求
133                             break
134                         else:
135                             context.write(data)
136                     except BlockingIOError as e:
137                         break
138                     except TimeoutError as e: #如果超时,,移除 发送的请求和接收的请求 取消请求
139                         self.fds.remove(context)
140                         self.connections.remove(context)
141                         context.finish(e)
142                         break
143 
144             for context in w:
145                 # 已经连接成功远程服务器,开始向远程发送请求数据
146                 if context in self.fds:
147                     data = context.send_request_data()#请求头 请求体
148                     context.sock.sendall(data)#进行连接
149                     self.connections.remove(context) #移除已经连接成功的请求
150 
151             self.check_conn_timeout()  #检测  是否超时
152 
153 
154 if __name__ == '__main__':
155     def callback_func(context, response, ex):
156         """
157         :param context: HttpContext对象,内部封装了请求相关信息
158         :param response: 请求响应内容
159         :param ex: 是否出现异常(如果有异常则值为异常对象;否则值为None)
160         :return:
161         """
162         print(context, response, ex)
163 
164     obj = AsyncRequest()
165     url_list = [
166         {'host': 'www.google.com', 'port': 80, 'method': 'GET', 'url': '/', 'data': '', 'timeout': 5,
167          'callback': callback_func},
168         {'host': 'www.baidu.com', 'port': 80, 'method': 'GET', 'url': '/', 'data': '', 'timeout': 5,
169          'callback': callback_func},
170         {'host': 'www.bing.com', 'port': 80, 'method': 'GET', 'url': '/', 'data': '', 'timeout': 5,
171          'callback': callback_func},
172     ]
173     for item in url_list:
174         print(item)
175         obj.add_request(**item)
176 
177     obj.running()

 

posted @ 2018-06-04 22:08  莫柔落切  阅读(333)  评论(0编辑  收藏  举报