异步IO 协程 是写爬虫目前来说最好的方式.

比多线程和多进程都好. 开辟新的线程和进程是非常耗时的

讲讲我在使用python异步IO语法时踩过的坑

简单介绍异步IO的原理

以及利用最新语法糖实现异步IO的步骤,

然后给出实现异步的不同例子

网上找了很多python的asyncio示例.很多都是用

# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
loop.run_until_complete(hello())
loop.close()

通过create_future向里面添加task的方法来进行异步IO调用.

这种方法显然不是很好理解,在python3.7中 asyncio引入了新的语法糖

asyncio.run()
asyncio.create_task()
asyncio.gather()

下面通过实例具体分析asyncio异步的原理和使用方法

假设有一个异步操作, 它可以是爬虫的请求等待网页响应, 数据库的操作, 或者是定时任务. 不管如何, 我们都可以抽象成下面这个函数来表示

async def foo():
print('----start foo')
await asyncio.sleep(1)
print('----end foo') /*
预期想要的结果
----start foo
(等待一秒)
----end foo
*/

async是旧版本装饰器的语法糖

await是旧版本yield from 的语法糖

这个函数表示,先打印start foo 然后等待一秒, 然后再打印end foo

这个函数不能直接被执行. 它必须放在一个异步环境中才能执行. 这个异步环境独立在整个程序之外,可以把所有的异步环境打包成一个箱子, 看成是一个同步事件.

(异步环境是我自己创造的为了理解异步操作发明的词汇)

把这个函数装在这个异步环境里 异步环境的长度取决于环境里需要执行事件最长的那个函数

开启这个异步环境的标志是

asyncio.run(foo())

这条命令执行了之后,异步环境就被开启了. 需要主要的事, 同一线程同一时间只能开启一个异步环境. 换句话说, 在run函数里面的函数(本例中为bar())里面不能再包含run函数.

因此, 上例需要执行的话:

async def foo():
print('start foo')
await asyncio.sleep(1)
print('----end foo') if __name__ == '__main__':
asyncio.run(foo())

执行以下之后发现结果没问题

异步是为了处理IO密集型事件的.一个读取操作需要1秒, 另一个需要2秒, 如果并发执行,需要3秒,

def foo2():
print('----start foo')
time.sleep(1)
print('----end foo') def bar2():
print('----start bar')
time.sleep(2)
print('----end bar') if __name__ == '__main__':
foo2()
bar2() /*
预期输出:
----start foo
(等待1秒)
----end foo
----start bar
(等待2秒)
----end bar
*/

把上面的函数改写成异步之后

async def foo():
print('----start foo')
await asyncio.sleep(1)
print('----end foo') async def bar():
print('****start bar')
await asyncio.sleep(2)
print('****end bar') async def main():
await foo()
await bar() if __name__ == '__main__':
asyncio.run(main())

我们想要的结果是

----start foo

****start bar

(等待1秒)

----end foo

(等待1秒)

****end bar

但是运行上面的程序 结果却是

----start foo

(等待1秒)

----end foo

****start bar

(等待2秒)

****end bar

这是为什么呢

await表示 等待后面的异步函数操作完了之后, 执行下面的语句.

所以在在本例中,await foo 等待foo函数完全结束了之后, 再去执行

那么如何一起执行呢

基本的有两种方法

1.采用函数gather

官方文档中的解释是

awaitable asyncio.gather(*awsloop=Nonereturn_exceptions=False)

并发 运行 aws 序列中的 可等待对象

如果 aws 中的某个可等待对象为协程,它将自动作为一个任务加入日程。

如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。

如果 return_exceptions 为 False (默认),所引发的首个异常会立即传播给等待 gather() 的任务。aws 序列中的其他可等待对象 不会被取消 并将继续运行。

如果 return_exceptions 为 True,异常会和成功的结果一样处理,并聚合至结果列表。

如果 gather() 被取消,所有被提交 (尚未完成) 的可等待对象也会 被取消

如果 aws 序列中的任一 Task 或 Future 对象 被取消,它将被当作引发了 CancelledError 一样处理 -- 在此情况下 gather() 调用 不会 被取消。这是为了防止一个已提交的 Task/Future 被取消导致其他 Tasks/Future 也被取消。

因此代码就有了

async def foo():
print('----start foo')
await asyncio.sleep(1)
print('----end foo') async def bar():
print('****start bar')
await asyncio.sleep(2)
print('****end bar') async def main():
res = await asyncio.gather(foo(), bar())
print(res) if __name__ == '__main__':
asyncio.run(main())

返回值为函数的返回值列表 本例中为[None, None]

第二种方法 创建task

asyncio.create_task(coro)

将 coro 协程 打包为一个 Task 排入日程准备执行。返回 Task 对象。

该任务会在 get_running_loop() 返回的loop中执行,如果当前线程没有在运行的loop则会引发 RuntimeError

此函数 在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低层级的 asyncio.ensure_future() 函数。

async def foo():
print('----start foo')
await asyncio.sleep(1)
print('----end foo') async def bar():
print('****start bar')
await asyncio.sleep(2)
print('****end bar') async def main():
asyncio.create_task(foo())
asyncio.create_task(bar()) if __name__ == '__main__':
asyncio.run(main())

但是运行一下就会发现, 只输出了

----start foo
****start bar

这是因为,create_task函数只是把任务打包放进了队列,至于它们有没有运行完.  不管.

因此需要等待它们执行完毕.

最后的代码为

async def foo():
print('----start foo')
await asyncio.sleep(1)
print('----end foo') async def bar():
print('****start bar')
await asyncio.sleep(2)
print('****end bar') async def main():
task1 = asyncio.create_task(foo())
task2 = asyncio.create_task(bar()) await task1
await task2 if __name__ == '__main__':
asyncio.run(main())

如果有多个请求

async def foo():
print('----start foo')
await asyncio.sleep(1)
print('----end foo') async def main():
tasks = []
for i in range(10):
tasks.append(asyncio.create_task(foo()))
await asyncio.wait(tasks) if __name__ == '__main__':
asyncio.run(main())
async def foo():
print('----start foo')
await asyncio.sleep(1)
print('----end foo') async def bar():
print('****start bar')
await asyncio.sleep(2)
print('****end bar') async def main():
tasks = []
for i in range(10):
tasks.append(asyncio.create_task(foo()))
for j in range(10):
tasks.append(asyncio.create_task(bar()))
await asyncio.wait(tasks) if __name__ == '__main__':
asyncio.run(main())

异步嵌套

async def foo():
print('----start foo')
await asyncio.sleep(1)
print('----end foo') async def bar():
print('****start bar')
await asyncio.sleep(2)
print('****end bar') async def foos():
print('----------------------')
tasks = []
for i in range(3):
tasks.append(asyncio.create_task(foo()))
await asyncio.wait(tasks) async def main():
tasks = []
for i in range(3):
tasks.append(asyncio.create_task(foos()))
for j in range(3):
tasks.append(asyncio.create_task(bar()))
await asyncio.wait(tasks) if __name__ == '__main__':
asyncio.run(main())

把每一个create_task当成新增了一条线. 这条线如果遇到IO操作了(即遇到了await) 那么就先等待在这里, 先执行别的线上的操作(如果已经有了结果)

create了线才可以跳来跳去, 如果不create, 是不会跳走的

async def foo():
print('----start foo')
await asyncio.sleep(1)
print('----end foo') async def foos():
print('----------------------')
tasks = []
await foo()
await foo()
await foo() async def main():
tasks = []
for i in range(3):
tasks.append(asyncio.create_task(foos()))
await asyncio.wait(tasks) if __name__ == '__main__':
asyncio.run(main())

这个例子里面 只创造了3条线, 因此只能3个3个执行, 其实应该9个一起等, 但是因为没有create_task所以并不会一起执行.

import asyncio
import aiohttp async def fetch(session, url, sem):
timeout = aiohttp.ClientTimeout(total=2)
try:
async with sem:
print(f'start get: {url}')
async with session.get(url, timeout=timeout) as response:
res = await response.text()
print(f'get {url} successfully')
except:
print('timeout') async def main():
url_list = [
# 'https://www.google.com.hk/',
'https://www.cnblogs.com/DjangoBlog/p/5783125.html',
'http://www.360doc.com/content/18/0614/19/3175779_762447601.shtml',
'https://www.baidu.com/',
]
url_list2 = ['http://es6.ruanyifeng.com/#docs/decorator' for _ in range(100)]
url_list3 = ['https://www.baidu.com' for _ in range(100)] # async with aiohttp.ClientSession() as session:
# tasks = []
# sem = asyncio.Semaphore(20)
# for url in url_list3:
# tasks.append(fetch(session, url, sem))
# await asyncio.gather(*tasks) async with aiohttp.ClientSession() as session:
sem = asyncio.Semaphore(20)
url = 'https://www.baidu.com'
task_list = [fetch(session, url, sem) for _ in range(100)]
await asyncio.gather(*task_list) if __name__ == '__main__':
asyncio.run(main())

被注释掉的代码和下面的代码干的是同样的事

Semaphore是一个计数器 超过容量的时候会阻塞. 可以限制并发数量

python3.7中asyncio的具体实现的更多相关文章

  1. 【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制

    转载自http://xidui.github.io/2015/10/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3python3-4-Asyncio%E5%BA%93% ...

  2. Python3.x中bytes类型和str类型深入分析

    Python 3最重要的新特性之一是对字符串和二进制数据流做了明确的区分.文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示.Python 3不会以任意隐式的方式混用str和b ...

  3. python3.X中的循环

    获取数字范围: range() 在python3.x中使用range(): >>> list(range(7)) [0, 1, 2, 3, 4, 5, 6] >>> ...

  4. python学习记录 - python3.x中如何实现print不换行

    python3.x中如何实现print不换行   大家应该知道python中print之后是默认换行的, 那如何我们不想换行,且不想讲输出内容用一个print函数输出时,就需要改变print默认换行的 ...

  5. Python3.5中安装Scrapy包时出现问题

    在Python3.5中安装Scrapy第三方库 pip install Scrapy 安装到后面出现的这类错误: error: Microsoft Visual C++ 14.0 is require ...

  6. python3.X中简单错误处理,和Python2区别

    1.print 1.1 Print是一个函数 在Python3中print是个函数,这意味着在使用的时候必须带上小括号,并且它是带有参数的. >>> print 'hello wor ...

  7. Python2.X和Python3.X中的urllib区别

    Urllib是Python提供的一个用于操作URL的模块,在Python2.X中,有Urllib库,也有Urllib2库,在Python3.X中Urllib2合并到了Urllib中,我们爬取网页的时候 ...

  8. 在python3.5中pip安装scrapy,遇到 error: Microsoft Visual C++ 14.0 is required

    本来在python3.5中安装scrapy一路顺畅(pip install scrapy),中间遇到一个 error: Microsoft Visual C++ 14.0 is required. x ...

  9. python3.4中自定义wsgi函数,make_server函数报错问题

    别的不多说,先上代码 #coding:utf-8 from wsgiref.simple_server import make_server def RunServer(environ, start_ ...

随机推荐

  1. ElasticSearch6.1.1集群搭建

    其实早就想研究ES了,因为之前用solr,资料较少(这倒不是问题,有问题去官网读文档),貌似用的人比较少?(别打我)前几天去京东面试,我觉得有必要了解一下es,昨天晚上简单了解了官方文档,今天居然鼓捣 ...

  2. Python 的内置函数

    函数 功能 示例 示例结果 abs(x) 返回x的绝对值 abs(-2) 2 chr(x) 返回整数x所代表的字符 chr(65) A divmod(x,y) 返回x除以y的商和余数的元组 divmo ...

  3. FFT笔记

    蝴蝶操作和Rader排序 蝴蝶操作的定义: 雷德(Rader)算法 (Gold Rader bit reversal algorithm) 按自然顺序排列的二进制数,其下面一个数总是比其上面一个数大1 ...

  4. (二叉树 BFS DFS) leetcode 104. Maximum Depth of Binary Tree

    Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the long ...

  5. Harbor作为Docker的镜像中心

    转载于网络 我们采用Harbor作为Docker的镜像中心. 有几个原因: Harbor采用Docker Compose拉起维护,简单方便. 采用Nginx作为入口网关,各种参数配置相对熟悉. 基于N ...

  6. MySQL mysqldump 导入/导出 结构&数据&存储过程&函数&事件&触发器

    ———————————————-库操作———————————————-1.①导出一个库结构 mysqldump -d dbname -u root -p > xxx.sql ②导出多个库结构 m ...

  7. Java引用类型传递整理

    引用数据类型(类) 10.1引用数据类型分类 可以把类的类型为两种: Java为我们提供好的类,如Scanner类,Random类等,这些已存在的类中包含了很多的方法与属性,可供我们使用. 我们自己创 ...

  8. 集成学习算法汇总----Boosting和Bagging(推荐AAA)

     sklearn实战-乳腺癌细胞数据挖掘(博主亲自录制视频) https://study.163.com/course/introduction.htm?courseId=1005269003& ...

  9. 网络编程基础【day09】:socketserver进阶(十)

    本节内容 1.概述 2.多用户并发 3.socketserver.BaseServer 一.概述 之前上一篇写的 day8-socketserver使用 讲解了socketsever如何使用,但是在最 ...

  10. java io系列09之 FileDescriptor总结

    本章对FileDescriptor进行介绍 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_09.html FileDescriptor 介绍 Fil ...