爬虫基础--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()
您的资助是我最大的动力!
金额随意,欢迎来赏!
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的
因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【莫柔落切】!
联系或打赏博主【莫柔落切】!https://home.cnblogs.com/u/uge3/