Skip to content

Conceitos centrais

O Pydoll é construído sobre algumas decisões de design que moldam como você escreve cada script: sem webdriver, uma API em versão síncrona e assíncrona, interações humanizadas e um sistema de eventos. Esta página explica cada uma delas em um nível prático, para que os guias de tarefas a seguir façam sentido.

Sem webdriver

O Pydoll se conecta diretamente ao navegador pelo Chrome DevTools Protocol (CDP), o mesmo protocolo que move o Chrome DevTools quando você abre o inspetor. Não há um executável webdriver no meio, então não há nada para baixar e nenhum "o chromedriver só suporta o Chrome versão X" para depurar.

graph LR
    subgraph P["Pydoll"]
        direction LR
        P1["Seu código"] --> P2["Pydoll"] --> P3["Navegador (CDP)"]
    end
    subgraph S["Selenium"]
        direction LR
        S1["Seu código"] --> S2["Cliente WebDriver"] --> S3["chromedriver"] --> S4["Navegador"]
    end

Quando você inicia um navegador, o Pydoll lança o Chrome que você já tem instalado com uma porta de depuração remota e abre um WebSocket para o endpoint CDP dele:

from pydoll.sync import Chrome

def main():
    with Chrome() as browser:
        tab = browser.start()
        tab.go_to('https://quotes.toscrape.com')

main()
import asyncio

from pydoll import Chrome


async def main():
    async with Chrome() as browser:
        tab = await browser.start()
        await tab.go_to('https://quotes.toscrape.com')

asyncio.run(main())

Você não gerencia a porta, a conexão nem o processo do navegador; start() faz isso, e o bloco with para o navegador quando você termina.

Os objetos browser e tab

Dois objetos cobrem a maior parte do que você faz. O browser (Chrome ou Edge) é o processo que você lança. A tab, retornada por browser.start(), é o que você controla: navegação, busca de elementos, screenshots, tudo na página acontece através dela.

with Chrome() as browser:
    tab = browser.start()          # a primeira aba
    tab.go_to('https://quotes.toscrape.com')

    second = browser.new_tab()     # abra mais abas a partir do browser
    second.go_to('https://books.toscrape.com')
async with Chrome() as browser:
    tab = await browser.start()          # a primeira aba
    await tab.go_to('https://quotes.toscrape.com')

    second = await browser.new_tab()     # abra mais abas a partir do browser
    await second.go_to('https://books.toscrape.com')

Veja Abas para gerenciar várias abas ao mesmo tempo, e Contextos de navegador para isolar sessões.

Síncrono e assíncrono

O Pydoll oferece uma API em duas formas. Importe de pydoll.sync e cada chamada bloqueia até o navegador responder, então o script se lê de cima para baixo sem nenhum event loop para gerenciar. Importe de pydoll e as mesmas classes são corrotinas: você usa await em cada chamada dentro de uma função async def e inicia o programa com asyncio.run(). A forma síncrona é gerada a partir da assíncrona, então as duas nunca diferem em métodos, argumentos ou padrões, e todo exemplo desta documentação mostra as duas. Há uma terceira porta de entrada para código que já existe: um script Playwright roda no Pydoll trocando um import, veja Traga seu script Playwright.

Onde a forma assíncrona compensa é na concorrência. Navegação e esperas por elementos passam a maior parte do tempo ociosas, então asyncio.gather as executa ao mesmo tempo em vez de uma após a outra. A forma síncrona obtém o mesmo efeito com threads, porque suas chamadas podem ser feitas de várias threads ao mesmo tempo com segurança:

from concurrent.futures import ThreadPoolExecutor

from pydoll.sync import Chrome


def title_of(browser, url):
    tab = browser.new_tab(url)
    title = tab.title()
    tab.close()
    return title


def main():
    urls = [
        'https://quotes.toscrape.com/page/1/',
        'https://quotes.toscrape.com/page/2/',
        'https://quotes.toscrape.com/page/3/',
    ]
    with Chrome() as browser:
        browser.start()
        with ThreadPoolExecutor() as pool:
            titles = list(pool.map(lambda url: title_of(browser, url), urls))
        print(titles)

main()
import asyncio

from pydoll import Chrome


async def title_of(browser, url):
    tab = await browser.new_tab(url)
    title = await tab.title()
    await tab.close()
    return title


async def main():
    urls = [
        'https://quotes.toscrape.com/page/1/',
        'https://quotes.toscrape.com/page/2/',
        'https://quotes.toscrape.com/page/3/',
    ]
    async with Chrome() as browser:
        await browser.start()
        titles = await asyncio.gather(*(title_of(browser, url) for url in urls))
        print(titles)

asyncio.run(main())

As três páginas carregam concorrentemente, então tudo leva mais ou menos o tempo da página mais lenta sozinha, não a soma das três.

Novo em Python assíncrono?

Se async, await e gather não são familiares, leia Python assíncrono na prática primeiro. Ele cobre apenas o suficiente de asyncio para você ficar confortável com o restante destes guias.

Interações humanizadas

Por padrão, um clique cai no centro de um elemento e a digitação envia as teclas uma atrás da outra, na velocidade em que o navegador as aceita. Passe humanize=True e o Pydoll move o cursor por um caminho curvo antes de clicar e digita com timing variável, incluindo o eventual erro de digitação corrigido:

search = tab.find(id='search')
search.type_text('web scraping', humanize=True)
search.click(humanize=True)
search = await tab.find(id='search')
await search.type_text('web scraping', humanize=True)
await search.click(humanize=True)

A humanização é opcional por interação, então você a usa onde um site observa o comportamento e a dispensa onde velocidade pura importa. Veja Interações parecidas com humanas para o modelo de timing, e Teclado e Mouse para as APIs de entrada completas.

Orientado a eventos

Em vez de consultar a página em um loop, você pode assinar eventos do navegador e executar um callback quando eles disparam. É assim que você captura tráfego de rede, reage a navegações ou espera por uma requisição específica:

import time
from functools import partial

from pydoll.sync import Chrome, NetworkEvent

def on_request(tab, event):
    url = event['params']['request']['url']
    if '/api/' in url:
        print(f'API call: {url}')

def main():
    with Chrome() as browser:
        tab = browser.start()

        tab.enable_network_events()
        tab.on(NetworkEvent.REQUEST_WILL_BE_SENT, partial(on_request, tab))

        tab.go_to('https://quotes.toscrape.com')
        time.sleep(2)

main()
import asyncio
from functools import partial

from pydoll import Chrome, NetworkEvent


async def on_request(tab, event):
    url = event['params']['request']['url']
    if '/api/' in url:
        print(f'API call: {url}')


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

        await tab.enable_network_events()
        await tab.on(NetworkEvent.REQUEST_WILL_BE_SENT, partial(on_request, tab))

        await tab.go_to('https://quotes.toscrape.com')
        await asyncio.sleep(2)

asyncio.run(main())

Ative apenas os domínios de evento que você usa, e desative-os quando terminar. Veja Eventos para o modelo completo e Monitoramento de rede para captura de tráfego.

Funciona em todos os navegadores Chromium

A mesma API controla qualquer navegador Chromium. O Chrome é o alvo principal; o Edge tem suporte completo; outras builds Chromium funcionam apontando binary_location para elas.

from pydoll.sync import Chrome, ChromiumOptions, Edge

# Chrome
with Chrome() as browser:
    tab = browser.start()

# Edge
with Edge() as browser:
    tab = browser.start()

# Qualquer outra build Chromium (Brave, Vivaldi, Opera, ...)
options = ChromiumOptions()
options.binary_location = '/path/to/brave-browser'
with Chrome(options=options) as browser:
    tab = browser.start()
from pydoll import Chrome, ChromiumOptions, Edge

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

# Edge
async with Edge() as browser:
    tab = await browser.start()

# Qualquer outra build Chromium (Brave, Vivaldi, Opera, ...)
options = ChromiumOptions()
options.binary_location = '/path/to/brave-browser'
async with Chrome(options=options) as browser:
    tab = await browser.start()

Próximos passos