Solicitações HTTP assíncronas em Python com aiohttp e asyncio
Tempo de leitura: 4 minutos
O código assíncrono tornou-se cada vez mais um pilar do desenvolvimento com Python. Com asyncio se tornando parte da biblioteca padrão e os muitos pacotes de terceiros que fornecem recursos compatíveis, esse paradigma não deve desaparecer tão cedo.
Vamos aprender a usar a biblioteca aiohttp para fazer solicitações HTTP assíncronas, que é um dos casos de uso mais comuns do código sem bloqueio.
O que é código sem bloqueio?
Talvez você ouça termos como "assíncrono", "sem bloqueio" ou "simultâneo" e fique um pouco confuso com o significado deles. De acordo com este tutorial mais detalhado, duas das principais propriedades são:
- É possível "pausar" rotinas assíncronas enquanto aguarda o resultado para que outras rotinas sejam executadas nesse intervalo.
- O código assíncrono, por meio do mecanismo acima, facilita a execução simultânea. Em outras palavras, o código assíncrono confere um aspecto de simultaneidade.
Por isso, o código assíncrono é aquele que pode ser suspenso enquanto se espera um resultado para que outro código possa ser executado nesse meio tempo. Ele não "bloqueia" a execução de outro código, por isso é possível chamar de código "sem bloqueio".
A biblioteca asyncio oferece diversas ferramentas para os desenvolvedores Python criarem esse código, e a aiohttp tem uma funcionalidade ainda mais específica para solicitações HTTP. As solicitações HTTP são um exemplo clássico de algo que é bem adequado à assincronicidade porque envolvem esperar uma resposta de um servidor, período durante o qual seria conveniente e eficiente ter outro código em execução.
Configuração
Antes de começar, verifique se o ambiente Python está configurado. Se precisar de ajuda, siga este guia até a seção virtualenv. É importante deixar tudo funcionando de forma correta, principalmente no que diz respeito a ambientes virtuais para isolar as dependências caso tenha vários projetos em execução no mesmo computador. É necessário, pelo menos, o Python 3.7 ou superior para executar o código desta publicação.
Agora que o ambiente está configurado, é preciso instalar algumas bibliotecas de terceiros. Vamos usar a aiohttp para fazer solicitações assíncronas e a biblioteca requests para fazer solicitações HTTP síncronas comuns e comparar depois as duas. Instale as duas bibliotecas usando o seguinte comando depois de ativar o ambiente virtual:
Após essas ações, você está pronto para prosseguir e gravar alguns códigos.
Como fazer uma solicitação HTTP com aiohttp
Comece fazendo uma única solicitação GET
usando aiohttp para demonstrar como funcionam as palavras-chave async
e await
. Usemos a API Pokémoncomo exemplo e, para começar, tente obter os dados associados ao lendário 151º Pokémon, Mew.
Execute o seguinte código Python para visualizar o nome "mew" exibido no terminal:
Neste código, é criada uma corrotina chamada main
, que está sendo executada com o loop de eventos asyncio. Aqui, estamos iniciando uma sessão do cliente aiohttp, um objeto único que pode ser usado para várias solicitações individuais e que, por padrão, pode se conectar com até 100 servidores diferentes ao mesmo tempo. Com esta sessão, estamos fazendo uma solicitação à API Pokémon e aguardando uma resposta.
Esta palavra-chave async
basicamente informa ao interpretador Python que a corrotina definida deve ser executada de maneira assíncrona com um loop de eventos. A palavra-chave await
devolve o controle ao loop de eventos, o que suspende a execução da corrotina adjacente e permite que o loop de eventos execute outras funções até receber o resultado que está sendo "aguardado".
Como fazer muitas solicitações
É ótimo fazer uma única solicitação HTTP assíncrona porque o loop de eventos pode trabalhar em outras tarefas, em vez de bloquear todo o thread enquanto aguarda uma resposta. Mas o grande destaque dessa funcionalidade é quando tenta fazer um número maior de solicitações. Vamos fazer uma demonstração executando a mesma solicitação anterior, mas para todos os 150 do Pokémon original.
Vejamos o código da solicitação anterior e colocamos em um loop, o que atualiza os dados do Pokémon que estão sendo solicitados e usando await
para cada solicitação:
Desta vez, também vamos calcular a duração de todo processo. Se você executar este código no shell do Python, aparecerá algo com o seguinte no terminal:
Oito segundos parece muito bom para 150 solicitações, mas não temos nada para comparar. Vamos tentar fazer a mesma coisa de maneira síncrona com a biblioteca requests.
Comparação da velocidade com solicitações síncronas
A Requests foi criada para ser uma biblioteca HTTP "para humanos", por isso tem uma API muito bonita e simplista. Recomendo muito a qualquer projeto em que a velocidade possa não ser de fundamental importância, se comparada a um código fácil de usar e seguir.
Para ter os 150 primeiros Pokémon como anteriormente, mas usando a biblioteca requests, execute o seguinte código:
Deve aparecer o mesmo resultado, mas com tempo de execução diferente:
Com quase 29 segundos, é consideravelmente mais lento do que o código anterior. Para cada solicitação consecutiva, antes mesmo de começar o processo, é preciso aguardar a conclusão da etapa anterior. Isso leva muito mais tempo porque esse código aguarda a conclusão em sequência das 150 solicitações.
Como utilizar a asyncio para melhorar o desempenho
Se for feita a comparação de 8 segundos para 29 segundos, é um grande salto de desempenho, mas podemos fazer ainda melhor usando as ferramentas fornecidas pela asyncio
. No exemplo original, é usado o await
depois de cada solicitação HTTP, o que não é o ideal. Ele ainda é mais rápido do que o exemplo de requests porque tudo é executado em corrotinas, mas é possível executar todas essas solicitações "simultaneamente" como tarefas asyncio e verificar os resultados no final usando asyncio.ensure_future
e asyncio.gather
.
Se o código que faz a solicitação for dividido na função de sua própria corrotina, é possível criar uma lista de tarefas formada por futures para cada solicitação. Depois, é possível descompactar essa lista em uma chamada gather, que executa tudo junto. Quando é usado o await
nesta chamada para asyncio.gather
, recebemos um iterável para todos os futures que foram passados, mantendo sua ordem na lista. Dessa forma, é usado o await uma única vez.
Para visualizar o que acontece quando é implementamos, execute o seguinte código:
Ele diminui o tempo a meros 1,53 segundos para as 150 solicitações HTTP! É um grande progresso, até mesmo em relação ao exemplo inicial de async/await. Este exemplo usa um código totalmente sem bloqueio, por isso o tempo total para executar as 150 solicitações será quase igual ao tempo de execução da solicitação mais demorada. Os números exatos variam conforme a conexão de Internet.
Considerações finais
Como pode se ver, o uso de bibliotecas como aiohttp para repensar o modo de fazer solicitações HTTP pode aumentar muito o desempenho do código e economizar bastante tempo se fizer muitas solicitações. Por padrão, ela é um pouco mais detalhada do que as bibliotecas síncronas, como requests, mas este comportamento é esperado, porque os desenvolvedores quiseram priorizar o desempenho.
Neste tutorial, mostramos apenas superficialmente o que é possível fazer com aiohttp e asyncio, mas esperamos ter facilitado um pouco o início da sua jornada no mundo do Python assíncrono.
Publicações relacionadas
Recursos relacionados
Twilio Docs
De APIs a SDKs e aplicativos de amostra
Documentação de referência de API, SDKs, bibliotecas auxiliares, guias de início rápido e tutoriais para sua linguagem e plataforma.
Centro de Recursos
Os mais recentes e-books, relatórios do setor e webinars
Aprenda com especialistas em engajamento do cliente para melhorar sua própria comunicação.
Ahoy
Centro da comunidade de desenvolvedores da Twilio
Melhores práticas, exemplos de código e inspiração para criar comunicações e experiências de engajamento digital.