• 类似IO(爬虫)高性能相关


    问题:我给你10个图片的url,你帮我去把10张图片下载。
    以前的你

      

    urls = ['http://www.xx1.png','http://www.xx1.png','http://www.xx10.png',]
    			for url in urls:
    				response = requests.get(url)
    				with open(url+'.png','wb') as f:
    					f.write(response.content)
    

      

    上面这种形式可以实现任务,但是效率是非常低的,如果每一个url的io时间为2s,这样就要花费6s,这样不是高效的

    下面有几种方案可以实现高性能

    1.多线程:

      缺点: 线程的利用率不高,每个线程访问一个url以后就闲置了。

    """
    多线程
    
    """
    import requests
    import threading
    
    
    urls = [
        'http://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://www.cnblogs.com/news/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',
    ]
    
    def task(url):
        response = requests.get(url)
        print(response)
    
    for url in urls:
        t = threading.Thread(target=task,args=(url,))
        t.start()
    

    2.协程:

      因为协程遇到io的时候可以进行切换(内部进行切换),提高效率

      

    """
    协程+IO切换
    pip3 install gevent
    gevent内部调用greenlet(实现了协程)。
    """
    from gevent import monkey; monkey.patch_all()
    import gevent
    import requests
    
    
    def func(url):
        response = requests.get(url)
        print(response)
    
    urls = [
        'http://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://www.cnblogs.com/news/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',
    ]
    spawn_list = []
    for url in urls:
        #在这里没有发送请求,只是都放在一个list中。
        spawn_list.append(gevent.spawn(func, url))
    
    #发起请求;遇到io就进行切换
    gevent.joinall(spawn_list)
    

    3.基于事件循环异步非阻塞的模块(该模块内部利用了IO多路复用。)Twisted

    """
    基于事件循环的异步非阻塞模块:Twisted
    """
    from twisted.web.client import getPage, defer
    from twisted.internet import reactor
    
    def stop_loop(arg):
        reactor.stop()
    
    
    def get_response(contents):
        print(contents)
    
    deferred_list = []
    
    url_list = [
        'http://www.baidu.com/',
        'https://www.cnblogs.com/',
        'https://www.cnblogs.com/news/',
        'https://cn.bing.com/',
        'https://stackoverflow.com/',
    ]
    
    for url in url_list:
        deferred = getPage(bytes(url, encoding='utf8'))
        deferred.addCallback(get_response)
        deferred_list.append(deferred)
    
    
    dlist = defer.DeferredList(deferred_list)
    dlist.addBoth(stop_loop)
    
    reactor.run()
    

     

    问题:基于事件循环的异步非阻塞模块内部是如何实现?利用了IO多路复用。
    - 爬虫本质上就是写socket
    - 非阻塞的好处? 发请求的时候不再等待了。

    内部实现的机制:IO多路复用。

    import socket
    import select
    
    
    class ChunSheng(object):
    
    	def __init__(self):
    		self.socket_list = []
    		self.conn_list = []
    
    		self.conn_func_dict = {}
    
    	# url_func[0] url; url_func--(url,func_url)
    	def add_request(self, url_func):
    		conn = socket.socket()
    		conn.setblocking(False)
    		try:
    			conn.connect((url_func[0], 80))
    		except BlockingIOError as e:
    			pass
    		self.conn_func_dict[conn] = url_func[1]
    
    		self.socket_list.append(conn)
    		self.conn_list.append(conn)
    
    	def run(self):
    		"""
    		检测self.socket_list中的5个socket对象是否连接成功
    		:return:
    		"""
    		while True:
    			#   select.select
    			#   第一个参数: 用于检测其中socket是否已经获取到响应内容
    			#   第二个参数: 用于检测其中socket是否已经连接成功
    
    			# 第一个返回值 r:具体是那一个socket获取到结果
    			# 第二个返回值 w:具体是那一个socket连接成功
    			r, w, e = select.select(self.socket_list, self.conn_list, [], 0.05)
    			for sock in w:  # [socket1,socket2]
    				sock.send(b'GET / http1.1
    host:xxxx.com
    
    ')
    				self.conn_list.remove(sock)
    
    			for sock in r:
    				data = sock.recv(8096)
    				func = self.conn_func_dict[sock]
    				func(data)
    				sock.close()
    				self.socket_list.remove(sock)
    
    			if not self.socket_list:
    				break
    

      

    什么是异步?
      起始就是回调,当一个任务完成后自动执行某个函数。
      我们接触:
      - 爬虫:下载完成后自动执行回调函数
      - ajax:向后台发送请求,请求完成后执行回调函数。
    - 什么是非阻塞?
      其实就是不等待,socket中如何设置setblocing(False)那么socket就不再阻塞。
    - IO多路复用的作用?
      监听socket的状态:
        - 是否连接成功
        - 是否获取结果
    IO多路复用的实现:
        - select,只能监听1024个socket;内部会循环所有的socket去检测;
        - poll,无个数限制,内部会循环所有的socket去检测;
        - epoll,无个数限制,回调。

    方案2和3效果差不多,只是切换的角度不一样。协程是内部进行切换的;而基于事件循环异步非阻塞的模块是外人(以上帝视角)进行调配的;

  • 相关阅读:
    Java进阶之并发初探
    Java进阶之HashMap剖析
    Java进阶之反射
    Linux常用命令
    海量数据处理算法与面试题
    一些刷题时总结的重要知识点
    一些Java刷题时的小知识点
    九章算法知识点与题目总结(不定时更新...)
    c++设计模式之状态模式
    c++设计模式之抽象工厂模式
  • 原文地址:https://www.cnblogs.com/zenghui-python/p/11653385.html
Copyright © 2020-2023  润新知