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

时间:2023-12-03 13:44:02

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

性能相关 参考

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

 # 目标:单线程实现并发HTTP请求
#
# socket
# IO多路复用
# HTTP协议
#
# 流程
# http://www.163.com/new/
# 1. sk连接 IP 禾端口进行连接
# 2.请求信息
# 请求头
# k=v\r\n
# k=v\r\n
# k=v\r\n
# \r\n\r\n
# 请求体 import select
import socket
import time class AsyncTimeoutException(TimeoutError):
"""
请求超时异常类
""" def __init__(self, msg):
self.msg = msg
super(AsyncTimeoutException, self).__init__(msg) class HttpContext(object):
"""封装请求和相应的基本数据""" def __init__(self, sock, host, port, method, url, data, callback, timeout=5):
"""
sock: 请求的客户端socket对象
host: 请求的主机名
port: 请求的端口
method: 请求方式
url: 请求的URL
data: 请求时请求体中的数据
callback: 请求完成后的回调函数
timeout: 请求的超时时间
"""
self.sock = sock #sock: 请求的客户端socket对象
self.callback = callback #callback: 请求完成后的回调函数
self.host = host #host: 请求的主机名
self.port = port # port: 请求的端口
self.method = method #method: 请求方式
self.url = url #url: 请求的URL
self.data = data #data: 请求时请求体中的数据 self.timeout = timeout #timeout: 请求的超时时间 self.__start_time = time.time() #当前时间
self.__buffer = [] #在buffer中写入响应内容 def is_timeout(self):
"""当前请求是否已经超时"""
current_time = time.time()
if (self.__start_time + self.timeout) < current_time:
return True def fileno(self):
"""请求sockect对象的文件描述符,用于select监听"""
return self.sock.fileno() def write(self, data):
"""在buffer中写入响应内容"""
self.__buffer.append(data) def finish(self, exc=None):
"""在buffer中写入响应内容完成,执行请求的回调函数"""
if not exc:
response = b''.join(self.__buffer)
self.callback(self, response, exc)
else:
self.callback(self, None, exc) def send_request_data(self): #发送请求 伪造请求头 请求体
content = """%s %s HTTP/1.0\r\nHost: %s\r\n\r\n%s""" % (
# 请求方式 请求的URL 请求的主机名 请求时请求体中的数据
self.method.upper(), self.url, self.host, self.data,) return content.encode(encoding='utf8') class AsyncRequest(object):
def __init__(self):
self.fds = [] #用于存放 连接有返回值的请求
self.connections = []#用于存放需要连接的请求 def add_request(self, host, port, method, url, data, callback, timeout):
"""创建一个要请求"""
client = socket.socket()
client.setblocking(False)
try:
client.connect((host, port))
except BlockingIOError as e:
pass
# print('已经向远程发送连接的请求')
req = HttpContext(client, host, port, method, url, data, callback, timeout)
self.connections.append(req)
self.fds.append(req) def check_conn_timeout(self):
"""检查所有的请求,是否有已经连接超时,如果有则终止"""
timeout_list = [] #超时列表
for context in self.connections:
if context.is_timeout(): #进行超时检测 如果是超时
timeout_list.append(context) #加入超时列表
for context in timeout_list: #进行超时处理
context.finish(AsyncTimeoutException('请求超时'))
self.fds.remove(context) #进行移除 请求 待返回列表
self.connections.remove(context) #进行移除 请求 待发送列表 def running(self):
"""事件循环,用于检测请求的socket是否已经就绪,从而执行相关操作"""
while True:
if not self.fds: #如果没有请求 直接返回
return
r, w, e = select.select(self.fds, self.connections, self.fds, 0.05) #监测socket对象的变化 for context in r:
sock = context.sock #接收请求 连接
while True:
try:
data = sock.recv(8096)# 取返回值
if not data:#如果没有返回值
self.fds.remove(context) #移除等待返回值 的请求
context.finish()#完成请求
break
else:
context.write(data)
except BlockingIOError as e:
break
except TimeoutError as e: #如果超时,,移除 发送的请求和接收的请求 取消请求
self.fds.remove(context)
self.connections.remove(context)
context.finish(e)
break for context in w:
# 已经连接成功远程服务器,开始向远程发送请求数据
if context in self.fds:
data = context.send_request_data()#请求头 请求体
context.sock.sendall(data)#进行连接
self.connections.remove(context) #移除已经连接成功的请求 self.check_conn_timeout() #检测 是否超时 if __name__ == '__main__':
def callback_func(context, response, ex):
"""
:param context: HttpContext对象,内部封装了请求相关信息
:param response: 请求响应内容
:param ex: 是否出现异常(如果有异常则值为异常对象;否则值为None)
:return:
"""
print(context, response, ex) obj = AsyncRequest()
url_list = [
{'host': 'www.google.com', 'port': 80, 'method': 'GET', 'url': '/', 'data': '', 'timeout': 5,
'callback': callback_func},
{'host': 'www.baidu.com', 'port': 80, 'method': 'GET', 'url': '/', 'data': '', 'timeout': 5,
'callback': callback_func},
{'host': 'www.bing.com', 'port': 80, 'method': 'GET', 'url': '/', 'data': '', 'timeout': 5,
'callback': callback_func},
]
for item in url_list:
print(item)
obj.add_request(**item) obj.running()

相关文章