本文共 2259 字,大约阅读时间需要 7 分钟。
在编写爬虫脚本时,经常会遇到性能瓶颈问题。传统的 requests + 多线程/多进程 方法虽然能够提高并发度,但由于其阻塞性,往往导致资源消耗过大,尤其是在等待网页响应和处理数据时。为了应对这一问题,Python 3.4 及及以上版本引入了异步 IO 编程模型,通过 async 和 await 关键字实现非阻塞式操作。
在理解 async 和 await 之前,首先需要明确阻塞调用和非阻塞调用之间的区别:
在 Python 中,async 和 await 是实现异步 IO 的关键工具。从 Python 3.5 开始,async 和 await 取代了之前的 @asyncio.coroutine 和 yield from,使得代码更加直观。
以下是官方示例代码,用于说明 async 和 await 的执行顺序:
import asyncioasync def compute(x, y): print("Compute %s + %s ..." % (x, y)) await asyncio.sleep(1.0) # 非阻塞式睡眠 return x + yasync def print_sum(x, y): result = await compute(x, y) print("%s + %s = %s" % (x, y, result))loop = asyncio.get_event_loop()loop.run_until_complete(print_sum(1, 2))loop.close() 如果没有使用 async 和 await,程序的执行流程是:
print_sum(1, 2)。compute(1, 2)。print("Compute 1 + 2 ...")。3。"1 + 2 = 3"。使用 async 和 await 后,执行流程如下:
print_sum(1, 2)。compute(1, 2),即同时启动 compute。print("Compute 1 + 2 ...") 并等待 1 秒。3。print("1 + 2 = 3")。在异步编程中,事件循环(event loop)扮演着核心角色。它负责管理和调度任务(tasks),确保每个任务按正确顺序执行。
event loop:负责检测和处理 I/O 事件。tasks:异步操作的执行单元,由 asyncio.Future 和 asyncio.Task 实现。在上述代码中,get_event_loop() 获取事件循环,run_until_complete() 方法运行指定的协程对象,并返回控制权。
为了更直观地理解异步执行顺序,可以稍作修改:
import asyncioasync def compute(x, y): print("Compute %s + %s ..." % (x, y)) await asyncio.sleep(10.0) # 非阻塞式睡眠 return x + yasync def print_sum(x, y): result = await compute(x, y) print("%s + %s = %s" % (x, y, result))loop = asyncio.get_event_loop()tasks = [print_sum(1, 2), print_sum(3, 4)]loop.run_until_complete(asyncio.wait(tasks))loop.close() 在此优化后的代码中,sleep(10.0) 被修改为更长的睡眠时间,以更直观地观察任务执行顺序:
print_sum(3, 4):首先提交任务,立即返回,开始计算 3 + 4。sleep(10.0):非阻塞式执行,等待 10 秒。print_sum(1, 2):立即提交任务,开始计算 1 + 2。sleep(10.0):非阻塞式执行,等待 10 秒。最终结果显示:
Compute 3 + 4 ...Compute 1 + 2 ...暂停 10 秒左右3 + 4 = 71 + 2 = 3
从执行结果可以看出,事件循环会先完成所有已提交的任务,并等待所有任务完成后才继续后续操作。
通过以上示例,可以清晰地看到 async 和 await 在异步编程中的重要作用。它们允许在不阻塞主线程的情况下,非顺序地执行任务,显著提升了爬虫等 I/O 驱动型任务的性能表现。理解事件循环和任务的执行机制,是掌握异步编程的关键。
如果需要更深入的理解,可以参考相关的技术文档或博客。例如,了解 asyncio.Future 的实现机制,以及如何在实际应用中高效地利用异步编程。
转载地址:http://jvafk.baihongyu.com/