Arquitetura do Sistema de Eventos
Este documento explora a arquitetura interna do sistema de eventos do Pydoll, cobrindo comunicação WebSocket, fluxo de eventos, gerenciamento de callbacks e considerações de performance.
Guia de Uso Prático
Para exemplos práticos e padrões de uso, consulte o Guia do Sistema de Eventos.
Comunicação WebSocket e CDP
No núcleo do sistema de eventos do Pydoll está o Chrome DevTools Protocol (CDP), que fornece uma maneira estruturada de interagir e monitorar atividades do navegador através de conexões WebSocket. Este canal de comunicação bidirecional permite que seu código tanto envie comandos para o navegador quanto receba eventos de volta.
sequenceDiagram
participant Client as Código Pydoll
participant Connection as Manipulador de Conexão
participant WebSocket
participant Browser as Navegador
Client->>Connection: Registra callback para evento
Connection->>Connection: Armazena callback no registro
Client->>Connection: Habilita domínio do evento
Connection->>WebSocket: Envia comando CDP para habilitar domínio
WebSocket->>Browser: Encaminha comando
Browser-->>WebSocket: Confirma domínio habilitado
WebSocket-->>Connection: Encaminha resposta
Connection-->>Client: Domínio habilitado
Browser->>WebSocket: Evento ocorre, envia mensagem de evento CDP
WebSocket->>Connection: Encaminha mensagem de evento
Connection->>Connection: Procura callbacks para este evento
Connection->>Client: Executa callback registrado
Modelo de Comunicação WebSocket
A conexão WebSocket entre o Pydoll e o navegador segue este padrão:
- Estabelecimento da Conexão: Quando o navegador inicia, um servidor WebSocket é criado, e o Pydoll estabelece uma conexão com ele
- Mensagens Bidirecionais: Tanto o Pydoll quanto o navegador podem enviar mensagens a qualquer momento
- Tipos de Mensagem:
- Comandos: Enviados do Pydoll para o navegador (ex: navegação, manipulação do DOM)
- Respostas de Comandos: Enviadas do navegador para o Pydoll em resposta a comandos
- Eventos: Enviados do navegador para o Pydoll quando algo acontece (ex: carregamento da página, atividade de rede)
Estrutura do Chrome DevTools Protocol
O CDP organiza sua funcionalidade em domínios, cada um responsável por uma área específica da funcionalidade do navegador:
| Domínio | Responsabilidade | Eventos Típicos |
|---|---|---|
| Page | Ciclo de vida da página | Eventos de carregamento, navegação, diálogos |
| Network | Atividade de rede | Monitoramento de requisição/resposta, WebSockets |
| DOM | Estrutura do documento | Mudanças no DOM, modificações de atributos |
| Fetch | Interceptação de requisição | Requisição pausada, autenticação necessária |
| Runtime | Execução JavaScript | Mensagens do console, exceções |
| Browser | Gerenciamento do navegador | Criação de janelas, abas, contextos |
Cada domínio must ser explicitamente habilitado antes de começar a emitir eventos, o que ajuda a gerenciar a performance processando apenas os eventos que são realmente necessários.
Arquitetura de Domínio
O Padrão Habilitar/Desabilitar (Enable/Disable)
O padrão explícito de habilitar/desabilitar atende a vários propósitos arquiteturais importantes:
- Otimização de Performance: Ao habilitar apenas os domínios nos quais você está interessado, você reduz a sobrecarga (overhead) do processamento de eventos
- Gerenciamento de Recursos: Alguns domínios de eventos (como monitoramento de Rede ou DOM) podem gerar grandes volumes de eventos que consomem memória
- Conformidade com o Protocolo: O CDP exige a habilitação explícita do domínio antes que os eventos sejam emitidos
- Limpeza Controlada: Desabilitar explicitamente os domínios garante uma limpeza adequada quando os eventos não são mais necessários
stateDiagram-v2
[*] --> Disabled: Estado Inicial
Disabled --> Enabled: enable_xxx_events()
Enabled --> Disabled: disable_xxx_events()
Enabled --> [*]: Aba Fechada
Disabled --> [*]: Aba Fechada
Prevenção de Vazamento de Eventos
A falha em desabilitar domínios de eventos quando eles não são mais necessários pode levar a vazamentos de memória e degradação de performance, especialmente em automações de longa duração. Sempre desabilite os domínios de eventos quando terminar de usá-los, particularmente para eventos de alto volume, como monitoramento de rede.
Métodos de Habilitação Específicos do Domínio
Diferentes domínios são habilitados através de métodos específicos nos objetos apropriados:
| Domínio | Método de Habilitação | Método de Desabilitação | Disponível Em |
|---|---|---|---|
| Page | enable_page_events() |
disable_page_events() |
Aba |
| Network | enable_network_events() |
disable_network_events() |
Aba |
| DOM | enable_dom_events() |
disable_dom_events() |
Aba |
| Fetch | enable_fetch_events() |
disable_fetch_events() |
Aba, Navegador |
| File Chooser | enable_intercept_file_chooser_dialog() |
disable_intercept_file_chooser_dialog() |
Aba |
Propriedade do Domínio
Eventos pertencem a domínios específicos com base em sua funcionalidade. Alguns domínios estão disponíveis apenas em certos níveis - por exemplo, eventos de Página (Page) estão disponíveis na instância da Aba (Tab), mas não diretamente noível do Navegador (Browser).
Sistema de Registro de Eventos
O Método on()
O método central para se inscrever (subscribing) em eventos é o método on(), disponível tanto nas instâncias de Aba (Tab) quanto de Navegador (Browser):
async def on(
self, event_name: str, callback: callable, temporary: bool = False
) -> int:
"""
Registra um ouvinte (listener) de evento.
Args:
event_name (str): O nome do evento a ser ouvido.
callback (callable): A função de callback a ser executada quando o
evento é disparado.
temporary (bool): Se True, o callback será removido após ser
disparado uma vez. O padrão é False.
Returns:
int: O ID do callback registrado.
"""
Este método retorna um ID de callback que pode ser usado para remover o callback posteriormente, se necessário.
Registro de Callback
Internamente, o ConnectionHandler (Manipulador de Conexão) mantém um registro de callbacks:
{
'Page.loadEventFired': [
(callback_id_1, callback_function_1, temporary=False),
(callback_id_2, callback_function_2, temporary=True),
],
'Network.requestWillBeSent': [
(callback_id_3, callback_function_3, temporary=False),
]
}
Quando um evento chega via WebSocket:
- O nome do evento é extraído da mensagem
- O registro é consultado por callbacks correspondentes
- Cada callback é executado com os dados do evento
- Callbacks temporários são removidos após a execução
Manipulação de Callback Assíncrono
Callbacks podem ser síncronos ou assíncronos. O sistema de eventos lida com ambos:
async def _trigger_callbacks(self, event_name: str, event_data: dict):
for cb_id, cb_data in self._event_callbacks.items():
if cb_data['event'] == event_name:
if asyncio.iscoroutinefunction(cb_data['callback']):
await cb_data['callback'](event_data)
else:
cb_data['callback'](event_data)
Callbacks assíncronos são aguardados (awaited) sequencialmente. Isso significa que cada callback é concluído antes que o próximo seja executado, o que é importante para:
- Ordem de Execução Previsível: Callbacks executam na ordem de registro
- Tratamento de Erros: Exceções em um callback não impedem que outros sejam executados
- Consistência de Estado: Callbacks podem confiar em mudanças de estado sequenciais
Execução Sequencial vs. Concorrente
Callbacks são executados sequencialmente dentro do mesmo evento. No entanto, eventos diferentes podem ser processados concorrentemente, já que o loop de eventos lida com múltiplas conexões simultaneamente.
Fluxo e Ciclo de Vida do Evento
O ciclo de vida do evento segue estes passos:
flowchart TD
A[Atividade do Navegador] -->|Gera| B[Evento CDP]
B -->|Enviado via WebSocket| C[Manipulador de Conexão]
C -->|Filtra por Nome de Evento| D{Callbacks Registrados?}
D -->|Sim| E[Processar Evento]
D -->|Não| F[Descartar Evento]
E -->|Para Cada Callback| G[Executar Callback]
G -->|Se Temporário| H[Remover Callback]
G -->|Se Permanente| I[Manter para Eventos Futuros]
Fluxo Detalhado
- Atividade do Navegador: Algo acontece no navegador (página carrega, requisição enviada, DOM muda)
- Geração de Evento CDP: O navegador gera uma mensagem de evento CDP
- Transmissão WebSocket: A mensagem é enviada pelo WebSocket para o Pydoll
- Recepção do Evento: O ConnectionHandler recebe o evento
- Busca de Callback: O ConnectionHandler verifica seu registro por callbacks que correspondem ao nome do evento
- Execução do Callback: Se callbacks existirem, cada um é executado com os dados do evento
- Remoção Temporária: Se um callback foi registrado como temporário, ele é removido após a execução
Eventos em Nível de Navegador vs. Nível de Aba
O sistema de eventos do Pydoll opera tanto no nível do navegador quanto no nível da aba, com distinções importantes:
graph TD
Browser[Instância do Navegador] -->|"Eventos Globais (ex: eventos de Target)"| BrowserCallbacks[Callbacks de Nível de Navegador]
Browser -->|"Cria"| Tab1[Instância de Aba 1]
Browser -->|"Cria"| Tab2[Instância de Aba 2]
Tab1 -->|"Eventos Específicos da Aba"| Tab1Callbacks[Callbacks da Aba 1]
Tab2 -->|"Eventos Específicos da Aba"| Tab2Callbacks[Callbacks da Aba 2]
Eventos em Nível de Navegador
Eventos em nível de navegador operam globalmente em todas as abas. Estes são limitados a domínios específicos como:
- Eventos de Alvo (Target): Criação, destruição, falha (crash) de abas
- Eventos do Navegador: Gerenciamento de janelas, coordenação de downloads
# Registro de evento em nível de navegador
await browser.on('Target.targetCreated', handle_new_target)
Os domínios de eventos em nível de navegador são limitados, e tentar usar eventos específicos de abas levantará uma exceção.
Eventos em Nível de Aba
Eventos em nível de aba são específicos para uma aba individual:
# Cada aba tem seu próprio contexto de evento
tab1 = await browser.start()
tab2 = await browser.new_tab()
await tab1.enable_page_events()
await tab1.on(PageEvent.LOAD_EVENT_FIRED, handle_tab1_load)
await tab2.enable_page_events()
await tab2.on(PageEvent.LOAD_EVENT_FIRED, handle_tab2_load)
Esta arquitetura permite:
- Manipulação Isolada de Eventos: Eventos em uma aba não afetam outras
- Configuração por Aba: Abas diferentes podem monitorar tipos de eventos diferentes
- Eficiência de Recursos: Habilite eventos apenas nas abas que precisam deles
Escopo Específico do Domínio
Nem todos os domínios de eventos estão disponíveis em ambos os níveis:
- Eventos Fetch: Disponíveis tanto no nível do navegador quanto da aba
- Eventos de Página (Page): Disponíveis apenas no nível da aba
- Eventos de Alvo (Target): Disponíveis apenas no nível do navegador
Arquitetura de Performance
Sobrecarga (Overhead) do Sistema de Eventos
O sistema de eventos adiciona sobrecarga (overhead) à automação do navegador, especialmente para eventos de alta frequência:
| Domínio do Evento | Volume Típico de Eventos | Impacto na Performance |
|---|---|---|
| Page | Baixo | Mínimo |
| Network | Alto | Moderado a Alto |
| DOM | Muito Alto | Alto |
| Fetch | Moderado | Moderado (maior se estiver interceptando) |
Estratégias de Otimização de Performance
- Habilitação Seletiva de Domínio: Apenas habilite domínios de eventos que você está usando ativamente
- Definição Estratégica de Escopo: Use eventos em nível de navegador apenas para preocupações que sejam verdadeiramente globais
- Desabilitação Oportuna: Sempre desabilite os domínios de eventos quando terminar de usá-los
- Filtragem Precoce: Nos callbacks, filtre eventos irrelevantes o mais cedo possível
- Callbacks Temporários: Use a flag
temporary=Truepara eventos de ocorrência única
Gerenciamento de Memória
O sistema de eventos gerencia a memória através de vários mecanismos:
- Limpeza do Registro de Callbacks: Remover callbacks libera suas referências
- Auto-Remoção Temporária: Callbacks temporários são limpos automaticamente
- Desabilitação de Domínio: Desabilitar um domínio interrompe a geração de eventos
- Fechamento da Aba: Quando uma aba fecha, todos os seus callbacks são removidos automaticamente
Prevenção de Vazamento de Memória
Em automações de longa duração, sempre limpe os callbacks e desabilite os domínios quando terminar. Eventos de alta frequência (especialmente DOM) podem acumular memória significativa se deixados habilitados.
Arquitetura do Manipulador de Conexão (Connection Handler)
O ConnectionHandler é o componente central que gerencia a comunicação WebSocket e o despacho de eventos.
Principais Responsabilidades
- Gerenciamento de WebSocket: Estabelecer e manter a conexão WebSocket
- Roteamento de Mensagens: Distinguir entre respostas de comandos e eventos
- Registro de Callbacks: Manter o mapeamento de nomes de eventos para callbacks
- Despacho de Eventos: Executar callbacks registrados quando os eventos chegam
- Limpeza: Remover callbacks e fechar conexões
Estrutura Interna
class ConnectionHandler:
def __init__(self, ...):
self._events_handler = EventsManager()
self._websocket = None
# ... outros atributos
async def register_callback(self, event_name, callback, temporary):
return self._events_handler.register_callback(event_name, callback, temporary)
class EventsManager:
def __init__(self):
self._event_callbacks = {} # ID do Callback -> dados do callback
self._callback_id = 0
def register_callback(self, event_name, callback, temporary):
self._callback_id += 1
self._event_callbacks[self._callback_id] = {
'event': event_name,
'callback': callback,
'temporary': temporary
}
return self._callback_id
async def _trigger_callbacks(self, event_name, event_data):
callbacks_to_remove = []
for cb_id, cb_data in self._event_callbacks.items():
if cb_data['event'] == event_name:
# Executa callback (await se assíncrono, chama diretamente se síncrono)
if asyncio.iscoroutinefunction(cb_data['callback']):
await cb_data['callback'](event_data)
else:
cb_data['callback'](event_data)
# Marca callbacks temporários para remoção
if cb_data['temporary']:
callbacks_to_remove.append(cb_id)
# Remove callbacks temporários após todos os callbacks serem executados
for cb_id in callbacks_to_remove:
self.remove_callback(cb_id)
Esta arquitetura garante:
- Busca Eficiente: Nomes de eventos mapeiam diretamente para listas de callbacks
- Sobrecarga Mínima: Apenas eventos registrados são processados
- Limpeza Automática: Callbacks temporários são removidos após a execução
- Segurança Assíncrona (Async-safe): Operações são seguras em ambientes assíncronos
Formato da Mensagem de Evento
Eventos CDP seguem um formato de mensagem padronizado:
{
"method": "Network.requestWillBeSent",
"params": {
"requestId": "1234.56",
"loaderId": "7890.12",
"documentURL": "https://example.com",
"request": {
"url": "https://api.example.com/data",
"method": "GET",
"headers": {...}
},
"timestamp": 123456.789,
"wallTime": 1234567890.123,
"initiator": {...},
"type": "XHR"
}
}
Componentes principais:
method: O nome do evento no formatoDominio.nomeDoEventoparams: Dados específicos do evento, variam por tipo de evento- Sem campo
id: Diferente dos comandos, eventos não têm IDs de requisição
O sistema de eventos extrai o campo method para rotear para os callbacks apropriados, passando a mensagem inteira para cada callback.
Coordenação de Eventos Multi-Aba
A arquitetura do Pydoll suporta coordenação sofisticada de eventos multi-aba:
Contextos de Aba Independentes
Cada aba mantém seus próprios:
- Estado de habilitação de domínio de evento
- Registro de callbacks
- Canal de comunicação de evento
- Logs de rede (se eventos de rede estiverem habilitados)
Arquitetura de Comunicação
Cada aba tem seu próprio canal de comunicação de eventos para o navegador. Para detalhes técnicos sobre como conexões WebSocket e IDs de alvo (target) funcionam no nível do protocolo, consulte Arquitetura do Domínio do Navegador.
Contexto de Navegador Compartilhado
Múltiplas abas podem compartilhar:
- Ouvintes de eventos em nível de navegador
- Armazenamento de cookies
- Cache
- Processo do navegador
Esta arquitetura permite:
- Processamento Paralelo de Eventos: Múltiplas abas podem processar eventos simultaneamente
- Falhas Isoladas: Problemas em uma aba não afetam outras
- Compartilhamento de Recursos: Recursos comuns do navegador são compartilhados eficientemente
- Ações Coordenadas: Eventos em nível de navegador podem coordenar atividades entre abas
Conclusão
A arquitetura do sistema de eventos do Pydoll é projetada para:
- Performance: Sobrecarga mínima através de habilitação seletiva de domínio e despacho eficiente de callbacks
- Flexibilidade: Suporte para eventos tanto em nível de navegador quanto de aba
- Escalabilidade: Lidar com múltiplas abas com contextos de eventos independentes
- Confiabilidade: Limpeza automática e gerenciamento de memória
Entender esta arquitetura ajuda você a:
- Otimizar Performance: Saber quais domínios têm alta sobrecarga
- Depurar Problemas: Entender o fluxo de eventos quando as coisas não funcionam como esperado
- Projetar Automação Melhor: Alavancar a arquitetura para fluxos de trabalho eficientes orientados a eventos
- Evitar Armadilhas: Prevenir vazamentos de memória e degradação de performance
Para padrões de uso práticos e exemplos, consulte o Guia do Sistema de Eventos.