Back to rankings

autoscrape-labs/pydoll

Pythonpydoll.tech

Pydoll is a library for automating chromium-based browsers without a WebDriver, offering realistic interactions.

cdpchromiumplaywrightpuppeteerrecaptcha-v3seleniumwebdriverbrowser-automationanti-detectionautomationcrawlere2e-tests
Star Growth
Stars
7k
Forks
391
Weekly Growth
Issues
13
2k4k6k
Oct 2024May 2025Dec 2025Jul 2026
ArtifactsPyPIpip install pydoll
README

Pydoll Logo

The stealth-first browser automation library for Python.
No WebDriver, no navigator.webdriver flag, built to look human.

Tests Ruff CI MyPy CI Python >= 3.10 Ask DeepWiki

Documentation · Getting Started · Features · Support

Pydoll is a stealth-first browser automation library for Python. It talks straight to the Chrome DevTools Protocol over WebSocket, so there's no WebDriver binary and no navigator.webdriver flag to give you away. It clicks, types, and scrolls like a real person, which is often enough to sail through the bot-protection that stops ordinary automation cold. All of it behind a clean, async-native, fully typed API.

Be a good human, give it a star ⭐

No star, no bug fixes. Just kidding. Or not.

Why Pydoll?

  • Looks human, not like a bot: Human-like mouse movement (Bezier curves), realistic typing, and scroll physics. Enough to pass behavioral challenges like Cloudflare Turnstile or reCAPTCHA v3, depending on your browser and IP reputation.
  • Zero WebDrivers: A direct CDP connection over WebSocket. No driver binary, no navigator.webdriver flag, no version-matching headaches.
  • Stealth built in: Realistic interactions plus granular browser preference control for fingerprint management.
  • Async and typed: Built on asyncio from the ground up, 100% type-checked with mypy. Full IDE autocompletion and static error checking.
  • Network control: Intercept requests to block ads/trackers, monitor traffic for API discovery, and make authenticated HTTP requests that inherit the browser session.
  • Shadow DOM and iframes: Full support for shadow roots (including closed) and cross-origin iframes. Discover, query, and interact with elements inside them using the same API.
  • Structured extraction: Define a Pydantic model, call tab.extract(), and get typed, validated data back. No manual element-by-element querying.

[!NOTE] A word from the maintainer. Pydoll is currently maintained by a single person, and I'm a bit stretched at the moment, so new releases and replies to issues may take a little longer than usual. To be clear: the project is not dead, and it is not going anywhere. Development continues; it's just moving at a calmer pace for now.

A goal to aim for: once the project reaches 10k stars, I plan to ship Firefox support, a big step that opens up a whole new range of possibilities for the library. Momentum like that is exactly the kind of incentive that makes a feature this large worth taking on, so if you'd like to see it happen, that's the push it needs.

Top Sponsors

The Web Scraping Club

Read a full review of Pydoll on The Web Scraping Club, the #1 newsletter dedicated to web scraping.

nodemaven

The most reliable proxy provider with the Highest Quality IP on the market. Best solution for automation, web scraping, SEO research, and social media management.

Sponsors

Thordata CapSolver LambdaTest

Learn more about our sponsors · Become a sponsor

Installation

pip install pydoll-python

No WebDriver binaries or external dependencies required.

Getting Started

1. Solving Cloudflare Turnstile

Pydoll gets you past Cloudflare Turnstile the same way a person does: by placing a realistic, humanized click on the widget. It simulates a real user (humanized clicks and movements) and works to make the browser look genuine, so Turnstile assigns a high enough trust score to accept the click. Whether it succeeds depends on your browser and IP reputation.

import asyncio

from pydoll.browser.chromium import Chrome

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

        # Waits for the Turnstile widget, performs a realistic click,
        # and continues once it settles.
        async with tab.expect_and_bypass_cloudflare_captcha():
            await tab.go_to('https://site-with-turnstile.com')

        print('Turnstile handled, continuing...')

asyncio.run(solve_turnstile())

Pydoll passing a Cloudflare Turnstile challenge with a humanized click

Pydoll getting past a Cloudflare Turnstile challenge with a realistic, humanized click.

[!NOTE] Despite the method name, this isn't a magic bypass. Pydoll performs the same click a real user would; whether it passes depends on your environment (browser fingerprint and IP reputation). See the Turnstile docs for details.

2. Stealthy Automation

When you need to navigate, get past challenges, or interact with dynamic UI, Pydoll's imperative API handles it. Pass humanize=True to add human-like timing for anti-bot evasion.

import asyncio

from pydoll.browser import Chrome
from pydoll.constants import Key

async def google_search(query: str):
    async with Chrome() as browser:
        tab = await browser.start()
        await browser.set_window_maximized()
        tab.mouse.debug = True
        await tab.go_to('https://www.google.com')
        # Find elements and interact with human-like timing
        search_box = await tab.find(tag_name='textarea', name='q')
        await search_box.type_text(query, humanize=True)
        await tab.keyboard.press(Key.ENTER)

        first_result = await tab.find(
            tag_name='h3',
            text='autoscrape-labs/pydoll',
            timeout=10,
        )
        await first_result.click(humanize=True)
        await asyncio.sleep(5)
        print(f"Page loaded: {await tab.title}")

asyncio.run(google_search('pydoll site:github.com'))

Pydoll running a humanized Google search

Features

Structured Data Extraction (Pydantic)

Define what you want with a Pydantic model and Pydoll maps the DOM straight into typed, validated Python objects, no manual element-by-element querying. Models support CSS/XPath auto-detection, HTML attribute targeting, custom transforms, and nested models.

import asyncio

from pydoll.browser.chromium import Chrome
from pydoll.extractor import ExtractionModel, Field

class Quote(ExtractionModel):
    text: str = Field(selector='.text', description='The quote text')
    author: str = Field(selector='.author', description='Who said it')
    tags: list[str] = Field(selector='.tag', description='Tags')


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

        quotes = await tab.extract_all(Quote, scope='.quote', timeout=5)

        for q in quotes:
            print(f'{q.author}: {q.text}')  # fully typed, IDE autocomplete works
            print(q.model_dump_json())       # pydantic serialization built-in

asyncio.run(extract_quotes())
Humanized Mouse Movement

Mouse operations can produce human-like cursor movement when you pass humanize=True:

  • Bezier curve paths with asymmetric control points
  • Fitts's Law timing: duration scales with distance
  • Minimum-jerk velocity: bell-shaped speed profile
  • Physiological tremor: Gaussian noise scaled with velocity
  • Overshoot correction: ~70% chance on fast movements, then corrects back
await tab.mouse.move(500, 300, humanize=True)
await tab.mouse.click(500, 300, humanize=True)
await tab.mouse.drag(100, 200, 500, 400, humanize=True)

button = await tab.find(id='submit')
await button.click(humanize=True)

# Default is fast, non-humanized movement
await tab.mouse.click(500, 300)

Mouse Control Docs

Shadow DOM Support

Full Shadow DOM support, including closed shadow roots. Because Pydoll operates at the CDP level (below JavaScript), the closed mode restriction doesn't apply.

shadow = await element.get_shadow_root()
button = await shadow.query('.internal-btn')
await button.click()

# Discover all shadow roots on the page
shadow_roots = await tab.find_shadow_roots()
for sr in shadow_roots:
    checkbox = await sr.query('input[type="checkbox"]', raise_exc=False)
    if checkbox:
        await checkbox.click()

Highlights:

  • Closed shadow roots work without workarounds
  • find_shadow_roots() discovers every shadow root on the page
  • timeout parameter for polling until shadow roots appear
  • deep=True traverses cross-origin iframes (OOPIFs)
  • Standard find(), query(), click() API inside shadow roots

Shadow DOM Docs

HAR Network Recording

Record network activity during a browser session and export as HAR 1.2. Replay recorded requests to reproduce exact API sequences.

from pydoll.browser.chromium import Chrome

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

    async with tab.request.record() as capture:
        await tab.go_to('https://example.com')

    capture.save('flow.har')
    print(f'Captured {len(capture.entries)} requests')

    responses = await tab.request.replay('flow.har')

HAR Recording Docs

Page Bundles

Save the current page and all its assets (CSS, JS, images, fonts) as a .zip bundle for offline viewing. Optionally inline everything into a single HTML file.

await tab.save_bundle('page.zip')
await tab.save_bundle('page-inline.zip', inline_assets=True)

Screenshots, PDFs & Bundles Docs

Hybrid Automation (UI + API)

Use UI automation to pass login flows (CAPTCHAs, JS challenges), then switch to tab.request for fast API calls that inherit the full browser session: cookies, headers, and all.

# Log in via UI
await tab.go_to('https://my-site.com/login')
await (await tab.find(id='username')).type_text('user')
await (await tab.find(id='password')).type_text('pass123')
await (await tab.find(id='login-btn')).click()

# Make authenticated API calls using the browser session
response = await tab.request.get('https://my-site.com/api/user/profile')
user_data = response.json()

Hybrid Automation Docs

Network Interception and Monitoring

Monitor traffic for API discovery or intercept requests to block ads, trackers, and unnecessary resources.

import asyncio
from pydoll.browser.chromium import Chrome
from pydoll.protocol.fetch.events import FetchEvent, RequestPausedEvent
from pydoll.protocol.network.types import ErrorReason

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

        async def block_resource(event: RequestPausedEvent):
            request_id = event['params']['requestId']
            resource_type = event['params']['resourceType']

            if resource_type in ['Image', 'Stylesheet']:
                await tab.fail_request(request_id, ErrorReason.BLOCKED_BY_CLIENT)
            else:
                await tab.continue_request(request_id)

        await tab.enable_fetch_events()
        await tab.on(FetchEvent.REQUEST_PAUSED, block_resource)

        await tab.go_to('https://example.com')
        await asyncio.sleep(3)
        await tab.disable_fetch_events()

asyncio.run(block_images())

Network Monitoring | Request Interception

Browser Fingerprint Control

Granular control over browser preferences: hundreds of internal Chrome settings for building consistent fingerprints.

options = ChromiumOptions()

options.browser_preferences = {
    'profile': {
        'default_content_setting_values': {
            'notifications': 2,
            'geolocation': 2,
        },
        'password_manager_enabled': False
    },
    'intl': {
        'accept_languages': 'en-US,en',
    },
    'browser': {
        'check_default_browser': False,
    }
}

Browser Preferences Guide

Concurrency, Contexts and Remote Connections

Manage multiple tabs and browser contexts (isolated sessions) concurrently. Connect to browsers running in Docker or remote servers.

async def scrape_page(url, tab):
    await tab.go_to(url)
    return await tab.title

async def concurrent_scraping():
    async with Chrome() as browser:
        tab_google = await browser.start()
        tab_ddg = await browser.new_tab()

        results = await asyncio.gather(
            scrape_page('https://google.com/', tab_google),
            scrape_page('https://duckduckgo.com/', tab_ddg)
        )
        print(results)

Multi-Tab Management | Remote Connections

Retry Decorator

The @retry decorator supports custom recovery logic between attempts (e.g., refreshing the page, rotating proxies) and exponential backoff.

from pydoll.decorators import retry
from pydoll.exceptions import ElementNotFound, NetworkError

@retry(
    max_retries=3,
    exceptions=[ElementNotFound, NetworkError],
    on_retry=my_recovery_function,
    exponential_backoff=True
)
async def scrape_product(self, url: str):
    # scraping logic
    ...

Retry Decorator Docs


Contributing

Contributions are welcome. See CONTRIBUTING.md for guidelines.

Support

If you find Pydoll useful, consider sponsoring the project on GitHub.

License

MIT License

Related repositories
PostHog/posthog

:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.

PythonPyPIOtheranalyticspython
posthog.com
37.2k3.1k
lightpanda-io/browser

Lightpanda: the headless browser designed for AI and automation

ZigGNU Affero General Public License v3.0browsercdp
lightpanda.io
32.1k1.4k
h4ckf0r0day/obscura

The headless browser for AI agents and web scraping

Rustcrates.ioApache License 2.0antidetectantidetect-browser
obscura.sh
19.5k1.4k
browser-use/browser-harness

Browser Harness | Self-healing harness that enables LLMs to complete any task.

PythonPyPIMIT Licenseai-agentbrowser-agent
browser-harness.com
16.2k1.5k
pinchtab/pinchtab

High-performance browser automation bridge and multi-instance orchestrator with advanced stealth injection and real-time dashboard.

GoGo ModulesMIT Licensebrowser-automationcdp
9.4k702
go-rod/rod

A Chrome DevTools Protocol driver for web automation and scraping.

GoGo ModulesMIT Licensecdpchrome-headless
go-rod.github.io
7k476
rudderlabs/rudder-server

Privacy and Security focused Segment-alternative, in Golang and React

GoGo ModulesOtherprivacywarehouse-management
rudderstack.com
4.5k57
pimcore/pimcore

Core Framework for the Open Core Data & Experience Management Platform (PIM, MDM, CDP, DAM, DXP/CMS & Digital Commerce)

PHPPackagistOtherpimcorecms
pimcore.com
3.8k1.5k
linuxfoundation/crowd.dev

LFX Community Data Platform (CDP)

TypeScriptnpmApache License 2.0communitycdp
lfx.linuxfoundation.org
3.4k730
CrowdDotDev/crowd.dev

LFX Community Management

TypeScriptnpmApache License 2.0communitycdp
lfx.linuxfoundation.org
3.3k741
zhizhuodemao/js-reverse-mcp

AI Agent-first JS 逆向 MCP Server:有头 Chrome 调试、断点、网络/WebSocket 分析、Patchright 反检测,可选 CloakBrowser。

TypeScriptnpmApache License 2.0anti-detectionbrowser-automation
2.3k301
Multiwoven/multiwoven

🔥🔥🔥 Open source Reverse ETL - alternative to hightouch and census.

RubyRubyGemsGNU Affero General Public License v3.0data-engineeringreverse-etl
aisquared.ai/enterprise/
1.7k92