Hướng dẫn python aiohttp multiple requests - python aiohttp nhiều yêu cầu

    Mã không đồng bộ ngày càng trở thành trụ cột của sự phát triển Python. Với việc Asyncio trở thành một phần của thư viện tiêu chuẩn và nhiều gói bên thứ ba cung cấp các tính năng tương thích với nó, mô hình này sẽ không biến mất sớm.

    Hãy đi qua cách sử dụng thư viện AIOHTTP để tận dụng điều này để thực hiện các yêu cầu HTTP không đồng bộ, đây là một trong những trường hợp sử dụng phổ biến nhất cho mã không chặn.

    Mã không chặn là gì?

    Bạn có thể nghe các thuật ngữ như "không đồng bộ", "không chặn" hoặc "đồng thời" và có một chút bối rối về ý nghĩa của chúng. Theo hướng dẫn chi tiết hơn nhiều này, hai trong số các thuộc tính chính là:

    • Các thói quen không đồng bộ có thể tạm dừng trong khi chờ đợi kết quả cuối cùng của họ để cho các thói quen khác chạy trong lúc này.
    • Mã không đồng bộ, thông qua cơ chế ở trên, tạo điều kiện thực hiện đồng thời. Nói cách khác, mã không đồng bộ mang lại cái nhìn và cảm giác đồng thời.

    Vì vậy, mã không đồng bộ là mã có thể treo trong khi chờ kết quả, để cho mã khác chạy trong lúc này. Nó không "chặn" mã khác chạy để chúng tôi có thể gọi nó là mã "không chặn".

    Thư viện Asyncio cung cấp nhiều công cụ khác nhau để các nhà phát triển Python thực hiện việc này và Aiohttp cung cấp một chức năng cụ thể hơn nữa cho các yêu cầu HTTP. Các yêu cầu HTTP là một ví dụ kinh điển về một cái gì đó phù hợp với sự không đồng bộ vì chúng liên quan đến việc chờ phản hồi từ một máy chủ, trong thời gian đó sẽ thuận tiện và hiệu quả khi chạy mã khác.

    Đang cài đặt

    Hãy chắc chắn để thiết lập môi trường Python của bạn trước khi chúng tôi bắt đầu. Thực hiện theo hướng dẫn này thông qua phần VirtualEnv nếu bạn cần trợ giúp. Nhận mọi thứ hoạt động chính xác, đặc biệt là đối với môi trường ảo rất quan trọng để cô lập các phụ thuộc của bạn nếu bạn có nhiều dự án chạy trên cùng một máy. Bạn sẽ cần ít nhất Python 3.7 hoặc cao hơn để chạy mã trong bài đăng này.

    Bây giờ môi trường của bạn đã được thiết lập, bạn sẽ cần cài đặt một số thư viện của bên thứ ba. Chúng tôi sẽ sử dụng AIOHTTP để thực hiện các yêu cầu không đồng bộ và thư viện yêu cầu để thực hiện các yêu cầu HTTP đồng bộ thường xuyên để so sánh hai yêu cầu sau này. Cài đặt cả hai với lệnh sau sau khi kích hoạt môi trường ảo của bạn:

    pip install aiohttp-3.7.4.post0 requests==2.25.1
    

    Với điều này, bạn nên sẵn sàng để tiếp tục và viết một số mã.

    Thực hiện yêu cầu HTTP với Aiohttp

    Hãy bắt đầu bằng cách thực hiện một yêu cầu GET duy nhất bằng AIOHTTP, để chứng minh cách thức hoạt động của các từ khóa asyncawait. Chúng tôi sẽ sử dụng API Pokemon làm ví dụ, vì vậy hãy bắt đầu bằng cách cố gắng lấy dữ liệu được liên kết với Pokemon huyền thoại thứ 151, MEW.

    Chạy mã Python sau và bạn sẽ thấy tên "MEW" được in vào thiết bị đầu cuối:

    import aiohttp
    import asyncio
    
    
    async def main():
    
        async with aiohttp.ClientSession() as session:
    
            pokemon_url = 'https://pokeapi.co/api/v2/pokemon/151'
            async with session.get(pokemon_url) as resp:
                pokemon = await resp.json()
                print(pokemon['name'])
    
    asyncio.run(main())
    

    Trong mã này, chúng tôi đang tạo ra một coroutine có tên main, mà chúng tôi đang chạy với vòng lặp sự kiện Asyncio. Ở đây, chúng tôi sẽ mở phiên khách AIOHTTP, một đối tượng duy nhất có thể được sử dụng cho khá nhiều yêu cầu riêng lẻ và theo mặc định có thể tạo kết nối với tối đa 100 máy chủ khác nhau cùng một lúc. Với phiên này, chúng tôi đang yêu cầu API Pokemon và sau đó chờ phản hồi.

    Từ khóa async này về cơ bản nói với trình thông dịch Python rằng coroutine chúng tôi xác định nên được chạy không đồng bộ với một vòng lặp sự kiện. Từ khóa await chuyển kiểm soát trở lại vòng lặp sự kiện, đình chỉ việc thực hiện coroutine xung quanh và để vòng lặp sự kiện chạy những thứ khác cho đến khi kết quả đang được "chờ đợi" được trả về.

    Thực hiện một số lượng lớn các yêu cầu

    Thực hiện một yêu cầu HTTP không đồng bộ là tuyệt vời vì chúng tôi có thể để vòng lặp sự kiện hoạt động trên các tác vụ khác thay vì chặn toàn bộ luồng trong khi chờ phản hồi. Nhưng chức năng này thực sự tỏa sáng khi cố gắng thực hiện một số lượng lớn các yêu cầu. Hãy trình bày điều này bằng cách thực hiện yêu cầu tương tự như trước đây, nhưng với tất cả 150 Pokemon ban đầu.

    Chúng ta hãy lấy mã yêu cầu trước đó và đặt nó vào một vòng lặp, cập nhật dữ liệu của Pokemon đang được yêu cầu và sử dụng await cho mỗi yêu cầu:

    import aiohttp
    import asyncio
    import time
    
    start_time = time.time()
    
    
    async def main():
    
        async with aiohttp.ClientSession() as session:
    
            for number in range(1, 151):
                pokemon_url = f'https://pokeapi.co/api/v2/pokemon/{number}'
                async with session.get(pokemon_url) as resp:
                    pokemon = await resp.json()
                    print(pokemon['name'])
    
    asyncio.run(main())
    print("--- %s seconds ---" % (time.time() - start_time))
    

    Lần này, chúng tôi cũng đo lường toàn bộ quá trình mất bao nhiêu thời gian. Nếu bạn chạy mã này trong vỏ Python của bạn, bạn sẽ thấy một cái gì đó giống như sau đây được in vào thiết bị đầu cuối của bạn:

    Hướng dẫn python aiohttp multiple requests - python aiohttp nhiều yêu cầu

    8 giây có vẻ khá tốt cho 150 yêu cầu, nhưng chúng tôi thực sự không có gì để so sánh nó. Chúng ta hãy thử hoàn thành điều tương tự đồng bộ bằng cách sử dụng thư viện yêu cầu.

    So sánh tốc độ với các yêu cầu đồng bộ

    Các yêu cầu được thiết kế để trở thành một thư viện HTTP "cho con người" vì vậy nó có API rất đẹp và đơn giản. Tôi đánh giá cao nó cho bất kỳ dự án nào trong đó tốc độ có thể không có tầm quan trọng hàng đầu so với sự thân thiện với nhà phát triển và dễ theo dõi mã.

    Để in 150 Pokemon đầu tiên như trước đây, nhưng sử dụng thư viện yêu cầu, hãy chạy mã sau:

    import requests
    import time
    
    start_time = time.time()
    
    for number in range(1, 151):
        url = f'https://pokeapi.co/api/v2/pokemon/{number}'
        resp = requests.get(url)
        pokemon = resp.json()
        print(pokemon['name'])
    
    print("--- %s seconds ---" % (time.time() - start_time))
    

    Bạn sẽ thấy cùng một đầu ra với thời gian chạy khác:

    Hướng dẫn python aiohttp multiple requests - python aiohttp nhiều yêu cầu

    Ở gần 29 giây, điều này chậm hơn đáng kể so với mã trước đó. Đối với mỗi yêu cầu liên tiếp, chúng tôi phải đợi bước trước đó kết thúc trước khi bắt đầu quá trình. Mất nhiều thời gian hơn vì mã này đang chờ 150 yêu cầu hoàn thành tuần tự

    Sử dụng Asyncio để cải thiện hiệu suất

    Vì vậy, 8 giây so với 29 giây là một bước nhảy lớn trong hiệu suất, nhưng chúng ta có thể làm tốt hơn nữa bằng cách sử dụng các công cụ mà

    import aiohttp
    import asyncio
    
    
    async def main():
    
        async with aiohttp.ClientSession() as session:
    
            pokemon_url = 'https://pokeapi.co/api/v2/pokemon/151'
            async with session.get(pokemon_url) as resp:
                pokemon = await resp.json()
                print(pokemon['name'])
    
    asyncio.run(main())
    
    2 cung cấp. Trong ví dụ ban đầu, chúng tôi đang sử dụng await sau mỗi yêu cầu HTTP riêng lẻ, điều này không hoàn toàn lý tưởng. Nó vẫn nhanh hơn ví dụ yêu cầu vì chúng tôi đang chạy mọi thứ trong coroutines, nhưng thay vào đó chúng tôi có thể chạy tất cả các yêu cầu này "đồng thời" dưới dạng các nhiệm vụ asyncio và sau đó kiểm tra kết quả ở cuối, sử dụng
    import aiohttp
    import asyncio
    
    
    async def main():
    
        async with aiohttp.ClientSession() as session:
    
            pokemon_url = 'https://pokeapi.co/api/v2/pokemon/151'
            async with session.get(pokemon_url) as resp:
                pokemon = await resp.json()
                print(pokemon['name'])
    
    asyncio.run(main())
    
    4 và
    import aiohttp
    import asyncio
    
    
    async def main():
    
        async with aiohttp.ClientSession() as session:
    
            pokemon_url = 'https://pokeapi.co/api/v2/pokemon/151'
            async with session.get(pokemon_url) as resp:
                pokemon = await resp.json()
                print(pokemon['name'])
    
    asyncio.run(main())
    
    5.

    Nếu mã thực sự đưa ra yêu cầu được chia thành chức năng Coroutine của riêng mình, chúng ta có thể tạo một danh sách các tác vụ, bao gồm tương lai cho mỗi yêu cầu. Sau đó, chúng ta có thể giải nén danh sách này vào một cuộc gọi tập hợp, điều này chạy tất cả chúng lại với nhau. Khi chúng tôi await cuộc gọi này đến

    import aiohttp
    import asyncio
    
    
    async def main():
    
        async with aiohttp.ClientSession() as session:
    
            pokemon_url = 'https://pokeapi.co/api/v2/pokemon/151'
            async with session.get(pokemon_url) as resp:
                pokemon = await resp.json()
                print(pokemon['name'])
    
    asyncio.run(main())
    
    5, chúng tôi sẽ lấy lại một điều đáng tin cậy cho tất cả các tương lai đã được thông qua, duy trì trật tự của họ trong danh sách. Bằng cách này, chúng tôi chỉ chờ đợi một lần.

    Để xem điều gì xảy ra khi chúng tôi thực hiện điều này, hãy chạy mã sau:

    import aiohttp
    import asyncio
    import time
    
    start_time = time.time()
    
    
    async def get_pokemon(session, url):
        async with session.get(url) as resp:
            pokemon = await resp.json()
            return pokemon['name']
    
    
    async def main():
    
        async with aiohttp.ClientSession() as session:
    
            tasks = []
            for number in range(1, 151):
                url = f'https://pokeapi.co/api/v2/pokemon/{number}'
                tasks.append(asyncio.ensure_future(get_pokemon(session, url)))
    
            original_pokemon = await asyncio.gather(*tasks)
            for pokemon in original_pokemon:
                print(pokemon)
    
    asyncio.run(main())
    print("--- %s seconds ---" % (time.time() - start_time))
    

    Điều này đưa thời gian của chúng tôi xuống chỉ còn 1,53 giây cho các yêu cầu 150 http! Đó là một cải tiến lớn so với ngay cả ASYNC/ATAIT ban đầu của chúng tôi. Ví dụ này hoàn toàn không chặn, vì vậy tổng thời gian để chạy tất cả 150 yêu cầu sẽ gần bằng với lượng thời gian mà yêu cầu dài nhất đã thực hiện. Các số chính xác sẽ thay đổi tùy thuộc vào kết nối Internet của bạn.

    Hướng dẫn python aiohttp multiple requests - python aiohttp nhiều yêu cầu

    Kết luận suy nghĩ

    Như bạn có thể thấy, sử dụng các thư viện như Aiohttp để suy nghĩ lại về cách bạn thực hiện các yêu cầu HTTP có thể thêm hiệu suất tăng lớn cho mã của bạn và tiết kiệm rất nhiều thời gian khi thực hiện một số lượng lớn yêu cầu. Theo mặc định, nó dài hơn một chút so với các thư viện đồng bộ như các yêu cầu, nhưng đó là do thiết kế vì các nhà phát triển muốn ưu tiên hiệu suất.

    Trong hướng dẫn này, chúng tôi chỉ cào lên bề mặt của những gì bạn có thể làm với aiohttp và asyncio, nhưng tôi hy vọng rằng điều này đã bắt đầu hành trình của bạn vào thế giới của Python không đồng bộ dễ dàng hơn một chút.

    Tôi mong muốn được xem những gì bạn xây dựng. Hãy tiếp cận và chia sẻ kinh nghiệm của bạn hoặc hỏi bất kỳ câu hỏi nào.

    • E-mail:
    • Twitter: @sagnewshreds
    • GitHub: Sagnew
    • Twitch (Mã trực tiếp phát trực tuyến): Sagnewshreds