异步编程&协程&asyncio

高性能异步爬虫

目的:在爬虫中使用异步实现高性能的数据爬取操作。

异步爬虫的方式:

多进程、多线程:
优点:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行。
缺点:无法无限制的开启多线程或者多进程。
进程池、线程池:
优点:降低系统对进程或者线程创建和销毁的一个频率,从而很好降低系统的开销。
缺点:池中线程或进程的数量是有上限的。

单线程+异步协程:

event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。
coroutine:协程对象,我们可以讲协程对象注册到事件循环中,它会被事件循环调用。可以使用async关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
task:任务。它是对协程对象的进一步封装,包含了任务的各个状态。
future:代表将来执行或还没有执行的任务,实际上和task没有本质区别。
async:定义一个协程。
await:用来挂起阻塞方法的执行。

协程
协程不是计算机提供,程序员人为创造。
协程可以被称为微线程,是一种用户态内的上下文切换技术,简而言之,其实就是通过一个线程实现代码块相互切换执行。例如:

def func1():
	print(1)
	……
	print(2)
	
def finc2():
	print(3)
	……
	print(4)

func1()
func2()

greenlet实现协程

from greenlet import greenlet


def func1():
    print(1)  # 第一步:输出 1
    gr2.switch()  # 第三步:切换到 func2 函数
    print(2)  # 第六步:输出 2
    gr2.switch()  # 第七步:切换到func2函数,从上一步执行的位置继续向后执行


def func2():
    print(3)
    gr1.switch()  # 第四步:输出 3
    print(4)  # 第五步:切换到func1 函数,从上一次执行的位置继续向后执行
    gr1.switch()  # 第八步:输出 4


gr1 = greenlet(func1)
gr2 = greenlet(func2)

gr1.switch()  # 第一步:去执行func1函数

yield关键字

def func1():
    yield 1
    yield from func2()
    yield 2


def func2():
    yield 3
    yield 4


f1 = func1()
for item in f1:
    print(item)

asyncio
python3.4及之后版本
遇到IO阻塞会自动切换

import asyncio


@asyncio.coroutine # 原本是个普通函数 加上coroutine可以理解为协程函数
def func1():
    print(1)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)


@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)


tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))# 可以同时执行两个协程函数

async & await关键字

import asyncio



async def func1():
    print(1)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)



async def func2():
    print(3)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)


tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))# 可以同时执行两个协程函数

普通方式下载图片

import requests


def download_image(url):
    print('开始下载:', url)
    # 发送网络请求,下载图片
    response = requests.get(url)
    print('下载完成')
    # 图片保存到本地文件
    file_name = url.rsplist('_')[-1]
    with open(file_name, mode='wb') as file_object:
        file_object.write(response.content)


if __name__ == "__main__":
    url_list = [
        'https://pic.netbian.com/uploads/allimg/220112/235701-16420030210840.jpg',
        'https://pic.netbian.com/uploads/allimg/220112/001807-16419178875bb3.jpg',
        'https://pic.netbian.com/uploads/allimg/220111/002539-16418319399169.jpg'
    ]
    for item in url_list:
        download_image(item)

通过协程下载图片

import asyncio

import aiohttp
import requests


async def fetch(session, url):
    print('发送请求:', url)
    async with session.get(url, verify_ssl=False) as response:
        content = await response.content.read()
        file_name = url.rsplist('_')[-1]
        with open(file_name, mode='wb') as file_object:
            file_object.write(content)
        print('下载完成', url)


async def main():
    async with aiohttp.ClientSession() as session:
        url_list = [
            'https://pic.netbian.com/uploads/allimg/220112/235701-16420030210840.jpg',
            'https://pic.netbian.com/uploads/allimg/220112/001807-16419178875bb3.jpg',
            'https://pic.netbian.com/uploads/allimg/220111/002539-16418319399169.jpg'
        ]
        tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
        await asyncio.wait(tasks)


if __name__ == "__main__":
    asyncio.run(main())

上一篇:Knight_day14


下一篇:【PAT】1020 Tree Traversals (25 分)