异步非阻塞爬虫框架的设计思路

 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()

 

posted @ 2019-03-07 13:01  冰底熊  阅读(293)  评论(0编辑  收藏  举报