Skip to main content

Python client for the Undetecta API - Anti-detection web scraping made simple

Project description

undetecta

Python client for the Undetecta API - Anti-detection web scraping made simple.

PyPI version Python License: MIT

Features

  • Full Type Hints - Comprehensive Pydantic v2 types for all API responses and requests
  • Web Scraping - Scrape URLs with advanced options (screenshots, branding extraction, actions)
  • Web Search - Search the web with result scraping capabilities
  • Automatic Retries - Built-in retry logic with exponential backoff for transient failures
  • Error Handling - Custom error classes for different error types
  • Async First - Built on httpx for async/await operations
  • Context Manager - Automatic resource cleanup with async context manager support

Installation

pip install undetecta

Or using uv:

uv add undetecta

Quick Start

import asyncio
from undetecta import UndetectaClient

async def main():
    async with UndetectaClient(api_key="your-api-key") as client:
        result = await client.scrape(url="https://example.com")
        print(result.markdown)

asyncio.run(main())

Configuration

from undetecta import UndetectaClient

client = UndetectaClient(
    api_key="your-api-key",                    # Required
    base_url="https://api.undetecta.com",     # Optional, defaults to production
    timeout=60000,                             # Optional, default 60 seconds (ms)
    max_retries=3,                             # Optional, default 3 retries
)

API Reference

Scraping

scrape(url, formats=None, **kwargs)

Scrape a URL and return the results.

result = await client.scrape(
    url="https://example.com",
    formats=["markdown", "screenshot"],
    wait_for_selector=".main-content",
)

Scrape Options:

Option Type Default Description
url str Required URL to scrape
formats list[str] ["markdown"] Output formats: html, rawHtml, markdown, links, screenshot, branding
headless bool True Run browser in headless mode
proxy str - Proxy URL to use
wait_for_selector str - CSS selector to wait for
wait_for int - Time to wait in ms
wait_until str - Wait until page load event (load, domcontentloaded, networkidle)
timeout int 30000 Request timeout in ms
mobile bool - Use mobile viewport
actions list[Action] - Browser actions to perform
screenshot_options ScreenshotOptions - Screenshot configuration

Browser Actions:

result = await client.scrape(
    url="https://example.com",
    actions=[
        {"type": "click", "selector": ".cookie-accept"},
        {"type": "fill", "selector": "input[name='email']", "value": "test@example.com"},
        {"type": "wait", "options": {"duration": 1000}},
        {"type": "scroll", "options": {"direction": "down", "amount": 500}},
    ],
)

Response:

class ScrapeJobResponse(BaseModel):
    id: str
    status: JobStatus  # pending, running, completed, failed, stopped
    created_at: str
    completed_at: str | None
    metadata: ScrapeMetadata | None
    html: str | None
    raw_html: str | None
    markdown: str | None
    links: list[str] | None
    screenshot: str | None  # base64 encoded image
    branding: BrandingProfile | None
    error: ScrapeJobError | None

Search

search(query, limit=None, **kwargs)

Perform a web search and return the results.

result = await client.search(
    query="web scraping tools",
    limit=10,
    scrape_options={"formats": ["markdown"]},
)

for item in result.web:
    print(item.title)
    print(item.markdown)  # Scraped content

Search Options:

Option Type Default Description
query str Required Search query
limit int 10 Number of results
categories list[str] - github, research, pdf
lang str - Language code
country str - Country code
scrape_options SearchScrapeOptions - Options for scraping results
timeout int - Timeout in ms

Response:

class SearchJobResponse(BaseModel):
    id: str
    status: JobStatus
    created_at: str
    completed_at: str | None
    web: list[WebSearchResult] | None
    news: list[NewsSearchResult] | None
    images: list[ImageSearchResult] | None
    error: SearchJobError | None

Health Check

health()

Check the API health status.

health = await client.health()
print(health.status)  # 'ok'

Error Handling

The client provides custom error classes for different error types:

from undetecta.errors import (
    ApiKeyError,
    RateLimitError,
    TimeoutError,
    NetworkError,
    ValidationError,
    NotFoundError,
    UndetectaError,
)

async with UndetectaClient(api_key="your-api-key") as client:
    try:
        result = await client.scrape(url="https://example.com")
    except ApiKeyError as e:
        print(f"Invalid API key: {e.message}")
    except RateLimitError as e:
        print(f"Rate limit exceeded: {e.message}")
    except TimeoutError as e:
        print(f"Request timed out: {e.message}")
    except NetworkError as e:
        print(f"Network error: {e.message}")
    except ValidationError as e:
        print(f"Validation error: {e.message}")
    except NotFoundError as e:
        print(f"Resource not found: {e.message}")
    except UndetectaError as e:
        print(f"API error: {e.code} - {e.message}")
        if e.status_code:
            print(f"Status: {e.status_code}")

Error Classes

Error Class Status Code Description
ApiKeyError 401 Invalid or missing API key
ValidationError 400 Request validation failed
NotFoundError 404 Resource not found
RateLimitError 429 Rate limit exceeded
UndetectaError Variable Base error class (includes status code for server errors)
TimeoutError - Request timed out
NetworkError - Network connection failed

Advanced Examples

Extract Branding

result = await client.scrape(
    url="https://example.com",
    formats=["branding"],
)

print(result.branding.colors.primary)
print(result.branding.typography.font_families)
print(result.branding.components.button_primary)

Screenshot with Custom Options

from undetecta.types import ScreenshotOptions, ScreenshotClip, ScreenshotViewport

result = await client.scrape(
    url="https://example.com",
    formats=["screenshot"],
    screenshot_options=ScreenshotOptions(
        full_page=False,
        format="png",
        quality=90,
        clip=ScreenshotClip(x=0, y=0, width=1920, height=1080),
        selector=".main-content",
        viewport=ScreenshotViewport(width=1920, height=1080),
    ),
)

# Save screenshot
import base64
with open("screenshot.png", "wb") as f:
    f.write(base64.b64decode(result.screenshot))

Search with Result Scraping

result = await client.search(
    query="typescript web scraping",
    limit=5,
    scrape_options={
        "formats": ["markdown", "links"],
        "only_main_content": True,
    },
)

for item in result.web or []:
    print(item.title)
    print(item.url)
    if item.markdown:
        print(item.markdown[:200] + "...")

Custom Error Handling with Retry

import asyncio

async def scrape_with_retry(url, max_retries=3):
    async with UndetectaClient(api_key="your-api-key") as client:
        for attempt in range(max_retries):
            try:
                return await client.scrape(url=url)
            except RateLimitError as e:
                if attempt < max_retries - 1:
                    delay = 2 ** attempt  # Exponential backoff
                    await asyncio.sleep(delay)
                    continue
                raise

Development

Install development dependencies:

uv sync

Run linting and formatting:

uv run ruff check src
uv run ruff format src

Run type checking:

uv run pyright src

Run tests:

uv run pytest

Requirements

  • Python: >= 3.10
  • httpx: >= 0.27.0
  • pydantic: >= 2.0.0

Schema Documentation

For information about how the Pydantic models relate to the JSON schemas and how to regenerate types, see SCHEMAS.md.

License

MIT © Undetecta

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

undetecta-0.2.0.tar.gz (84.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

undetecta-0.2.0-py3-none-any.whl (14.8 kB view details)

Uploaded Python 3

File details

Details for the file undetecta-0.2.0.tar.gz.

File metadata

  • Download URL: undetecta-0.2.0.tar.gz
  • Upload date:
  • Size: 84.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.0

File hashes

Hashes for undetecta-0.2.0.tar.gz
Algorithm Hash digest
SHA256 deb6630f11ea61f46f032e2c96cfd84bb5872102e9986cf32f73284cc2b141e7
MD5 443792796b2d8760545652bf2c21beca
BLAKE2b-256 4036d99a6df90de1fd2c7b663cf3ac6c72189e69f52eba10eaf379b592323eff

See more details on using hashes here.

File details

Details for the file undetecta-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: undetecta-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 14.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.0

File hashes

Hashes for undetecta-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 483dd6e95cae11f51bd05fce66b797e97c41c7439ca663b75e929516471c7986
MD5 1ca38cc0195888b3aace2bcf90252f9e
BLAKE2b-256 7d4dad951d4cfa242d769f428a8a8487b89b0d5d9605250b05bf1dc7f514d85c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page