Fundamentos e Aplicações da Sintaxe async/await no Python

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.

Índice

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 com await ou asyncio.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ísticasCorrotinaTarefa
Forma de definiçãoasync defasyncio.create_task()
Execuçãoawait ou asyncio.run()Agendada automaticamente no loop de eventos
Execução paralelaExecuta uma única operação assíncronaPermite 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 e asyncio.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 de asyncio.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 arquivo example_com.txt.
  • Da mesma forma, os conteúdos das outras URLs serão salvos nos respectivos arquivos.

Cuidados ao usar I/O assíncrono

  1. 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}")
  1. Implementação de limitação (throttling)
    Executar muitas tarefas assíncronas simultaneamente pode sobrecarregar o sistema ou servidor. Use asyncio.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()
  1. 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:

  1. Gerenciamento da lista de URLs: Gerencie eficientemente as URLs a serem rastreadas.
  2. Comunicação HTTP assíncrona: Use a biblioteca aiohttp para recuperar as páginas da web.
  3. 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

  1. 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.
  2. 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.
  3. Função crawl
    Recebe uma lista de URLs e processa cada uma delas paralelamente. Usa asyncio.gather para executar as tarefas em paralelo.
  4. Função process_url
    Executa o processo completo para uma única URL, chamando fetch_page e parse_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

  1. Verifique a legalidade
    Antes de rodar um web crawler, verifique o robots.txt do site e as políticas de uso.
  2. 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.
  3. 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.

Índice