##1. 阻塞与非阻塞的应用程序的状态
request.get()
就绪和阻塞给用户的感觉就是卡,阻塞是指程序遇到了io操作,无法继续执行
对于io操作,可以使用多线程也可以使用多进程
io密集型 :可以使用多线程
计算密集型 :必须使用多进程
同步:指的是提交任务之后就在原地等待,必须执行完毕之后才能继续,效率低下
异步: 提交任务之后就不用管了,可以继续执行,效率较高
import requests
def parse_page(res):
print('解析 %s' %(len(res)))
def get_page(url):
print('下载 %s' %url)
response=requests.get(url)
if response.status_code == 200:
return response.text
urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']
for url in urls:
res=get_page(url) #调用一个任务,就在原地等待任务结束拿到结果后才继续往后执行
parse_page(res)
在服务器使用多线程或者多进程的时候,是让每一个链接都拥有独立的线程(进程),这样任何一个拦截的阻塞都不会影响其他链接(但是现场太多的话会对服务器的压力太大)
#IO密集型程序应该用多线程
import requests
from threading import Thread,current_thread
def parse_page(res):
print('%s 解析 %s' %(current_thread().getName(),len(res)))
def get_page(url,callback=parse_page):
print('%s 下载 %s' %(current_thread().getName(),url))
response=requests.get(url)
if response.status_code == 200:
callback(response.text)
if __name__ == '__main__':
urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']
list = []
for url in urls:
t=Thread(target=get_page,args=(url,))
t.start()
list.append(t)
# 等待全部线程结束之后才结束主线程
for i in list:
i.join()
解决方式:进程池或者线程池+回调机制
线程池:在减少创建的和销毁线程的频率,其维持在一定合理数量的线程,并让空闲的线程重新承担新的执行任务
链接池:维持链接的缓存池,尽量重用已有的连接,减少创建和关闭链接的频率
以上两种计数可以很好的降低系统的开销,都被广泛的应用
import requests
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def parse_page(res):
res = res.result()
print('%s 解析 %s' % (current_thread().getName(), len(res)))
def get_page(url):
print('%s 下载 %s' % (current_thread().getName(), url))
response = requests.get(url)
if response.status_code == 200:
return response.text
if __name__ == '__main__':
urls = [
'https://www.baidu.com/', 'http://www.sina.com.cn/', 'https://www.python.org'
]
pool = ThreadPoolExecutor(50)
for url in urls:
pool.submit(get_page, url).add_done_callback(parse_page)
# 等待线程池全部线程结束才结束主线程
pool.shutdown(wait=True)
改进之后依然存在的问题:
线程池和连接池技术也只是在一定程度上缓解了频繁调用Io带来的调用资源的问题,当请求的数量大大超过了线程的数量的时候,与没有线程池相比并没有很大的区别,所以要根据响应规模来调整线程池的大小
多线程模型可以方便高效的解决小规模的服务请求,但是面对大规模的服务请求,多线程模型也会遇到瓶颈,解决方式就是使用非阻塞io接口
**分析问题:**解决问题的的关键是我们从应用程序级别检测,当程序遇到io操作时,我们就切换到程序的别的操作,这样就最大程度的运用了cpu的效率,把程序的Io降到最低,这样操作系统会认为这个程序整体的Io较少的运行程序,cpu就最大限度的分配给我们
在python3.3之后新增了asyncio模块,可以帮我们自动检测io,实现应用程序的切换,原理同gevent一样,都是在遇到io时就切换一个任务(也就是协程)
详情操作:https://blog.csdn.net/qq_42737056/article/details/86645971
import asyncio
# 装饰器和下面的Io检测搭配使用
@asyncio.coroutine
def task(task_id, senconds):
print('%s is start' % task_id)
yield from asyncio.sleep(senconds) # yield from在每一个Io操作都加,这样就能检测到io切换任务的执行
print('%s is end' % task_id)
tasks = [task(task_id="任务1", senconds=3), task("任务2", 2), task(task_id="任务3", senconds=1)]
loop = asyncio.get_event_loop()
# 等待任务全部执行完在执行下面的代码
loop.run_until_complete(asyncio.wait(tasks))
print('任务全部执行完毕')
loop.close()
由于asycio只能发送tcp协议的请求,要是发送http协议的请求头得自己定义报头,所以就和requests进行分工操作
import asyncio
import requests
def get_page(func, *args):
print('GET: %s' % args[0])
loop = asyncio.get_event_loop()
future = loop.run_in_executor(None, func, *args)
response = yield from future
print(response.url, len(response.text))
return ('complete')
tasks = [
get_page(requests.get, 'https://www.python.org/doc'),
get_page(requests.get, 'https://www.cnblogs.com/linhaifeng'),
get_page(requests.get, 'https://www.openstack.org')
]
def run():
loop = asyncio.get_event_loop()
results = loop.run_until_complete(asyncio.gather(*tasks))
loop.close()
print('>>>', results)
if __name__ == '__main__':
run()
from gevent import monkey;monkey.patch_all()
import gevent
import requests
def get_page(url):
print('GET:%s' %url)
response=requests.get(url)
print(url,len(response.text))
return 1
#协程池
from gevent.pool import Pool
pool=Pool(2)
g1=pool.spawn(get_page,'https://www.python.org/doc')
g2=pool.spawn(get_page,'https://www.cnblogs.com/linhaifeng')
g3=pool.spawn(get_page,'https://www.openstack.org')
gevent.joinall([g1,g2,g3,])
print(g1.value,g2.value,g3.value) #拿到返回值
封装了request+gevent模块
#pip3 install grequests
import grequests
request_list=[
grequests.get('https://wwww.xxxx.org/doc1'),
grequests.get('https://www.cnblogs.com/linhaifeng'),
grequests.get('https://www.openstack.org')
]
# 执行并获取响应列表(处理异常)
def exception_handler(request, exception):
# print(request,exception)
print("%s Request failed" %request.url)
# 执行并获取响应列表
response_list = grequests.map(request_list, exception_handler=exception_handler)
print(response_list)
Twisted是用Python实现的基于事件驱动的网络引擎框架,Twisted支持许多常见的传输及应用层协议,包括TCP、UDP、HTTP、等。
注意,python3中,字符串必须转码成utf8的格式,否则无法发送。比如str("test").encode("utf8")
即可
from twisted.web.client import getPage, defer
from twisted.internet import reactor
def all_done(args):
# print(args)
reactor.stop()
def callback(res):
print(res)
return 1
defer_list = []
urls = [
'http://www.baidu.com',
'http://www.bing.com',
'https://www.python.org',
]
for url in urls:
obj = getPage(url.encode('utf-8'), )
obj.addCallback(callback)
defer_list.append(obj)
defer.DeferredList(defer_list).addBoth(all_done)
reactor.run()
Tornado是使用Python编写的一个强大的、可扩展的Web服务器。它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中。
from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPRequest
from tornado import ioloop
count=0
def handle_response(response):
"""
处理返回值内容(需要维护计数器,来停止IO循环),调用 ioloop.IOLoop.current().stop()
:param response:
:return:
"""
if response.error:
print("Error:", response.error)
else:
print(len(response.body))
global count
count-=1 #完成一次回调,计数减1
if count == 0:
ioloop.IOLoop.current().stop()
def func():
url_list = [
'http://www.baidu.com',
'http://www.bing.com',
]
global count
for url in url_list:
print(url)
http_client = AsyncHTTPClient()
http_client.fetch(HTTPRequest(url), handle_response)
count+=1 #计数加1
ioloop.IOLoop.current().add_callback(func)
ioloop.IOLoop.current().start()
twisted与tornado的区别
都是基于爬虫的服务器框架(技能写服务器,有能写客户端)
twisted:性能较强但是对cpu的占用也很强
tornado:性能先比较弱,对于cpu的占用也较弱