Skip to content

Gerenciamento de Múltiplas Abas

O Pydoll oferece capacidades sofisticadas de múltiplas abas que permitem fluxos de trabalho de automação complexos, abrangendo várias abas do navegador simultaneamente. Entender como as abas funcionam no Pydoll é essencial para construir uma automação robusta e escalável.

Entendendo as Abas no Pydoll

No Pydoll, uma instância de Tab representa uma única aba (ou janela) do navegador e fornece a interface principal para todas as operações de automação de página. Cada aba mantém seus próprios:

  • Contexto de execução independente: JavaScript, DOM e estado da página
  • Manipuladores de eventos isolados: Callbacks registrados em uma aba não afetam outras
  • Monitoramento de rede separado: Cada aba pode rastrear sua própria atividade de rede
  • Conexão CDP única: Comunicação WebSocket direta com o navegador
graph LR
    Browser[Instancia do Navegador] --> Tab1[Aba 1]
    Browser --> Tab2[Aba 2]
    Browser --> Tab3[...]

    Tab1 --> Features1[Contexto<br/>Independente]
    Tab2 --> Features2[Contexto<br/>Independente]
Componente da Aba Descrição Independência
Contexto de Execução Runtime JavaScript, DOM, estado da página ✓ Cada aba tem o seu
Manipuladores de Eventos Callbacks registrados para eventos CDP ✓ Isolados por aba
Monitoramento de Rede Requisições HTTP, respostas, tempos ✓ Rastreia separadamente
Conexão CDP Canal de comunicação WebSocket ✓ Conexão direta

O que é uma Aba de Navegador?

Uma aba de navegador é tecnicamente um alvo (target) CDP - um contexto de navegação isolado com seu próprio:

  • Document Object Model (DOM)
  • Ambiente de execução JavaScript
  • Pool de conexões de rede
  • Armazenamento de cookies (compartilhado com outras abas no mesmo contexto)
  • Loop de eventos e motor de renderização

Cada aba tem um target_id único atribuído pelo navegador, que o Pydoll usa para rotear comandos e eventos corretamente.

Gerenciamento de Instâncias de Aba

A classe Browser do Pydoll mantém um registro de instâncias de Tab com base no target_id de cada aba. Isso garante que múltiplas referências à mesma aba do navegador sempre retornem o mesmo objeto Tab. O Browser armazena essas instâncias em um dicionário interno _tabs_opened.

Benefício Descrição
Eficiência de Recursos Uma instância de Tab por aba do navegador, sem duplicatas
Estado Consistente Todas as referências compartilham os mesmos manipuladores de eventos e estado
Segurança de Memória Evita múltiplas conexões WebSocket para o mesmo alvo
Comportamento Previsível Mudanças em uma referência afetam todas as referências
import asyncio
from pydoll.browser.chromium import Chrome

async def tab_registry_demonstration():
    async with Chrome() as browser:
        # Inicia o navegador com a aba inicial
        tab1 = await browser.start()

        # Obtém a mesma aba através de métodos diferentes
        # Nota: get_opened_tabs() retorna as abas em ordem inversa (mais nova primeiro)
        # Então a aba inicial (mais antiga) está no final
        opened_tabs = await browser.get_opened_tabs()
        tab2 = opened_tabs[-1]  # A aba inicial é a mais antiga, então é a última

        # Ambas as referências apontam para o mesmo objeto
        # porque o Browser retorna a mesma instância de seu registro
        print(f"Mesma instância? {tab1 is tab2}")  # True
        print(f"Mesmo target ID? {tab1._target_id == tab2._target_id}")  # True

        # Registrar evento em uma referência afeta a outra
        await tab1.enable_network_events()
        print(f"Eventos de rede na aba 2? {tab2.network_events_enabled}")  # True

        # O Browser mantém o registro internamente
        print(f"Aba registrada no navegador? {tab1._target_id in browser._tabs_opened}")  # True

asyncio.run(tab_registry_demonstration())

Registro Gerenciado pelo Navegador

A classe Browser gerencia um dicionário _tabs_opened indexado por target_id. Quando você solicita uma aba (via new_tab(), get_opened_tabs(), ou get_frame()), o Browser verifica este registro primeiro. Se uma instância de Tab já existe para aquele target_id, ele retorna a instância existente; caso contrário, cria uma nova e a armazena no registro.

Criando e Gerenciando Abas

Iniciando o Navegador

Quando você inicia o navegador, o Pydoll automaticamente cria e retorna uma instância de Tab para a aba inicial do navegador:

import asyncio
from pydoll.browser.chromium import Chrome

async def start_browser():
    async with Chrome() as browser:
        # Aba inicial é criada automaticamente
        tab = await browser.start()

        print(f"Aba criada com target ID: {tab._target_id}")
        await tab.go_to('https://example.com')

        title = await tab.execute_script('return document.title')
        print(f"Título da página: {title}")

asyncio.run(start_browser())

Criando Abas Adicionais Programaticamente

Use browser.new_tab() para criar abas adicionais com controle total:

import asyncio
from pydoll.browser.chromium import Chrome

async def create_multiple_tabs():
    async with Chrome() as browser:
        # Começar com a aba inicial
        main_tab = await browser.start()

        # Criar abas adicionais com URLs específicas
        search_tab = await browser.new_tab('https://google.com')
        docs_tab = await browser.new_tab('https://docs.python.org')
        news_tab = await browser.new_tab('https://news.ycombinator.com')

        # Cada aba pode ser controlada independentemente
        await search_tab.find(name='q')  # Caixa de busca do Google
        await docs_tab.find(id='search-field')  # Busca da doc do Python
        await news_tab.find(class_name='storylink', find_all=True)  # Matérias do HN

        # Obter todas as abas abertas
        all_tabs = await browser.get_opened_tabs()
        print(f"Total de abas: {len(all_tabs)}")  # 4 (inicial + 3 novas)

        # Fechar abas específicas quando terminar
        await search_tab.close()
        await docs_tab.close()
        await news_tab.close()

asyncio.run(create_multiple_tabs())

Parâmetro de URL Opcional

Você pode criar abas sem especificar uma URL: await browser.new_tab(). A aba abrirá com uma página em branco (about:blank), pronta para navegação.

Lidando com Abas Abertas pelo Usuário

Quando usuários clicam em links com target="_blank" ou usam "Abrir em nova aba", o Pydoll pode detectar e gerenciar essas abas:

import asyncio
from pydoll.browser.chromium import Chrome

async def handle_user_tabs():
    async with Chrome() as browser:
        main_tab = await browser.start()
        await main_tab.go_to('https://example.com')

        # Registrar contagem inicial de abas
        initial_tabs = await browser.get_opened_tabs()
        print(f"Abas iniciais: {len(initial_tabs)}")

        # Clicar em um link que abre uma nova aba (target="_blank")
        external_link = await main_tab.find(text='Open in New Tab')
        await external_link.click()

        # Esperar a nova aba abrir
        await asyncio.sleep(2)

        # Detectar novas abas
        current_tabs = await browser.get_opened_tabs()
        print(f"Abas atuais: {len(current_tabs)}")

        # Encontrar a aba recém-aberta (última da lista)
        if len(current_tabs) > len(initial_tabs):
            new_tab = current_tabs[-1]

            # Trabalhar com a nova aba
            url = await new_tab.current_url
            print(f"URL da nova aba: {url}")

            await new_tab.go_to('https://different-site.com')
            title = await new_tab.execute_script('return document.title')
            print(f"Título da nova aba: {title}")

            # Fechá-la quando terminar
            await new_tab.close()

asyncio.run(handle_user_tabs())

Listando Todas as Abas Abertas

Use browser.get_opened_tabs() para recuperar todas as abas atualmente abertas:

import asyncio
from pydoll.browser.chromium import Chrome

async def list_tabs():
    async with Chrome() as browser:
        # Usar a aba inicial retornada pelo start()
        initial_tab = await browser.start()
        await initial_tab.go_to('https://example.com')

        # Abrir várias outras abas
        await browser.new_tab('https://github.com')
        await browser.new_tab('https://stackoverflow.com')
        await browser.new_tab('https://reddit.com')

        # Obter todas as abas
        all_tabs = await browser.get_opened_tabs()

        # Inspecionar cada aba
        for i, tab in enumerate(all_tabs, 1):
            url = await tab.current_url
            title = await tab.execute_script('return document.title')
            print(f"Aba {i}: {title} - {url}")

asyncio.run(list_tabs())

Operações Concorrentes de Abas

A arquitetura assíncrona do Pydoll permite fluxos de trabalho concorrentes poderosos em múltiplas abas:

Coleta de Dados Paralela

Processe múltiplas páginas simultaneamente para máxima eficiência:

import asyncio
from pydoll.browser.chromium import Chrome

async def scrape_page(tab, url):
    """Raspar uma única página dentro de uma aba específica."""
    await tab.go_to(url)
    title = await tab.execute_script('return document.title')
    articles = await tab.find(class_name='article', find_all=True)
    content = [await article.text for article in articles[:5]]

    return {
        'url': url,
        'title': title,
        'articles_count': len(articles),
        'sample_content': content
    }

async def concurrent_scraping():
    urls = [
        'https://example.com/page1',
        'https://example.com/page2',
        'https://example.com/page3',
        'https://example.com/page4',
    ]

    async with Chrome() as browser:
        # Iniciar o navegador e abrir a primeira aba
        initial_tab = await browser.start()
        # Criar uma aba por URL
        tabs = [initial_tab] + [await browser.new_tab() for _ in urls[1:]]

        # Executar todos os scrapers concorrentemente
        results = await asyncio.gather(*[
            scrape_page(tab, url) for tab, url in zip(tabs, urls)
        ])

        # Exibir resultados
        for result in results:
            print(f"\n{result['title']}")
            print(f"  URL: {result['url']}")
            print(f"  Artigos: {result['articles_count']}")
            if result['sample_content']:
                print(f"  Amostra: {result['sample_content'][0][:100]}...")

asyncio.run(concurrent_scraping())

Ganho de Desempenho

A raspagem concorrente pode reduzir o tempo total de execução em 5 a 10 vezes em comparação com o processamento sequencial, especialmente para tarefas limitadas por I/O (entrada/saída) como carregamento de página.

Fluxos de Trabalho Coordenados de Múltiplas Abas

Orquestre fluxos de trabalho complexos que exigem a interação de múltiplas abas:

import asyncio
from pydoll.browser.chromium import Chrome
from pydoll.protocol.network.events import NetworkEvent, RequestWillBeSentEvent

async def multi_tab_workflow():
    async with Chrome() as browser:
        # Usar a aba inicial para login
        login_tab = await browser.start()
        await login_tab.go_to('https://app.example.com/login')
        await asyncio.sleep(2)

        username = await login_tab.find(id='username')
        password = await login_tab.find(id='password')

        await username.type_text('admin@example.com')
        await password.type_text('secure_password')

        login_btn = await login_tab.find(id='login')
        await login_btn.click()
        await asyncio.sleep(3)

        # Aba 2: Navegar para a página de exportação de dados
        export_tab = await browser.new_tab('https://app.example.com/export')
        await asyncio.sleep(2)

        export_btn = await export_tab.find(text='Export Data')
        await export_btn.click()

        # Aba 3: Monitorar chamadas de API em um dashboard
        monitor_tab = await browser.new_tab('https://app.example.com/dashboard')
        await monitor_tab.enable_network_events()

        # Rastrear chamadas de API
        api_calls = []
        async def track_api(event: RequestWillBeSentEvent):
            url = event['params']['request']['url']
            if '/api/' in url:
                api_calls.append(url)

        await monitor_tab.on(NetworkEvent.REQUEST_WILL_BE_SENT, track_api)
        await asyncio.sleep(5)

        print(f"Rastreadas {len(api_calls)} chamadas de API:")
        for call in api_calls[:10]:
            print(f"  - {call}")

        # Limpeza
        await login_tab.close()
        await export_tab.close()
        await monitor_tab.close()

asyncio.run(multi_tab_workflow())

Ciclo de Vida e Limpeza da Aba

Fechamento Explícito de Aba

Sempre feche as abas quando terminar de usá-las para liberar recursos do navegador:

import asyncio
from pydoll.browser.chromium import Chrome

async def explicit_cleanup():
    async with Chrome() as browser:
        initial_tab = await browser.start()

        # Criar abas para diferentes tarefas
        tab1 = await browser.new_tab('https://example.com')
        tab2 = await browser.new_tab('https://example.org')

        # Trabalhar com as abas
        await tab1.go_to('https://different-site.com')
        await tab2.take_screenshot('/tmp/screenshot.png')

        # Fechar abas explicitamente
        await tab1.close()
        await tab2.close()

        # Verificar se as abas estão fechadas
        remaining = await browser.get_opened_tabs()
        print(f"Abas restantes: {len(remaining)}")  # Deve ser 1 (inicial)

asyncio.run(explicit_cleanup())

Vazamentos de Memória

Deixar de fechar abas em automações de longa duração pode levar ao esgotamento da memória. Cada aba consome recursos do navegador (memória, handles de arquivo, conexões de rede).

Usando Gerenciadores de Contexto para Limpeza Automática

Embora o Pydoll não forneça um gerenciador de contexto de aba nativo, você pode criar o seu:

import asyncio
from contextlib import asynccontextmanager
from pydoll.browser.chromium import Chrome

@asynccontextmanager
async def managed_tab(browser, url=None):
    """Gerenciador de contexto para limpeza automática de abas."""
    tab = await browser.new_tab(url)
    try:
        yield tab
    finally:
        await tab.close()

async def auto_cleanup_example():
    async with Chrome() as browser:
        initial_tab = await browser.start()

        # Aba fecha automaticamente ao sair do contexto
        async with managed_tab(browser, 'https://example.com') as tab:
            title = await tab.execute_script('return document.title')
            print(f"Título: {title}")

            await tab.take_screenshot('/tmp/page.png')
        # Aba é fechada automaticamente aqui

        tabs = await browser.get_opened_tabs()
        print(f"Abas após sair do contexto: {len(tabs)}")  # 1 (apenas initial_tab)

asyncio.run(auto_cleanup_example())

Limpeza do Navegador

Quando o navegador fecha, todas as abas são fechadas automaticamente:

import asyncio
from pydoll.browser.chromium import Chrome

async def browser_cleanup():
    # Usando gerenciador de contexto - limpeza automática
    async with Chrome() as browser:
        initial_tab = await browser.start()

        # Criar múltiplas abas
        await browser.new_tab('https://example.com')
        await browser.new_tab('https://github.com')
        await browser.new_tab('https://stackoverflow.com')

        tabs = await browser.get_opened_tabs()
        print(f"Abas abertas: {len(tabs)}")  # 4 (inicial + 3 novas)

    # Todas as abas são fechadas automaticamente quando o navegador sai
    print("Navegador fechado, todas as abas limpas")

asyncio.run(browser_cleanup())

Gerenciamento de Estado da Aba

Verificando o Estado da Aba

Consulte vários aspectos do estado atual de uma aba:

import asyncio
from pydoll.browser.chromium import Chrome

async def check_tab_state():
    async with Chrome() as browser:
        tab = await browser.start()
        await tab.go_to('https://example.com')

        # Verificar URL atual
        url = await tab.current_url
        print(f"URL Atual: {url}")

        # Verificar código-fonte da página
        source = await tab.page_source
        print(f"Tamanho do código-fonte: {len(source)} caracteres")

        # Verificar domínios de eventos habilitados
        print(f"Eventos de página habilitados: {tab.page_events_enabled}")
        print(f"Eventos de rede habilitados: {tab.network_events_enabled}")
        print(f"Eventos DOM habilitados: {tab.dom_events_enabled}")

        # Habilitar eventos e verificar novamente
        await tab.enable_network_events()
        print(f"Eventos de rede habilitados: {tab.network_events_enabled}")  # True

asyncio.run(check_tab_state())

Identificação da Aba

Cada aba possui identificadores únicos:

import asyncio
from pydoll.browser.chromium import Chrome

async def tab_identification():
    async with Chrome() as browser:
        tab1 = await browser.start()
        tab2 = await browser.new_tab()

        # Target ID - identificador único atribuído pelo navegador
        print(f"Target ID da Aba 1: {tab1._target_id}")
        print(f"Target ID da Aba 2: {tab2._target_id}")

        # Detalhes da conexão
        print(f"Porta de conexão da Aba 1: {tab1._connection_port}")
        print(f"Porta de conexão da Aba 2: {tab2._connection_port}")

        # ID do contexto do navegador (geralmente None para o contexto padrão)
        print(f"ID do contexto da Aba 1: {tab1._browser_context_id}")
        print(f"ID do contexto da Aba 2: {tab2._browser_context_id}")

asyncio.run(tab_identification())

Funcionalidades Avançadas de Aba

Trazendo Abas para a Frente

Torne uma aba específica visível (trazer para o primeiro plano):

import asyncio
from pydoll.browser.chromium import Chrome

async def bring_to_front():
    async with Chrome() as browser:
        tab1 = await browser.start()
        tab2 = await browser.new_tab('https://github.com')
        tab3 = await browser.new_tab('https://stackoverflow.com')

        # tab3 está atualmente na frente (última criada)
        await asyncio.sleep(2)

        # Trazer tab1 para a frente
        await tab1.bring_to_front()
        print("Aba 1 trazida para a frente")

        await asyncio.sleep(2)

        # Trazer tab2 para a frente
        await tab2.bring_to_front()
        print("Aba 2 trazida para a frente")

asyncio.run(bring_to_front())

Monitoramento de Rede Específico da Aba

Cada aba pode monitorar independentemente sua própria atividade de rede:

import asyncio
from pydoll.browser.chromium import Chrome

async def tab_network_monitoring():
    async with Chrome() as browser:
        # Usar aba inicial para navegação monitorada
        tab1 = await browser.start()
        await tab1.go_to('https://example.com')

        # Criar segunda aba sem monitoramento
        tab2 = await browser.new_tab('https://github.com')

        # Habilitar monitoramento de rede apenas na aba 1
        await tab1.enable_network_events()

        # Navegar em ambas as abas
        await tab1.go_to('https://example.com/page1')
        await tab2.go_to('https://github.com/explore')

        await asyncio.sleep(3)

        # Obter logs de rede apenas da aba 1
        tab1_logs = await tab1.get_network_logs()
        print(f"Requisições de rede da Aba 1: {len(tab1_logs)}")

        # tab2 não tem monitoramento de rede
        print(f"Eventos de rede da Aba 2 habilitados: {tab2.network_events_enabled}")  # False

asyncio.run(tab_network_monitoring())

Manipuladores de Eventos Específicos da Aba

Registre diferentes manipuladores de eventos em diferentes abas:

import asyncio
from pydoll.browser.chromium import Chrome
from pydoll.protocol.page.events import PageEvent

async def tab_specific_events():
    async with Chrome() as browser:
        # Usar aba inicial como primeira aba
        tab1 = await browser.start()
        tab2 = await browser.new_tab()

        # Habilitar eventos de página em ambas
        await tab1.enable_page_events()
        await tab2.enable_page_events()

        # Manipuladores diferentes para cada aba
        async def tab1_handler(event):
            print("Aba 1 carregada!")

        async def tab2_handler(event):
            print("Aba 2 carregada!")

        await tab1.on(PageEvent.LOAD_EVENT_FIRED, tab1_handler)
        await tab2.on(PageEvent.LOAD_EVENT_FIRED, tab2_handler)

        # Navegar em ambas as abas
        await tab1.go_to('https://example.com')
        await tab2.go_to('https://github.com')

        await asyncio.sleep(2)

asyncio.run(tab_specific_events())

Considerações de Desempenho

Cenário Impacto nos Recursos Recomendação
1-5 abas Baixo Gerenciamento direto, sem tratamento especial
5-20 abas Moderado Usar semáforos para limitar concorrência
20-50 abas Alto Processamento em lote, fechar abas agressivamente
50+ abas Muito Alto Considerar processamento sequencial ou múltiplos navegadores

Uso de Memória

Cada aba consome aproximadamente:

  • Memória base: 50-100 MB
  • Com eventos de rede: +10-20 MB
  • Com eventos DOM: +20-50 MB
  • Página complexa (SPA): +100-300 MB

Para 20 abas com monitoramento de rede: ~1.5-3 GB de memória.

Padrões Comuns

Processamento Sequencial com Aba Única

async def sequential_pattern():
    async with Chrome() as browser:
        tab = await browser.start()

        for url in urls:
            await tab.go_to(url)
            # Extrair dados
            await tab.clear_callbacks()  # Limpar eventos

asyncio.run(sequential_pattern())

Processamento Paralelo com Múltiplas Abas

import asyncio
from pydoll.browser.chromium import Chrome

async def parallel_pattern():
    urls = [
        'https://example.com/page1',
        'https://example.com/page2',
        'https://example.com/page3',
        'https://example.com/page4',
    ]

    async with Chrome() as browser:
        # Iniciar navegador e obter aba inicial
        initial_tab = await browser.start()
        # Criar uma aba por URL (reutilizando a inicial para a primeira)
        tabs = [initial_tab] + [await browser.new_tab() for _ in urls[1:]]

        async def process_page(tab, url):
            """Processar uma única página dentro da aba fornecida."""
            try:
                await tab.go_to(url)
                await asyncio.sleep(2)
                title = await tab.evaluate('document.title')
                print(f"[{url}] {title}")
            finally:
                if tab is not initial_tab:
                    await tab.close()

        # Executar todas as abas concorrentemente
        await asyncio.gather(*[
            process_page(tab, url) for tab, url in zip(tabs, urls)
        ])

asyncio.run(parallel_pattern())

Padrão de Pool de Workers

async def worker_pool_pattern():
    async with Chrome() as browser:
        # Usar aba inicial como primeiro worker
        initial_tab = await browser.start()

        # Criar abas worker adicionais (5 workers no total: 1 inicial + 4 novas)
        workers = [initial_tab] + [await browser.new_tab() for _ in range(4)]

        # Distribuir trabalho entre todos os workers
        for url in urls:
            worker = workers[urls.index(url) % len(workers)]
            await worker.go_to(url)
            # Processar...

        # Limpar todos os workers (incluindo aba inicial)
        for worker in workers:
            await worker.close()

asyncio.run(worker_pool_pattern())

Reutilizando a Aba Inicial

Sempre use a aba retornada por browser.start() em vez de deixá-la ociosa. Isso economiza recursos do navegador e melhora o desempenho. Nos exemplos acima, a aba inicial é reutilizada como o primeiro worker ou para a primeira URL do lote.

Veja Também

O gerenciamento de múltiplas abas no Pydoll fornece a base para construir automação de navegador escalável e eficiente. Ao entender o ciclo de vida da aba, o padrão singleton e as melhores práticas, você pode criar fluxos de trabalho de automação robustos que lidam com cenários complexos de múltiplas páginas com facilidade.