网络知识 娱乐 大型fastapi项目实战 高并发请求神器之aiohttp(下)

大型fastapi项目实战 高并发请求神器之aiohttp(下)

  • 大型fastapi项目实战 高并发请求神器之aiohttp(下)
    • 1. 上节代码简单解释
    • 2. aiohttp 性能测试
    • 3. 解决 aiohttp 不支持 HTTPS 代理
    • 总结

大型fastapi项目实战 高并发请求神器之aiohttp(下)

1. 上节代码简单解释

基于上节给出的代码实例,直接使用代码就可以工作,本节我们注解一下核心代码

# -*- encoding: utf-8 -*-

import asyncio
import aiohttp


async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get('http://www.baidu.com') as resp:
            print(resp.status)
            res = await resp.text()
            print(res[:100])


if __name__ == '__main__':
    # 注意:
    # python3.7+ 支持写法
    # asyncio.run(main())
    # python3.6及以下版本写法
    event_loop = asyncio.get_event_loop()
    result = event_loop.run_until_complete(asyncio.gather(main()))
    event_loop.close()

1.先通过 event_loop = asyncio.get_event_loop() 创建了一个事件循环 2.通过 asyncio.gather 接受多个 future 或 coro 组成的列表 任务 3.通过 event_loop.run_until_complete(task) 我们 就开启 事件循环 直到这个任务执行结束。 4.async with aiohttp.ClientSession() as session: 是创建了一个异步的网络请求的上线文管理具柄 5.async with session.get('http://www.baidu.com') as resp: 异步请求数据 6.res = await resp.text() 异步的接收数据 再解释一下两个关键词 1.async 如果一个函数被这个async 关键词修饰 那这个函数就是一个 future object 2.await 协程对象执行到这个关键词定义之处就会做挂起操作,原理是与yield /yield from 类似的。

2. aiohttp 性能测试

使用 aiohttp、requests 作为客户端 模拟多任务请求 做一下两者的性能测试。 我们的请求地址为: url = "http://www.baidu.com" 分别模拟请求: 1.50次调用 2.300次调用

# -*- encoding: utf-8 -*-
# requests 方式


import random
import time
import datetime
import requests


def request_task():
    res = requests.get(url="http://www.baidu.com",verify=False)
    print(res.status_code)


def request_main():
    start = time.time()
    for _ in range(300):
        request_task()
    end = time.time()
    print("发送300次,耗时: %s" % (end - start))  # 发送300次,耗时: 7.497658014297485


if __name__ == "__main__":
    request_main()
# -*- encoding: utf-8 -*-
# aiohttp 方式

import aiohttp
import time
import asyncio


async def aoi_main():
    async with aiohttp.ClientSession() as session:
        async with session.get('http://www.baidu.com') as resp:
            print(resp.status)


start = time.time()
scrape_index_tasks = [asyncio.ensure_future(aoi_main()) for _ in range(300)]
loop = asyncio.get_event_loop()
tasks = asyncio.gather(*scrape_index_tasks)
loop.run_until_complete(tasks)
end = time.time()
print("发送300次,耗时: %s" % (end - start))  # 发送300次,耗时: 2.5207901000976562

我这边测试的最终数据为:

  1. 当请求量为 50 时: requests 方式耗时:0.854 s aiohttp 方式耗时: 0.866 s
  2. 当请求量为 300 时: requests 方式耗时: 7.497 s aiohttp 方式耗时: 2.520 s

通过简单的测试我们可以得出一些结论:

  1. 并不是说使用异步请求就比同步请求性能高
  2. 在并发任务少的情况下建议使用同步的方式做请求,反之在并发任务量大的情况下建议使用异步的方式做请求。你可能好奇怎么界定什么是大并发任务,什么是小并发任务 这个都是相对的 后期我们专门聊一下这个话题

3. 解决 aiohttp 不支持 HTTPS 代理

背景: 有做过爬虫的同学应该知道,同一个ip大并发量请求同一个域名,还没过多久你的这个ip 就会被查封了,所以我们就需要通过代理服务去做请求。

  1. aiohttp 做 http 代理
# -*- encoding: utf-8 -*-

import aiohttp
import time
import asyncio

async def main():
    async with aiohttp.ClientSession() as session:
        proxy_auth = aiohttp.BasicAuth('user', 'pass')
        async with session.get("http://www.hao123.com", proxy="http://www.hao123.com", proxy_auth=proxy_auth) as resp:
            print(resp.status)

if __name__ == '__main__':
    event_loop = asyncio.get_event_loop()
    result = event_loop.run_until_complete(asyncio.gather(main()))
    event_loop.close()
  1. aiohttp 做 http 代理
# -*- encoding: utf-8 -*-

import aiohttp
import time
import asyncio

async def main():
    async with aiohttp.ClientSession() as session:
        proxy_auth = aiohttp.BasicAuth('user', 'pass')
        async with session.get("http://www.hao123.com", proxy="https://www.hao123.com", proxy_auth=proxy_auth) as resp:
            print(resp.status)

if __name__ == '__main__':
    event_loop = asyncio.get_event_loop()
    result = event_loop.run_until_complete(asyncio.gather(main()))
    event_loop.close()

我们请求之后会发现报错: ValueError: Only http proxies are supported

查阅 aiohttp 文档 可知: aiohttp supports plain HTTP proxies and HTTP proxies that can be upgraded to HTTPS via the HTTP CONNECT method. aiohttp does not support proxies that must be connected to via https://. 也就是说: aiohttp 支持纯 HTTP 代理和可以通过 HTTP CONNECT 方法升级到 HTTPS 的 HTTP 代理,不支持必须通过 https:// 连接的代理。

网上找了一大圈也没有找到太好的解决方案,查看了源码 找到一个可以解决这个问题的轮子,我贴出来大家可以参考。

  1. 解决方式一: requests 配合 run_in_executor 来做 这个就不详细展开了 不是我们本节的讨论重点
  2. 解决方式二: 使用 aiohttp-socks,其中实现的 ProxyConnector 继承了 aiohttp 的 TCPConnector。
# -*- encoding: utf-8 -*-
import asyncio
import aiohttp
from aiohttp_socks import ProxyConnector


async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()


async def main(url):
    connector = ProxyConnector.from_url('https://xxx.xxx.com')
    async with aiohttp.ClientSession(connector=connector, headers=headers) as session:
        html = await fetch(session, url)
        # to do list
        pass


def execute():
    urls = ["http://www.hao123.com","http://www.baidu.com"]
    loop = asyncio.get_event_loop()
    tasks = [main(url) for url in urls]
    loop.run_until_complete(asyncio.wait(tasks))

总结

  1. 介绍了asyncio、aiohttp 做请求时候的语法糖
  2. 通过做简单的性能测试,解释了很多人的认知误区,并不是什么场景使用异步都是好的选择,也给出了使用场景的建议
  3. 给出了 ValueError: Only http proxies are supported 的两种可行的解决方案