**多线程,多进程(不建议):**可以为相关阻塞操作单独开启线程或进程,阻塞操作可以异步执行。但无法无限制地开启线程或进程。
**线程池、进程池(适当使用):**可以降低系统开启或者销毁一个线程或者进程的频率,从而很好的降低系统开销。但也不是无限制的。
单线程+异步协程(推荐):
event_loop:事件循环,相当于一个无限循环,可以吧一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。
coroutine:协程对象,我们可以讲协程对象注册到事件循环当中,他会被事件循环调用。可以使用 async 关键字来定义一个方法,当这个方法被调用时不会立即执行,而是返回一个协程对象。
task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
future:代表将来执行或者还未执行的任务,在本质上和task没有区别。
async:定义一个协程。
await:用来挂起阻塞方法的执行。
import asyncio
async def test_func(a):
print("start:",a)
print("end:",a)
# async修饰的函数,调用后返回一个协程(coroutine)对象
f = test_func(3)
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 将协程对象注册到loop中,然后启动loop
loop.run_until_complete(f)
下面task与future的使用分别对应两种创建任务的方法,可任选其一创建任务。
task的使用
# task的使用
loop = asyncio.get_event_loop()
task = loop.create_task(f)
print(task)
loop.run_until_complete(task)
print(task)
future的使用
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(f)
print(task)
loop.run_until_complete(task)
print(task)
绑定回调
# 绑定回调
def callback_func(task):
# result返回的就是任务对象封装的协程对象对应的函数的返回值
print(task.result())
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(f)
# 将回调函数绑定到任务对象中
task.add_done_callback(callback_func)
loop.run_until_complete(task)
import asyncio
import time
async def request(url):
print('正在下载:',url)
# 注意:在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步。
# time.sleep(2)
# 注意:需要实现异步协程的阻塞操作前面要加await关键字才能实现异步协程,需要收到手动挂起。
await asyncio.sleep(2)
print("下载成功!",url)
start = time.time()
urls = [
'www.baidu.com',
'www.sougou.com',
'www.douban.com'
]
tasks = []
for url in urls:
f = request(url)
task = asyncio.ensure_future(f)
tasks.append(task)
loop = asyncio.get_event_loop()
# 需要将任务列表tasks封装到async.wait中
loop.run_until_complete(asyncio.wait(tasks))
print("函数耗时:",time.time()-start)
结果一:(使用了同步模块的代码的情况)
正在下载: www.baidu.com
下载成功! www.baidu.com
正在下载: www.sougou.com
下载成功! www.sougou.com
正在下载: www.douban.com
下载成功! www.douban.com
函数耗时: 6.001343250274658
结果二:(异步协程实现结果)
正在下载: www.baidu.com
正在下载: www.sougou.com
正在下载: www.douban.com
下载成功! www.baidu.com
下载成功! www.sougou.com
下载成功! www.douban.com
函数耗时: 2.0021145343780518
注意:在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步。
注意:需要实现异步协程的阻塞操作前面要加await关键字才能实现异步协程,需要收到手动挂起。
使用aiohttp的原因:
使用async异步协程爬取网站时,用requests模块会导致不能实现异步爬取,原因是requests模块是同步模块,其中get、post等阻塞操作是同步方法,不能实现异步运行。
代码:
import aiohttp
import asyncio
import time
now = lambda : time.time()
start = now()
header = {
'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0'
}
url_list = [
'https://www.baidu.com',
'https://www.sougou.com/',
'https://www.douban.com/'
]
async def get_page(url):
print("开始爬取:",url)
# 使用aiohttp.ClientSession创建一个session对象
async with aiohttp.ClientSession() as session:
# 注意使用await挂起
async with await session.get(url,headers=header) as response:
# text()返回字符串形式的响应数据。
# read()返回二进制形式的数据。
# json()返回的是json对象。
# 注意上述都为方法,不是属性。
page_text = await response.text()
print(len(page_text))
print("爬取成功:",url)
tasks = []
for url in url_list:
c = get_page(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print("耗时:",now()-start)
结果:
开始爬取: https://www.baidu.com
开始爬取: https://www.sougou.com/
开始爬取: https://www.douban.com/
291324
爬取成功: https://www.baidu.com
15584
爬取成功: https://www.sougou.com/
81219
爬取成功: https://www.douban.com/
耗时: 1.2360711097717285
单线程+异步协程的效率非常高,但在以后的使用还需要多注意。本文的例子比较简单,真正实现高效率的异步协程的爬虫还需要多实战、多练习。