类似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\r\nhost:xxxx.com\r\n\r\n')
				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效果差不多,只是切换的角度不一样。协程是内部进行切换的;而基于事件循环异步非阻塞的模块是外人(以上帝视角)进行调配的;

猜你喜欢

转载自www.cnblogs.com/zenghui-python/p/11653385.html