线程池,协程,多任务异步

基于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同样

posted @ 2020-11-24 00:40  我在路上回头看  阅读(245)  评论(0编辑  收藏  举报