异步非阻塞爬虫框架的设计思路
1.socket 实现http 请求
1.阻塞情况
# 1 阻塞 client = socket.socket() client.connect(('14.215.177.39',80)) # 阻塞 , '14.215.177.39' 为百度ip 默认端口 data=b'GET / HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n' # 所有web框架都是通过 \r\n\r\n 进行分割 client.sendall(data) response=client.recv(8096) # # 阻塞 print(response) client.close()
2. 非阻塞情况
import time
client = socket.socket() client.setblocking(False) # 设置非阻塞 # 连接已经发送出去了 try: client.connect(('14.215.177.39',80)) # 非阻塞 , '14.215.177.39' 为百度ip 默认端口 except BlockingIOError as e: print(e) time.sleep(5) # 在等的过程可能连接成功 # 等待阶段 可以做其他事情 while True: # 计算或数据库取数据,取完 再去检查是否连接成功,,如果成功就退出循环继续往下走 pass # if 某个条件成立时就退出循环 比如所有的请求都返回时,就退出循环 break data=b'GET / HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n' # 所有web框架都是通过 \r\n\r\n进行分割 client.sendall(data) response=client.recv(8096) # # 非阻塞 print(response) client.close() # 总结: # 1. 非阻塞 会报错 用try # 2. 没连接成功或数据还没收到前,让线程去做其他的事 : 计算或去数据库取数据 可以用while # 定义一些操作
2.select 事件监听
IO多路复用,检测多个socket对象是否有变化? r,w,e =select.select([sk1,sk2,sk3,...100],[],[],0.05) # r ,是什么? 谁发生变化 可读,如果socket中返回内容了,表示可读,表示要收数据了 # w ,是什么?[sk2,sk3] ,连接成功了 for obj in w: obj.send('GET /HTTP/1.0 ') # r,是什么? [sk2,sk3],要收数据了 for obj in r: response=obj.recv(..) print(response)
3.结合socket的非阻塞和事件监听IO 多路复用 完成异步非阻塞模块的基本框架设计
思路步骤:
1. 思路先从 用户端入手
url_list=[
{'host':'www.baidu.com','port':80,'path':'/',"callback":do1},
{'host':'www.bing.com','port':80,'path':'/index.html',"callback":do3},]
]
2. 循环 url_list
# 实例化 对象
xiake = XiaKe()
for url in list:
# 思路 把url 做为参数 传给一个对象或方法 ,逻辑通过该对象或方法实现
# 这里用一个类封装好 ,处理逻辑 实例化该类, 在调用该类中的某个方法 ,把功能运行起来
# add_request(url) # 此处开始的思路是放一个函数, 但思路可以转变到,类中可以包含函数
xieke.add_request(url)
# 再通过类下某个方法启动:
xieke.run()
3. 该类的大致雏形:
import select import socket class XiaKe(): def __init__(self): self.sock_list=[] # 可以接收消息的对象 self.conns=[] # 已经连接 可发送消息的对象 def add_request(self,req_info): ''' 创建请求 :param req: {'host':'www.bing.com','port':80,'path':'/index.html'},] :return: ''' sock=socket.socket() sock.setblocking(False) # 设置为非阻塞 try: sock.connect((req_info['host'],req_info['post'])) except BlockingIOError as e: pass self.sock_list.append(sock) self.conns.append(sock) def run(self): ''' 进行事件监测 监听是否连接成功 :return: ''' # 监听 while True: r,w,e = select.select(self.sock_list,self.conns,[],0.05) # 连接成功发消息 for sock in w: # sock.send('GET /index.html http/1.0\r\nhost:\r\n\r\n') # 这里 sock.send(b'GET /index.html http/1.0\r\nhost:\r\n\r\n') # 信息发送完 从监测列表移除 self.conns.remove(sock) # 等待接收信息 for sock in r : response =sock.recv(8096) # 接收信息 或将接收后的信息 给外部的回调函数 # func=req_info['callback'] # 外部传过来的 通过字典 {'callback':callback} # func(response) # 此处可以对数据进行处理 self.sock_list.remove(sock) # 所有请求已经返回时,退出循环 if not self.sock_list: break
4. 对基本的类进行进一步的优化处理:
需要注意的是: select.select(rlist,wlist,[],0.05)
1.参数 : rlist,wlist 默认里面放的是sock 对象
2. 但其实只要rlist 和wlist 中的对象,含有fileno 方法就可以。
3. 所有改进思路: 重新 构建一个类,让该类中含有该fileno 方法,
class Request(object): def __init__(self,sock,re_info): # self.sock=sock self.info=re_info def fileno(self): return self.sock.fileno()
最后的异步非阻塞框架的 大致框为:
import socket import select class Request(object): def __init__(self,sock,re_info): self.sock=sock self.info=re_info def fileno(self): return self.sock.fileno() class XiaKe(object): def __init__(self): self.socket_list=[] self.conns=[] def add_request(self,req_info): ''' 创建请求 req_info:{'host':'www.baidu.com','port':80,'path':'/'}, :return: ''' sock =socket.socket() sock.setblocking(False) try: sock.connect((req_info["host"],req_info['port'])) except BlockingIOError as e: pass obj=Request(sock,req_info) # 此处本来是加socket 对象, 但只要对象中含有fileno 方法就可以,加入到select监听对象中。 self.socket_list.append(obj) self.conns.append(obj) def run(self): ''' 开始事件循环: 检查连接成功了吗,数据是否返回 :return: ''' while True: r,w,e =select.select(self.socket_list,self.conns,[],0.05) # r 可以接收消息 # w 连接成功后的对象 #循环连接成功的列表对象 w for obj in w: # obj 为request 对象 # obj.sock.send('GET /index.html http/1.0\r\nhost:\r\n\r\n') data='GET %s http/1.0\r\nhost:%s\r\n\r\n'%(obj.info["path"],obj.info['host']) obj.sock.send(data.encode('utf-8')) # 发消息 self.conns.remove(obj) # 一次请求一次响应 移除已经发连接成功发了数据的对象 # 接收消息 for obj in r: response=obj.sock.recv(8096) # 此处数据可能比较大需要进行处理,或用循环 # print(obj.info["host"],response) obj.info["callback"](response) # 移除接收完消息的对象 self.socket_list.remove(obj) # 所有请求的已经返回 if not self.socket_list: break
# 回调函数 用户处理返回结果的回调函数 def do1(response): print(response) def do2(response): print(response) def do3(response): print(response)
url_list=[ {'host':'www.baidu.com','port':80,'path':'/',"callback":do1}, # callback 函数可以是一个[c1,c2,] {'host':'www.conblogs.com','port':80,'path':'/',"callback":do2}, {'host':'www.bing.com','port':80,'path':'/index.html',"callback":do3},] xiake=XiaKe() for item in url_list: xiake.add_request(item) xiake.run()
有疑问可以加wx:18179641802,进行探讨