前言
今天这篇属于 asyncio 的应用篇,asyncio 的应用包括 web 服务端、爬虫应用、数据库连接库、分布式任务队列等领域。这里我们重点讲的是爬虫领域,使用的模块是 aiohttp。同样的和前面的教程一样,这里我们使用的 Python 版本同样为 3.8。
关于 aiohttp
Python 标准库的 asyncio 模块,内部实现了对 TCP、UDP、SSL 协议的异步操作方式,但是没有直接提供 HTTP 的异步操作方式。所以如果需要使用 HTTP 协议,我们就需要用到一个第三方的模块 aiohttp。
aiohttp 是一个基于 asyncio 的异步 http 网络模块分为了客户端和服务端,同时支持 websocket 的使用。在写爬虫时提供异步网络请求,而我们常用到的 requests 库是同步库,它会阻塞住 asyncio 所以不能在异步的环境中直接使用(这里不是说不能用,而是不能像写同步代码一样用它。我们需要用 run_in_executor
来运行,因为这属于使用额外线程的操作,开销上也是不小,所以不推荐大家使用)。
aiohttp 分为服务端和客户端,因为我们主要写爬虫程序所以我们重点说的是客户端的开发。
requests 和 requests+线程池以及使用 aiohttp 对比
首先,我们看一下使用 requests 进行循环访问、requests+线程池 、以及使用 aiohttp 这三种方式访问网址 2000 次返回最终结果的所用时间。 首先我们我通过 aiohttp 创建了一个本地的服务,地址为 127.0.0.1:5000。 先不要关注代码细节 代码如下:
from aiohttp import web
async def home(request: web.Request) -> web.Response:
return web.Response(text="Hi")
async def init_app() -> web.Application:
app = web.Application()
app.add_routes([web.get("/", home)])
return app
web.run_app(init_app(), port=5000)
之后打开我们的爬虫端分别测试上面三种结果的访问 2000 次的时间 测试代码如下
import requests
import timeit
from concurrent.futures import ThreadPoolExecutor
import aiohttp
import asyncio
session = requests.session()
url = "http://127.0.0.1:5000"
Count = 2000
def req(url: str):
req = requests.get(url)
req.status_code
def requests_test():
"""
第一组:循环的方式
:return:
"""
for i in range(Count):
req(url)
def pool_requests_test():
"""
第二组:线程池的方式
:return:
"""
url_list = [url for _ in range(Count)]
with ThreadPoolExecutor(max_workers=20) as pool:
pool.map(req, url_list)
async def fetch(url: str):
async with aiohttp.TCPConnector(ssl=False) as tc:
async with aiohttp.ClientSession(connector=tc) as session:
async with session.get(url) as req:
req.status
async def start():
tasks = [asyncio.create_task(fetch(url)) for _ in range(Count)]
await asyncio.wait(tasks)
def aiohttp_test():
"""
第三组:aiohttp 的方式
:param url:
:return:
"""
asyncio.run(start())
if __name__ == '__main__':
# 循环的
print(timeit.timeit(stmt=requests_test, number=1))
# 使用线程池的
print(timeit.timeit(stmt=pool_requests_test, number=1))
# 使用 aiohttp 的
# print(timeit.timeit(stmt=aiohttp_test, number=1))
测试了一共三次得到的结果如下: 第一次
3.08348508
2.1871939129999998
1.154268153
第二次
3.0424138099999998
2.395021737
1.315619199
第三次
2.953041779
2.242499116
1.14594682
很明显使用 aiohttp 的程序访问 2000 次时用时最短的。