D4Vinci/Scrapling · error · ValueError

CDP URL must use 'ws://', 'wss://', 'http://', or 'https://'

Error message

CDP URL must use 'ws://', 'wss://', 'http://', or 'https://' scheme

What it means

Raised when the `cdp_url` config value does not start with ws://, wss://, http://, or https://. Scrapling validates this up front because Playwright's connect_over_cdp requires a proper endpoint URL, and the check catches inputs like 'localhost:9222' or 'chromium:9222' before a confusing Playwright failure.

Source

Thrown at scrapling/engines/_browsers/_validators.py:112

    dns_over_https: bool = False

    def __post_init__(self):  # pragma: no cover
        """Custom validation after msgspec validation"""
        if self.page_action and not callable(self.page_action):
            raise TypeError(f"page_action must be callable, got {type(self.page_action).__name__}")
        if self.page_setup and not callable(self.page_setup):
            raise TypeError(f"page_setup must be callable, got {type(self.page_setup).__name__}")
        if self.proxy and self.proxy_rotator:
            raise ValueError(
                "Cannot use 'proxy_rotator' together with 'proxy'. "
                "Use either a static proxy or proxy rotation, not both."
            )
        if self.proxy:
            self.proxy = construct_proxy_dict(self.proxy)
        if self.cdp_url:
            cdp_msg = _is_invalid_cdp_url(self.cdp_url)
            if cdp_msg:
                raise ValueError(cdp_msg)

        if not self.cookies:
            self.cookies = []
        if not self.extra_flags:
            self.extra_flags = []
        if not self.selector_config:
            self.selector_config = {}
        if not self.additional_args:
            self.additional_args = {}
        if not self.capture_xhr:
            self.capture_xhr = None

        if self.init_script is not None:
            validation_msg = _is_invalid_file_path(self.init_script)
            if validation_msg:
                raise ValueError(validation_msg)

        if self.executable_path is not None:

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Prefix the endpoint with a supported scheme, e.g. cdp_url='http://localhost:9222'
  2. For WebSocket endpoints use the full ws://host:port/devtools/browser/<uuid> URL from http://host:port/json/version
  3. Do not pass a bare host:port

Example fix

// before
session = StealthySession(cdp_url='localhost:9222')

// after
session = StealthySession(cdp_url='http://localhost:9222')
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED = ('ws://', 'wss://', 'http://', 'https://')
assert cdp_url.startswith(ALLOWED), f'cdp_url must start with one of {ALLOWED}'

Type guard

def is_valid_cdp_url(u: str) -> bool:
    return isinstance(u, str) and u.startswith(('ws://', 'wss://', 'http://', 'https://'))

Try / catch

try:
    session = StealthySession(cdp_url=ep)
except ValueError as e:
    if 'CDP URL' in str(e):
        ep = 'http://' + ep  # normalize host:port input
        session = StealthySession(cdp_url=ep)
    else:
        raise

Prevention

When it happens

Trigger: Passing cdp_url='localhost:9222' (no scheme), cdp_url='9222', or a pathlike endpoint without protocol. Note the ws endpoint usually looks like ws://host:port/devtools/browser/<id>.

Common situations: Copy-pasting the host:port from Chrome's --remote-debugging-port output without prefixing http://, or using the DevTools page URL instead of the WebSocket endpoint.

Related errors


AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14). Data as JSON: /api/errors/b52000f7df938c9a. Report an issue: GitHub.