A sintaxe async/await do Python é um mecanismo que permite escrever operações assíncronas de maneira concisa, desempenhando um papel crucial em tarefas que dependem de I/O ou em aplicativos que lidam com um grande número de solicitações. Este artigo explica os conceitos fundamentais dessa sintaxe, passando por exemplos práticos e aplicações mais avançadas, para que você possa entender e aplicar a programação assíncrona por meio de exemplos reais de código.
Conceitos Fundamentais da Sintaxe async/await
A sintaxe async/await do Python é uma palavra-chave utilizada para implementar programação assíncrona de maneira simples. Ao usá-la, é possível processar operações demoradas (como I/O) de maneira eficiente, melhorando a responsividade do programa.
O que é Programação Assíncrona?
Programação assíncrona é uma técnica que permite que o programa execute outras tarefas enquanto aguarda a conclusão de uma tarefa específica. Enquanto na programação síncrona as tarefas são executadas uma após a outra, na assíncrona, várias tarefas parecem ser executadas “simultaneamente”.
Funções async e o papel do await
- async: Utilizado para definir uma função como assíncrona. Esta função é chamada de corrotina e pode invocar outras funções assíncronas utilizando
await
. - await: Usado para esperar pelo resultado de uma operação assíncrona. Enquanto o programa aguarda com o
await
, outras tarefas podem ser executadas, melhorando a eficiência geral do programa.
Exemplo Básico de Uso
Abaixo está um exemplo simples de async/await:
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1) # Aguarda 1 segundo
print("World")
# Executa a função assíncrona
asyncio.run(say_hello())
Este código imprime “Hello”, aguarda por 1 segundo e então imprime “World”. Durante o tempo de espera do await
, outras tarefas assíncronas podem ser executadas.
Características das Corrotinas
- Funções definidas com
async
não podem ser executadas diretamente; elas precisam ser executadas comawait
ouasyncio.run()
. - Para utilizar eficientemente a programação assíncrona, é necessário combinar corrotinas e tarefas (que serão abordadas a seguir) adequadamente.
Visão Geral e Funções da Biblioteca asyncio
A biblioteca asyncio
do Python é uma ferramenta essencial para gerenciar operações assíncronas de maneira eficiente, fornecendo recursos para gerenciar tarefas e operações de I/O de maneira paralela.
O Papel do asyncio
- Gestão do Loop de Eventos: Responsável por agendar e executar tarefas assíncronas.
- Gestão de Corrotinas e Tarefas: Registra e executa tarefas assíncronas de forma eficiente.
- Suporte a I/O Assíncrono: Permite realizar operações de I/O, como leitura de arquivos ou comunicação em rede, de forma assíncrona.
O que é o Loop de Eventos?
O loop de eventos é um motor que processa tarefas assíncronas em sequência. O asyncio
utiliza esse loop para gerenciar funções assíncronas e agendar tarefas de maneira eficiente.
import asyncio
async def example_task():
print("Tarefa iniciada")
await asyncio.sleep(1)
print("Tarefa finalizada")
async def main():
# Executa a tarefa no loop de eventos
await example_task()
# Inicia o loop de eventos e executa main()
asyncio.run(main())
Funções e Classes Principais do asyncio
asyncio.run()
: Inicia o loop de eventos e executa a função assíncrona.asyncio.create_task()
: Registra uma corrotina como tarefa no loop de eventos.asyncio.sleep()
: Faz o programa aguardar de forma assíncrona por um determinado tempo.asyncio.gather()
: Executa várias tarefas em paralelo e retorna seus resultados.asyncio.Queue
: Permite a troca de dados de forma eficiente entre tarefas assíncronas.
Exemplo Simples de Aplicação
Abaixo está um exemplo de como executar várias tarefas em paralelo:
async def task1():
print("Tarefa 1 iniciada")
await asyncio.sleep(2)
print("Tarefa 1 finalizada")
async def task2():
print("Tarefa 2 iniciada")
await asyncio.sleep(1)
print("Tarefa 2 finalizada")
async def main():
# Execução paralela
await asyncio.gather(task1(), task2())
asyncio.run(main())
Neste programa, as tarefas 1 e 2 são executadas paralelamente, com a tarefa 2 sendo finalizada primeiro.
Vantagens do asyncio
- Permite gerenciar eficientemente muitas tarefas.
- Melhora a performance em tarefas que dependem de I/O.
- Facilita o agendamento flexível de tarefas através do loop de eventos.
Compreender o asyncio permite aproveitar ao máximo o potencial da programação assíncrona.
Diferenças e Uso de Corrotinas e Tarefas
Corrotinas e tarefas são conceitos fundamentais na programação assíncrona no Python. Entender suas características e como usá-las adequadamente permitirá que você execute processos assíncronos de maneira mais eficiente.
O que é uma Corrotina?
Uma corrotina é uma função especial definida com async def
que pode executar outras operações assíncronas usando await
. Corrotinas podem ser interrompidas durante sua execução e retomadas de onde pararam.
Exemplo de Definição e Uso de uma Corrotina
import asyncio
async def my_coroutine():
print("Iniciando corrotina")
await asyncio.sleep(1)
print("Finalizando corrotina")
# Executando a corrotina
asyncio.run(my_coroutine())
O que é uma Tarefa?
Uma tarefa é uma corrotina que foi registrada no loop de eventos para execução paralela. As tarefas são criadas usando asyncio.create_task()
e, após registradas, são executadas simultaneamente no loop de eventos.
Exemplo de Criação e Execução de Tarefas
import asyncio
async def my_coroutine(number):
print(f"Iniciando corrotina {number}")
await asyncio.sleep(1)
print(f"Finalizando corrotina {number}")
async def main():
# Criando e executando múltiplas tarefas
task1 = asyncio.create_task(my_coroutine(1))
task2 = asyncio.create_task(my_coroutine(2))
# Aguardando a conclusão das tarefas
await task1
await task2
asyncio.run(main())
Neste exemplo, as tarefas 1 e 2 são iniciadas ao mesmo tempo e executadas paralelamente.
Diferenças entre Corrotinas e Tarefas
Características | Corrotina | Tarefa |
---|---|---|
Forma de definição | async def | asyncio.create_task() |
Execução | await ou asyncio.run() | Agendada automaticamente no loop de eventos |
Execução paralela | Executa uma única operação assíncrona | Permite a execução simultânea de múltiplas operações assíncronas |
Pontos Importantes de Uso
- Corrotinas são usadas para tarefas assíncronas simples.
- Tarefas são úteis quando se deseja executar múltiplas operações assíncronas simultaneamente.
Aplicação Prática: Processamento Paralelo com Tarefas
A seguir, temos um exemplo de como usar tarefas para executar várias funções assíncronas em paralelo de maneira eficiente:
import asyncio
async def fetch_data(url):
print(f"Buscando dados de {url}")
await asyncio.sleep(2) # Simula uma espera de rede
print(f"Concluída a busca de dados de {url}")
async def main():
urls = ["https://example.com", "https://example.org", "https://example.net"]
# Criando múltiplas tarefas
tasks = [asyncio.create_task(fetch_data(url)) for url in urls]
# Aguardando a conclusão de todas as tarefas
await asyncio.gather(*tasks)
asyncio.run(main())
Este programa cria várias tarefas usando compreensão de listas e as executa em paralelo.
Pontos de Atenção
- A ordem de execução das tarefas não é garantida, o que significa que pode não ser adequada para operações com dependências entre elas.
- As tarefas são registradas no loop de eventos, portanto não podem ser executadas fora dele.
Compreendendo as diferenças entre corrotinas e tarefas e utilizando-as adequadamente, você pode maximizar a eficiência da programação assíncrona.
Benefícios e Limitações da Programação Assíncrona
A programação assíncrona é uma ferramenta poderosa, especialmente em aplicativos que realizam muitas operações de I/O. No entanto, ela não é uma solução universal. Nesta seção, vamos entender os benefícios e limitações da programação assíncrona e como utilizá-la de forma eficaz.
Benefícios da Programação Assíncrona
1. Aumento de Velocidade e Eficiência
- Uso eficiente dos recursos durante a espera de I/O: Enquanto a programação síncrona faz o programa parar enquanto espera, a programação assíncrona permite que outras tarefas sejam executadas durante o tempo de espera.
- Alta taxa de throughput: Ideal para servidores que processam muitas solicitações ao mesmo tempo ou clientes que realizam múltiplas operações de rede.
2. Melhorias na Responsividade
- Melhora na experiência do usuário: Usando programação assíncrona, é possível executar tarefas em segundo plano sem bloquear a interface do usuário, melhorando a responsividade.
- Redução do tempo de espera: Com operações I/O assíncronas, o tempo total de espera diminui, pois outras operações podem ser realizadas simultaneamente.
3. Flexibilidade e Escalabilidade
- Design escalável: Programas assíncronos consomem menos recursos do sistema, evitando o uso excessivo de threads ou processos.
- Execução multitarefa: Programas assíncronos podem alternar entre várias tarefas de forma eficiente, o que permite ao sistema lidar com cargas de trabalho altas.
Limitações da Programação Assíncrona
1. Complexidade do Programa
A programação assíncrona pode ser mais difícil de entender do que a programação síncrona, especialmente quando se trata de depuração e manutenção. Alguns problemas comuns incluem:
- Condição de corrida: Quando múltiplas tarefas acessam os mesmos recursos, a consistência dos dados pode ser comprometida.
- Callback hell: Programas assíncronos com dependências complexas podem se tornar difíceis de entender e manter.
2. Ineficiente para Tarefas CPU-bound
A programação assíncrona é otimizada para tarefas I/O-bound, ou seja, aquelas que esperam por operações externas. Para tarefas intensivas de CPU, o GIL (Global Interpreter Lock) pode limitar os ganhos de desempenho.
3. Necessidade de um Design Apropriado
Para que a programação assíncrona seja eficaz, é essencial ter um design adequado e escolher as bibliotecas certas. Designs inadequados podem levar aos seguintes problemas:
- Deadlock: Quando as tarefas aguardam umas pelas outras para serem finalizadas, resultando em uma parada completa do programa.
- Inconsistências no agendamento: O agendamento ineficiente de tarefas pode causar atrasos imprevistos.
Dicas para Aproveitar ao Máximo a Programação Assíncrona
1. Uso Apropriado para Cada Caso
- Aplicação para tarefas I/O-bound: Ideal para operações de banco de dados, comunicação de rede, leitura de arquivos, etc.
- Para tarefas CPU-bound, use threads ou processos: Combine técnicas de processamento paralelo para complementar as limitações da programação assíncrona.
2. Uso de Ferramentas e Bibliotecas de Qualidade
- asyncio: A biblioteca padrão para gerenciar tarefas assíncronas.
- aiohttp: Uma biblioteca focada em comunicação HTTP assíncrona.
- Quart e FastAPI: Frameworks web que oferecem suporte para programação assíncrona.
3. Dedicação ao Debugging e Monitoramento
- Utilize logs para registrar o comportamento das tarefas e facilitar a depuração.
- Habilite o modo de depuração do
asyncio
para obter informações detalhadas sobre erros.
A programação assíncrona pode melhorar significativamente o desempenho de uma aplicação, desde que projetada corretamente, levando em consideração suas limitações.
Escrevendo Funções Assíncronas na Prática
Para implementar programação assíncrona em Python, você deve combinar async
e await
para definir e executar funções assíncronas. Neste segmento, vamos aprender a criar funções assíncronas e entender o fluxo básico de execução.
Estrutura Básica de uma Função Assíncrona
Funções assíncronas são definidas com async def
. Dentro dessas funções, outras operações assíncronas são invocadas com await
.
Exemplo de Função Assíncrona Básica
import asyncio
async def greet():
print("Hello,")
await asyncio.sleep(1) # Espera 1 segundo de forma assíncrona
print("World!")
# Executa a função assíncrona
asyncio.run(greet())
Neste exemplo, await asyncio.sleep(1)
representa a execução de um processo assíncrono. Durante o tempo de espera de 1 segundo, outras tarefas podem continuar sendo executadas.
Interligando Funções Assíncronas
É possível chamar várias funções assíncronas e coordená-las para trabalharem juntas.
Exemplo de Interligação de Funções Assíncronas
async def task1():
print("Tarefa 1 iniciada")
await asyncio.sleep(2)
print("Tarefa 1 finalizada")
async def task2():
print("Tarefa 2 iniciada")
await asyncio.sleep(1)
print("Tarefa 2 finalizada")
async def main():
# Executa as funções assíncronas sequencialmente
await task1()
await task2()
asyncio.run(main())
Aqui, a função main
é definida como assíncrona e executa as funções task1
e task2
sequencialmente.
Funções Assíncronas e Execução Paralela
Para executar várias funções assíncronas em paralelo, utiliza-se asyncio.create_task
, o que permite que múltiplas operações assíncronas ocorram simultaneamente.
Exemplo de Execução Paralela
async def task1():
print("Tarefa 1 iniciada")
await asyncio.sleep(2)
print("Tarefa 1 finalizada")
async def task2():
print("Tarefa 2 iniciada")
await asyncio.sleep(1)
print("Tarefa 2 finalizada")
async def main():
# Cria tarefas para execução paralela
task1_coroutine = asyncio.create_task(task1())
task2_coroutine = asyncio.create_task(task2())
# Aguardar a conclusão de ambas as tarefas
await task1_coroutine
await task2_coroutine
asyncio.run(main())
Neste exemplo, as tarefas task1
e task2
são executadas em paralelo, com a task2
terminando primeiro.
Exemplo Aplicado: Contador Assíncrono Simples
Abaixo está um exemplo simples de contador assíncrono, com várias operações de contagem sendo realizadas simultaneamente.
async def count(number):
for i in range(1, 4):
print(f"Contador {number}: {i}")
await asyncio.sleep(1) # Aguarda 1 segundo de forma assíncrona
async def main():
# Executa múltiplas contagens em paralelo
await asyncio.gather(count(1), count(2), count(3))
asyncio.run(main())
Resultado da Execução
Contador 1: 1
Contador 2: 1
Contador 3: 1
Contador 1: 2
Contador 2: 2
Contador 3: 2
Contador 1: 3
Contador 2: 3
Contador 3: 3
Com o uso de programação assíncrona, podemos ver que cada contador opera independentemente dos outros.
Pontos e Atenções Importantes
- A programação assíncrona ajuda a reduzir o desperdício de recursos do sistema e torna a gestão de tarefas mais eficiente.
- Use
asyncio.gather
easyncio.create_task
conforme necessário. - Certifique-se de usar
asyncio.run
ou um loop de eventos para executar funções assíncronas.
Praticando com funções assíncronas desde o básico, você aprimora suas habilidades para aplicar programação assíncrona de maneira mais eficaz.
Métodos para implementar o processamento paralelo: Utilizando gather e wait
No processamento assíncrono em Python, asyncio.gather
e asyncio.wait
são usados para executar várias tarefas de forma eficiente e paralela. Compreendendo as características e formas de uso de cada um, é possível construir programas assíncronos mais flexíveis.
Visão geral do asyncio.gather e exemplo de uso
asyncio.gather
executa várias tarefas assíncronas ao mesmo tempo e aguarda até que todas as tarefas sejam concluídas. Após a conclusão, ele retorna os resultados de cada tarefa em forma de lista.
Exemplo básico
import asyncio
async def task1():
await asyncio.sleep(1)
return "Tarefa 1 concluída"
async def task2():
await asyncio.sleep(2)
return "Tarefa 2 concluída"
async def main():
results = await asyncio.gather(task1(), task2())
print(results)
asyncio.run(main())
Resultado da execução
['Tarefa 1 concluída', 'Tarefa 2 concluída']
Características
- Aguarda a conclusão das tarefas em execução paralela e recebe os resultados em uma lista.
- Se ocorrer uma exceção,
gather
interrompe todas as tarefas e propaga a exceção para a origem da chamada.
Visão geral do asyncio.wait e exemplo de uso
asyncio.wait
executa várias tarefas paralelamente e retorna um conjunto de tarefas concluídas e não concluídas.
Exemplo básico
import asyncio
async def task1():
await asyncio.sleep(1)
print("Tarefa 1 concluída")
async def task2():
await asyncio.sleep(2)
print("Tarefa 2 concluída")
async def main():
tasks = [task1(), task2()]
done, pending = await asyncio.wait(tasks)
print(f"Tarefas concluídas: {len(done)}, Tarefas pendentes: {len(pending)}")
asyncio.run(main())
Resultado da execução
Tarefa 1 concluída
Tarefa 2 concluída
Tarefas concluídas: 2, Tarefas pendentes: 0
Características
- É possível verificar o estado das tarefas (concluídas ou pendentes) em detalhes.
- Mesmo que uma tarefa seja interrompida, é possível continuar processando as tarefas pendentes.
- Com a opção
return_when
deasyncio.wait
, é possível controlar o momento em que as tarefas são encerradas com base em condições específicas.
Exemplo da opção return_when
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
FIRST_COMPLETED
: Retorna quando a primeira tarefa for concluída.FIRST_EXCEPTION
: Retorna quando ocorrer a primeira exceção.ALL_COMPLETED
: Aguarda até que todas as tarefas sejam concluídas (padrão).
Diferença entre gather e wait
- Quando você deseja receber os resultados juntos: Use
asyncio.gather
. - Quando você deseja gerenciar o estado das tarefas individualmente: Use
asyncio.wait
. - Quando você deseja interromper o processo ou tratar exceções no meio: Use
asyncio.wait
.
Exemplo avançado: Chamada paralela de APIs
A seguir, temos um exemplo de como chamar várias APIs paralelamente e obter suas respostas:
import asyncio
async def fetch_data(api_name, delay):
print(f"Buscando de {api_name}...")
await asyncio.sleep(delay) # Espera simulada
return f"Dados de {api_name}"
async def main():
apis = [("API_1", 2), ("API_2", 1), ("API_3", 3)]
tasks = [fetch_data(api, delay) for api, delay in apis]
# Executa as tarefas em paralelo e coleta os resultados
results = await asyncio.gather(*tasks)
for result in results:
print(result)
asyncio.run(main())
Resultado da execução
Buscando de API_1...
Buscando de API_2...
Buscando de API_3...
Dados de API_2
Dados de API_1
Dados de API_3
Cuidados importantes
- Tratamento de exceções: Quando uma exceção ocorre em tarefas paralelas, é importante capturá-la e tratá-la adequadamente. Utilize
try
/except
. - Cancelamento de tarefas: Para cancelar tarefas desnecessárias, use
task.cancel()
. - Evite deadlocks: É necessário projetar para evitar situações em que as tarefas ficam esperando umas pelas outras indefinidamente.
Ao utilizar asyncio.gather
e asyncio.wait
de forma eficaz, podemos maximizar a flexibilidade e a eficiência do processamento assíncrono.
Exemplo de I/O assíncrono: Operações de arquivos e rede
O I/O assíncrono é uma técnica para otimizar processos que envolvem espera, como operações com arquivos e comunicação em rede. Usando asyncio
, podemos implementar facilmente I/O assíncrono. Nesta seção, veremos exemplos de como utilizá-lo em situações práticas.
Operações assíncronas com arquivos
Para operações assíncronas com arquivos, usamos a biblioteca aiofiles
. Esta biblioteca estende as funções de arquivo da biblioteca padrão para operações assíncronas.
Exemplo: Leitura e escrita assíncrona de arquivos
import aiofiles
import asyncio
async def read_file(filepath):
async with aiofiles.open(filepath, mode='r') as file:
contents = await file.read()
print(f"Conteúdo de {filepath}:")
print(contents)
async def write_file(filepath, data):
async with aiofiles.open(filepath, mode='w') as file:
await file.write(data)
print(f"Dados escritos em {filepath}")
async def main():
filepath = 'example.txt'
await write_file(filepath, "Olá, I/O Assíncrono!")
await read_file(filepath)
asyncio.run(main())
Pontos importantes
- Com
aiofiles.open
, podemos realizar operações de arquivos de forma assíncrona. - Use a sintaxe
async with
para lidar com arquivos de forma segura. - Durante as operações de arquivos, outras tarefas podem continuar sendo executadas.
Operações assíncronas de rede
Em operações de rede, a biblioteca aiohttp
permite realizar requisições HTTP assíncronas.
Exemplo: Requisições HTTP assíncronas
import aiohttp
import asyncio
async def fetch_url(session, url):
async with session.get(url) as response:
print(f"Buscando {url}")
content = await response.text()
print(f"Conteúdo de {url}: {content[:100]}...")
async def main():
urls = [
"https://example.com",
"https://example.org",
"https://example.net"
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
Pontos importantes
- Use
aiohttp.ClientSession
para realizar comunicação HTTP assíncrona. - Gerencie as sessões de forma segura com a sintaxe
async with
. - Use
asyncio.gather
para executar várias requisições de forma paralela, tornando o processo mais eficiente.
Combinação de I/O assíncrono com arquivos e rede
Combinando operações assíncronas de arquivos e rede, podemos coletar e salvar dados de forma eficiente.
Exemplo: Salvar dados baixados de forma assíncrona
import aiohttp
import aiofiles
import asyncio
async def fetch_and_save(session, url, filepath):
async with session.get(url) as response:
print(f"Buscando {url}")
content = await response.text()
async with aiofiles.open(filepath, mode='w') as file:
await file.write(content)
print(f"Conteúdo de {url} salvo em {filepath}")
async def main():
urls = [
("https://example.com", "example_com.txt"),
("https://example.org", "example_org.txt"),
("https://example.net", "example_net.txt")
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_and_save(session, url, filepath) for url, filepath in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
Exemplo de resultado da execução
- O conteúdo de
https://example.com
será salvo no arquivoexample_com.txt
. - Da mesma forma, os conteúdos das outras URLs serão salvos nos respectivos arquivos.
Cuidados ao usar I/O assíncrono
- Implementação de tratamento de exceções
Prepare-se para falhas de rede ou erros de gravação de arquivo, implementando tratamento adequado de exceções.
try:
# Tarefa assíncrona
except Exception as e:
print(f"Ocorreu um erro: {e}")
- Implementação de limitação (throttling)
Executar muitas tarefas assíncronas simultaneamente pode sobrecarregar o sistema ou servidor. Useasyncio.Semaphore
para limitar o número de tarefas em execução paralela.
semaphore = asyncio.Semaphore(5) # Limita a 5 tarefas em execução simultânea
async with semaphore:
await some_async_task()
- Definir um tempo limite
Evite processos que ficam esperando indefinidamente configurando um tempo limite para tarefas assíncronas.
try:
await asyncio.wait_for(some_async_task(), timeout=10)
except asyncio.TimeoutError:
print("Tarefa ultrapassou o tempo limite")
Usar I/O assíncrono de maneira adequada pode melhorar significativamente a eficiência e o throughput das aplicações.
Exemplo avançado: Construção de um Web Crawler Assíncrono
Ao aproveitar o processamento assíncrono, é possível criar crawlers de websites rápidos e eficientes. Usando I/O assíncrono, várias páginas da web podem ser recuperadas simultaneamente, maximizando a velocidade de rastreamento. Nesta seção, veremos como implementar um Web Crawler assíncrono usando Python.
Estrutura básica de um Web Crawler Assíncrono
Em um Web Crawler assíncrono, três elementos são essenciais:
- Gerenciamento da lista de URLs: Gerencie eficientemente as URLs a serem rastreadas.
- Comunicação HTTP assíncrona: Use a biblioteca
aiohttp
para recuperar as páginas da web. - Armazenamento de dados: Use operações de arquivos assíncronas para salvar os dados recuperados.
Exemplo de código: Web Crawler Assíncrono
A seguir está um exemplo básico de um Web Crawler assíncrono:
import aiohttp
import aiofiles
import asyncio
from bs4 import BeautifulSoup
async def fetch_page(session, url):
try:
async with session.get(url) as response:
if response.status == 200:
html = await response.text()
print(f"Página recuperada de {url}")
return html
else:
print(f"Falha ao recuperar {url}: {response.status}")
return None
except Exception as e:
print(f"Erro ao recuperar {url}: {e}")
return None
async def parse_and_save(html, url, filepath):
if html:
soup = BeautifulSoup(html, 'html.parser')
title = soup.title.string if soup.title else "Sem título"
async with aiofiles.open(filepath, mode='a') as file:
await file.write(f"URL: {url}\nTítulo: {title}\n\n")
print(f"Dados salvos para {url}")
async def crawl(urls, output_file):
async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
tasks.append(process_url(session, url, output_file))
await asyncio.gather(*tasks)
async def process_url(session, url, output_file):
html = await fetch_page(session, url)
await parse_and_save(html, url, output_file)
async def main():
urls = [
"https://example.com",
"https://example.org",
"https://example.net"
]
output_file = "crawl_results.txt"
# Inicialização: Limpar o arquivo de resultados
async with aiofiles.open(output_file, mode='w') as file:
await file.write("")
await crawl(urls, output_file)
asyncio.run(main())
Explicação do funcionamento do código
- Função
fetch_page
Recupera o HTML da página da web com requisições HTTP assíncronas. Também faz o tratamento de erros verificando o código de status. - Função
parse_and_save
Usa o BeautifulSoup para analisar o HTML e extrair o título da página. Em seguida, salva essas informações em um arquivo de forma assíncrona. - Função
crawl
Recebe uma lista de URLs e processa cada uma delas paralelamente. Usaasyncio.gather
para executar as tarefas em paralelo. - Função
process_url
Executa o processo completo para uma única URL, chamandofetch_page
eparse_and_save
.
Exemplo de resultado da execução
No arquivo crawl_results.txt
, você verá os seguintes dados salvos:
URL: https://example.com
Título: Example Domain
URL: https://example.org
Título: Example Domain
URL: https://example.net
Título: Example Domain
Dicas para melhorar o desempenho
- Limitação de tarefas paralelas
Quando estiver rastreando muitas URLs, limite o número de tarefas paralelas para evitar sobrecarregar o servidor.
semaphore = asyncio.Semaphore(10)
async def limited_process_url(semaphore, session, url, output_file):
async with semaphore:
await process_url(session, url, output_file)
- Adicionar funcionalidade de retry
Adicionar lógica para tentar novamente quando uma requisição falhar pode aumentar a confiabilidade do seu crawler.
Cuidados adicionais
- Verifique a legalidade
Antes de rodar um web crawler, verifique orobots.txt
do site e as políticas de uso. - Tratamento adequado de erros
Garanta que erros de rede ou de análise de HTML sejam tratados corretamente para evitar que o crawler pare de funcionar. - Configuração de timeouts
Defina timeouts para suas requisições para evitar que o crawler fique esperando indefinidamente por respostas.
async with session.get(url, timeout=10) as response:
Um web crawler assíncrono, com o design e controle adequados, pode ser uma ferramenta eficiente e escalável para coleta de dados.
Conclusão
Este artigo explicou detalhadamente o uso do processamento assíncrono em Python, desde os conceitos básicos até as aplicações avançadas. Compreender o processamento assíncrono permite otimizar tarefas de I/O e melhorar o desempenho de aplicações.
Em particular, você aprendeu os fundamentos da biblioteca asyncio
, como usar gather
e wait
para processamento paralelo, exemplos de I/O assíncrono e a construção de um web crawler assíncrono.
O processamento assíncrono, quando projetado corretamente, pode ajudar a criar sistemas eficientes e escaláveis, mas é fundamental ter cuidado com o tratamento de exceções e a conformidade legal. Esperamos que este artigo tenha ajudado você a aprender e aplicar o processamento assíncrono de forma prática.