线程池,协程,多任务异步
基于flask的server断服务器
from flask import Flask, render_template
from time import sleep
app = Flask(__name__)
@app.route('/index')
def index():
sleep(2)
return render_template('test.html')
@app.route('/home')
def index1():
sleep(2)
return render_template('test.html')
@app.route('/login')
def index2():
sleep(2)
return render_template('test.html')
if __name__ == '__main__':
app.run(threaded=True) # 旧版本需要加上这个才能开启多线程
线程池
from multiprocessing.dummy import Pool
import requests
import time
from lxml import etree
start = time.time()
urls = [
'http://127.0.0.1:5000/index',
'http://127.0.0.1:5000/home',
'http://127.0.0.1:5000/login',
]
pool = Pool(3) # 开启3个线程
# 回调函数会异步处理列表中的每一个列表元素
def get_request(url):
page_text = requests.get(url).text
return page_text
page_text_list = pool.map(get_request, urls)
# 异步解析数据
def parse_text(page_text):
tree = etree.HTML(page_text)
tree.xpath('//*[@id="feng"]')
pool.map(parse_text, page_text_list)
print('总耗时', time.time()-start)
协程的基本操作
import asyncio
import time
async def get_request(url):
print('正在请求', url)
time.sleep(2)
print('请求结束', url)
return 'go go go go'
# 创建一个协程对象
c = get_request('www.1.com')
# 创建一个任务对象
task = asyncio.ensure_future(c)
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 将任务对象装载到事件循环对象这个容器中且启动事件循环
loop.run_until_complete(task)
任务对象的回调函数
import asyncio
import requests
import time
async def get_request(url):
print('正在请求', url)
time.sleep(2)
print('请求结束', url)
return 'go go go go'
def parse(task):
# result()返回的就是任务对象表示的特殊函数内部的返回值
result = task.result()
print(result)
c = get_request('www.1.com')
task = asyncio.ensure_future(c)
# 给任务对象绑定一个回调 目的是为了函数return获取返回值
# 该回调函数必须要有一个参数,这个参数表示的就是add_done_callback的调用者
# 回调函数一定是在任务对象完全执行结束后才会被调用执行
task.add_done_callback(parse)
loop = asyncio.get_event_loop()
loop.run_until_complete(task)
多任务协程
import asyncio
import requests
import time
start = time.time()
async def get_request(url):
print('正在请求', url)
# time.sleep(2)
await asyncio.sleep(2)
print('请求结束', url)
return 'go go go go'
urls = [
'http://127.0.0.1:5000/index',
'http://127.0.0.1:5000/home',
'http://127.0.0.1:5000/login',
]
tasks = []
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks)) # wait挂起
print('总耗时', time.time()-start)
# 上面代码出现的问题:
# 1.多任务的模式并没有实现真正的异步
# 2.wait()方法的作用
多任务的异步爬虫
import requests
import time
import asyncio
import aiohttp
from lxml import etree
start = time.time()
urls = [
'http://127.0.0.1:5000/index',
'http://127.0.0.1:5000/home',
'http://127.0.0.1:5000/login',
]
# async def get_request(url):
# # requests是一个不支持异步的模块
# page_text = await requests.get(url)
# return page_text
async def get_request(url):
async with aiohttp.ClientSession() as sess: #实例化了一个请求对象
async with await sess.get(url) as response: #get/post和requests的get/post用法几乎一致
page_text = await response.text() #byte类型的数据使用read()
return page_text
def parse(task):
page_text = task.result()
tree = etree.HTML(page_text)
tree.xpath('//a[@id="feng"]')
tasks = []
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
task.add_done_callback(parse)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print('总耗时', time.time()-start)
笔记
异步爬虫的实现方式
- 线程池
- 多任务的异步协程
- 多线程(生产者消费者模型)
协程asyncio
- 特殊的函数
- 如果一个函数的定义被async关键字装饰,则该函数就是一个特殊函数
- 特殊之处:
- 1.特殊函数调用后,函数内部的程序语句没有被立即执行
- 2.特殊函数调用后会返回一个协程对象
- 协程
- 对象,可以调用特殊函数返回一个协程。
- 协程 == 特殊的函数 == 一组指定形式的操作
- 协程 == 一组指定形式的操作
- 任务
- 对象. 任务对象其实就是一个高级的协程对象
- 任务对象 == 协程 == 一组指定形式的操作
- 任务 == 一组指定形式的操作
- 特殊的机制:
- 绑定一个回调函数
- 事件循环(核心)
- 对象asyncio.get_event_loop()
- 事件循环你可以暂且把它当做一个载体或者容器
- 将协程或者任务对象装载在事件循环这个容器中
- run_until_complete
- 开启事件循环
- async
- wait()
- 用来对tasks这个任务列表进行指定操作.
- wait可以将tasks任务列表中每一个任务对象一次赋予可挂起的权限
- 挂起:可以让当前被执行的任务对象交出cpu的使用权
- 当任务列表被wait方法修饰处理后,依然没有出现异步的效果
- 在特殊函数内部的实现语句中不可以出现不支持异步模块的代码,否则会中断异步效果
- await
- 是可以确保在异步的过程中,让阻塞操作可以执行完毕
支持异步的网络请求模块
- aiohttp
- pip install aiohttp
- 代码实现:
- 第一步:编写基本架构
async def get_request(url):
with aiohttp.ClientSession() as sess: #实例化了一个请求对象
with sess.get(url) as response: #get/post和requests的get/post用法几乎一致
page_text = response.text() #byte类型的数据使用read()
return page_text
- 第二步:补充细节
- 在每个with前加上async关键字
- 在每一步阻塞前加上await关键字
async def get_request(url):
async with aiohttp.ClientSession() as sess: #实例化了一个请求对象
async with await sess.get(url) as response: #get/post和requests的get/post用法几乎一致
page_text = await response.text() #byte类型的数据使用read()
return page_text
流程:
首先确保任务列表可以被挂起,然后依次执行,当执行任务列表里面的A任务时,遇到await asyncio.sleep(2),遇到阻塞,
将A任务对象挂起,交出CPU使用权,执行任务列表里面B任务对象获得使用权,遇到await asyncio.sleep(2),遇到阻塞,
将B任务对象挂起,交出CPU使用权,执行任务列表里面C任务对象获得使用权,遇到await asyncio.sleep(2),遇到阻塞,
将C任务对象挂起,交出CPU使用权,可能C还没执行完的时候,A任务对象开始从之前阻塞的地方往下执行。后面B.C同样
-------------------------------------------
个性签名:代码过万,键盘敲烂!!!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!