Python Socket请求网站获取数据

 Python Socket请求网站获取数据

 

---阻塞 I/O           ->收快递,快递如果不到,就干不了其他的活

 

---非阻塞I/0       ->收快递,不断的去问,有没有送到,有没有送到,...如果送到了就接收

 

---I/O多路复用      ->找个代理人(select), 去收快递。快递到了,就通知用户.  

一 . 阻塞方式 

blocking IO 会一直block 对应的进程,直到操作完成

# 客户端请求网站-阻塞实现(一次一次的请求)
import socket
import time

# 访问网站
ACCESS_URL = 'www.baidu.com'
# 端口
ACCESS_PORT = 80


# socket阻塞请求网站
def blocking(pn):
    sock = socket.socket()
    sock.connect((ACCESS_URL, ACCESS_PORT))  # 连接网站 ,发出一个HTTP请求
    request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn))
    sock.send(request_url.encode())
    response = b''
    chunk = sock.recv(1024)
    while chunk:  # 循环接收数据,因为一次接收不完整
        response += chunk
        chunk = sock.recv(1024)
    # print(response.decode())
    return response


def block_way():
    for i in range(5):
        blocking(i)


if __name__ == '__main__':
    start = time.time()
    block_way()
    print('请求5次页面耗时{}'.format(time.time() - start))
    """
    请求5次页面耗时2.4048924446105957
    """

 

二. 非阻塞方式

non-blcoking在kernel还没准备好数据的情况下,会立即返回(会抛出异常)

# 客户端请求网站-非阻塞实现
import socket
import time

# 访问网站
ACCESS_URL = 'www.baidu.com'
# 端口
ACCESS_PORT = 80


# socket非阻塞请求网站(时间消耗在不断的while循环中,和阻塞的时间差不多)
def blocking(pn):
    sock = socket.socket()
    sock.setblocking(False)  # 设置为非阻塞
    try:
        # connect连接需要一定时间,非阻塞发送完请求,立即返回,如果没有数据会报异常
        sock.connect((ACCESS_URL, ACCESS_PORT))  # 连接网站 ,发出一个HTTP请求
    except BlockingIOError:  # 非阻塞套接字捕获异常
        pass
    request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn))
    while True:  # 不断发送http请求
        try:
            sock.send(request_url.encode())
            break
        except OSError:
            pass
    response = b''
    while True:  # 不断地接收数据
        try:
            chunk = sock.recv(1024)  # 没有数据返回时会收到异常
            while chunk:  # 循环接收数据,因为一次接收不完整
                response += chunk
                chunk = sock.recv(1024)
            break
        except BlockingIOError:  # 处理非阻塞异常
            pass
    # print(response.decode())
    return response


def block_way():
    for i in range(5):
        blocking(i)


if __name__ == '__main__':
    start = time.time()
    block_way()
    print('请求5次页面耗时{}'.format(time.time() - start))
    """
    请求5次页面耗时2.681565046310425
    时间消耗在不断的while循环中,和阻塞的时间差不多
    """

 

三. 多线程方式

# 客户端请求网站-线程池实现
import socket
from multiprocessing.pool import ThreadPool
import time

# 访问网站
ACCESS_URL = 'www.baidu.com'
# 端口
ACCESS_PORT = 80


def blocking(pn):
    sock = socket.socket()
    sock.connect((ACCESS_URL, ACCESS_PORT))  # 连接网站 ,发出一个HTTP请求
    request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn))
    sock.send(request_url.encode())
    response = b''
    chunk = sock.recv(1024)
    while chunk:  # 循环接收数据,因为一次接收不完整
        response += chunk
        chunk = sock.recv(1024)
    # print(response.decode())
    return response


def block_way():
    pool = ThreadPool(5)
    for i in range(10):
        pool.apply_async(blocking, args=(i,))
    pool.close()  # close()执行后不会有新的进程加入到pool
    pool.join()  # join函数等待子进程结束



if __name__ == '__main__':
    start = time.time()
    block_way()
    print('请求10次页面耗时{}'.format(time.time() - start))
    """
    请求10次页面耗时1.1231656074523926 
    """

 

四. 多进程方式

# 客户端请求网站-进程池实现
import socket
from multiprocessing import Pool
import time

# 访问网站
ACCESS_URL = 'www.baidu.com'
# 端口
ACCESS_PORT = 80


def blocking(pn):
    """
    发送请求,接收数据
    :param pn: 
    :return: 
    """
    sock = socket.socket()
    sock.connect((ACCESS_URL, ACCESS_PORT))  # 连接网站 ,发出一个HTTP请求
    request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn))
    sock.send(request_url.encode())
    response = b''
    chunk = sock.recv(1024)
    while chunk:  # 循环接收数据,因为一次接收不完整
        response += chunk
        chunk = sock.recv(1024)
    # print(response.decode())
    return response


def block_way():
    pool = Pool(5)
    for i in range(10):
        pool.apply_async(blocking, args=(i,))
    pool.close()  # close()执行后不会有新的进程加入到pool
    pool.join()  # join函数等待子进程结束



if __name__ == '__main__':
    start = time.time()
    block_way()
    print('请求10次页面耗时{}'.format(time.time() - start))
    """
    请求10次页面耗时1.1685676574707031 略慢于线程池实现方式,因为进程相对于线程开销比较大
    """

 

五. 协程方式

# 客户端请求网站-协程实现
from gevent import monkey;monkey.patch_all()  # 加补丁,实现非阻塞
import socket
import gevent
import time

# 访问网站
ACCESS_URL = 'www.baidu.com'
# 端口
ACCESS_PORT = 80


def blocking(pn):
    """
    发送请求,接收数据
    :param pn: 
    :return: 
    """
    sock = socket.socket()
    sock.connect((ACCESS_URL, ACCESS_PORT))  # 连接网站 ,发出一个HTTP请求
    request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn))
    sock.send(request_url.encode())
    response = b''
    chunk = sock.recv(1024)
    while chunk:  # 循环接收数据,因为一次接收不完整
        response += chunk
        chunk = sock.recv(1024)
    # print(response.decode())
    return response


def block_way():
    tasks = [gevent.spawn(blocking,i) for i in range (10)] #启动协程
    gevent.joinall(tasks)  # 阻塞等待所有操作都执行完毕


if __name__ == '__main__':
    start = time.time()
    block_way()
    print('请求10次页面耗时{}'.format(time.time() - start))
    """
    请求10次页面耗时0.6231002807617188 效率高于线程方式,协程相当于单线程并发(微线程),开销小于线程
    """

六. IO多路复用

I/O多路复用就是通过一种机制,操作系统通过一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

# 客户端请求网站-I/O多路复用
import socket
import selectors
import time

# 访问网站
ACCESS_URL = 'www.baidu.com'
# 端口
ACCESS_PORT = 80

sel = selectors.DefaultSelector()
flag = False  # 结束标志
num_list = [0] * 5


class Crawler(object):
    def __init__(self,pn):
        self.pn = pn
        self.response = b''


    def req_connect(self):
        """
        请求建立连接
        :return:
        """
        sock = socket.socket()
        sock.setblocking(False)
        try:
            sock.connect((ACCESS_URL, ACCESS_PORT))  # 连接网站 ,发出一个HTTP请求
        except BlockingIOError:  # 非阻塞套接字捕获异常
            pass
        sel.register(sock,selectors.EVENT_WRITE, self.conn_send)  # 监听socket,向服务端发送数据WRITE


    def conn_send(self,sock):
        """
        发送请求数据
        :param sock:
        :return:
        """
        # 取消上面注册监听的套接字
        sel.unregister(sock)
        request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(self.pn))
        sock.send(request_url.encode())  # 当我们发送数据之后,就等接收数据
        sel.register(sock,selectors.EVENT_READ, self.read)

    def read(self, sock):
        global flag
        chunk = sock.recv(1024)
        if chunk:
            self.response += chunk
        else:
            # 没有数据可读
            print(self.response)
            sel.unregister(sock)
            num_list.pop()
            if not num_list:
                flag = True


# 事件循环
def loop():
    while not flag:
        events = sel.select() # 监听发生了变化的套接字
        for key, mask in events:
            callback = key.data
            callback(key.fileobj)


if __name__ == '__main__':
    start = time.time()
    # print(num_list)
    for i in range(5):
        crawler = Crawler(i)  # 实例
        crawler.req_connect()
    loop()
    print('请求5次页面耗时{}'.format(time.time() - start))
    """
    请求5次页面耗时0.5865745544433594 多路复用非阻塞效率要高于非阻塞方式
    """

 

posted @ 2018-03-30 00:48 一只小小的寄居蟹 阅读(...) 评论(...) 编辑 收藏