"""Browser profile definitions for :mod:`pyfetcher`.
Purpose:
Define complete browser identity profiles that bundle User-Agent strings
with matching Client Hints (Sec-CH-UA-*), Sec-Fetch-* metadata, and Accept
headers into coherent, realistic browser identities. Profiles ensure that
all identity-related headers are internally consistent, which is critical
for avoiding detection by anti-bot systems.
Design:
- Each profile represents a specific browser + version + platform combination.
- Headers within a profile are guaranteed to be mutually consistent (e.g.
a Chrome UA will have matching Sec-CH-UA and platform values).
- Profiles are immutable dataclass instances for safety and hashability.
- The ``get_profile`` function selects profiles by name; ``list_profiles``
enumerates all available profile names.
Examples:
::
>>> profile = get_profile("chrome_win")
>>> "Chrome" in profile.user_agent
True
>>> profile.platform
'Windows'
"""
from __future__ import annotations
from dataclasses import dataclass, field
[docs]
@dataclass(frozen=True, slots=True)
class BrowserProfile:
"""A complete browser identity profile.
Bundles all headers that form a browser's identity fingerprint into
a single coherent object. Anti-bot systems check for consistency
across these headers, so they must all agree on browser type, version,
and platform.
Args:
name: Unique profile identifier (e.g. ``'chrome_win'``).
browser: Browser family name (e.g. ``'chrome'``, ``'firefox'``, ``'safari'``).
platform: Operating system name (e.g. ``'Windows'``, ``'macOS'``, ``'Linux'``).
mobile: Whether this is a mobile browser profile.
user_agent: The User-Agent header string.
sec_ch_ua: The ``Sec-CH-UA`` client hint header value.
sec_ch_ua_full_version_list: The ``Sec-CH-UA-Full-Version-List`` header.
sec_ch_ua_mobile: The ``Sec-CH-UA-Mobile`` header (``'?0'`` or ``'?1'``).
sec_ch_ua_platform: The ``Sec-CH-UA-Platform`` header (quoted string).
sec_ch_ua_platform_version: The ``Sec-CH-UA-Platform-Version`` header.
sec_ch_ua_model: The ``Sec-CH-UA-Model`` header (empty for desktop).
accept: The ``Accept`` header for document requests.
accept_language_options: Pool of ``Accept-Language`` values for randomization.
accept_encoding: The ``Accept-Encoding`` header value.
sec_fetch_dest: Default ``Sec-Fetch-Dest`` value.
sec_fetch_mode: Default ``Sec-Fetch-Mode`` value.
sec_fetch_site: Default ``Sec-Fetch-Site`` value.
sec_fetch_user: Default ``Sec-Fetch-User`` value.
upgrade_insecure_requests: The ``Upgrade-Insecure-Requests`` header.
extra_headers: Additional browser-specific headers.
Examples:
::
>>> profile = BrowserProfile(
... name="test", browser="chrome", platform="Windows",
... user_agent="Mozilla/5.0", sec_ch_ua='"Chrome";v="133"',
... sec_ch_ua_full_version_list='"Chrome";v="133.0"',
... sec_ch_ua_mobile="?0", sec_ch_ua_platform='"Windows"',
... sec_ch_ua_platform_version='"10.0.0"',
... )
>>> profile.browser
'chrome'
"""
name: str
browser: str
platform: str
mobile: bool = False
user_agent: str = ""
sec_ch_ua: str = ""
sec_ch_ua_full_version_list: str = ""
sec_ch_ua_mobile: str = "?0"
sec_ch_ua_platform: str = '""'
sec_ch_ua_platform_version: str = '""'
sec_ch_ua_model: str = '""'
accept: str = (
"text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,image/apng,*/*;q=0.8"
)
accept_language_options: tuple[str, ...] = (
"en-US,en;q=0.9",
"en-US,en;q=0.8",
"en-GB,en;q=0.9",
"en-CA,en;q=0.9",
"en-AU,en;q=0.9",
)
accept_encoding: str = "gzip, deflate, br, zstd"
sec_fetch_dest: str = "document"
sec_fetch_mode: str = "navigate"
sec_fetch_site: str = "none"
sec_fetch_user: str = "?1"
upgrade_insecure_requests: str = "1"
extra_headers: dict[str, str] = field(default_factory=dict)
# ---------------------------------------------------------------------------
# Chrome profiles
# ---------------------------------------------------------------------------
CHROME_WIN = BrowserProfile(
name="chrome_win",
browser="chrome",
platform="Windows",
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/133.0.0.0 Safari/537.36"
),
sec_ch_ua='"Google Chrome";v="133", "Chromium";v="133", "Not(A:Brand";v="99"',
sec_ch_ua_full_version_list=(
'"Google Chrome";v="133.0.6943.98", '
'"Chromium";v="133.0.6943.98", '
'"Not(A:Brand";v="99.0.0.0"'
),
sec_ch_ua_mobile="?0",
sec_ch_ua_platform='"Windows"',
sec_ch_ua_platform_version='"10.0.0"',
sec_ch_ua_model='""',
extra_headers={"sec-ch-prefers-color-scheme": "light"},
)
CHROME_MAC = BrowserProfile(
name="chrome_mac",
browser="chrome",
platform="macOS",
user_agent=(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/133.0.0.0 Safari/537.36"
),
sec_ch_ua='"Google Chrome";v="133", "Chromium";v="133", "Not(A:Brand";v="99"',
sec_ch_ua_full_version_list=(
'"Google Chrome";v="133.0.6943.98", '
'"Chromium";v="133.0.6943.98", '
'"Not(A:Brand";v="99.0.0.0"'
),
sec_ch_ua_mobile="?0",
sec_ch_ua_platform='"macOS"',
sec_ch_ua_platform_version='"14.5.0"',
sec_ch_ua_model='""',
extra_headers={"sec-ch-prefers-color-scheme": "light"},
)
CHROME_LINUX = BrowserProfile(
name="chrome_linux",
browser="chrome",
platform="Linux",
user_agent=(
"Mozilla/5.0 (X11; Linux x86_64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/133.0.0.0 Safari/537.36"
),
sec_ch_ua='"Google Chrome";v="133", "Chromium";v="133", "Not(A:Brand";v="99"',
sec_ch_ua_full_version_list=(
'"Google Chrome";v="133.0.6943.98", '
'"Chromium";v="133.0.6943.98", '
'"Not(A:Brand";v="99.0.0.0"'
),
sec_ch_ua_mobile="?0",
sec_ch_ua_platform='"Linux"',
sec_ch_ua_platform_version='"6.5.0"',
sec_ch_ua_model='""',
extra_headers={"sec-ch-prefers-color-scheme": "light"},
)
CHROME_ANDROID = BrowserProfile(
name="chrome_android",
browser="chrome",
platform="Android",
mobile=True,
user_agent=(
"Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/133.0.6943.98 Mobile Safari/537.36"
),
sec_ch_ua='"Google Chrome";v="133", "Chromium";v="133", "Not(A:Brand";v="99"',
sec_ch_ua_full_version_list=(
'"Google Chrome";v="133.0.6943.98", '
'"Chromium";v="133.0.6943.98", '
'"Not(A:Brand";v="99.0.0.0"'
),
sec_ch_ua_mobile="?1",
sec_ch_ua_platform='"Android"',
sec_ch_ua_platform_version='"14.0.0"',
sec_ch_ua_model='"Pixel 8 Pro"',
)
# ---------------------------------------------------------------------------
# Firefox profiles
# ---------------------------------------------------------------------------
FIREFOX_WIN = BrowserProfile(
name="firefox_win",
browser="firefox",
platform="Windows",
user_agent=("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0"),
sec_ch_ua="", # Firefox does not send Client Hints
sec_ch_ua_full_version_list="",
sec_ch_ua_mobile="",
sec_ch_ua_platform="",
sec_ch_ua_platform_version="",
sec_ch_ua_model="",
accept=(
"text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8"
),
accept_language_options=(
"en-US,en;q=0.5",
"en-GB,en;q=0.5",
"en-CA,en;q=0.5",
),
extra_headers={"dnt": "1", "priority": "u=0, i"},
)
FIREFOX_MAC = BrowserProfile(
name="firefox_mac",
browser="firefox",
platform="macOS",
user_agent=(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0"
),
sec_ch_ua="",
sec_ch_ua_full_version_list="",
sec_ch_ua_mobile="",
sec_ch_ua_platform="",
sec_ch_ua_platform_version="",
sec_ch_ua_model="",
accept=(
"text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8"
),
accept_language_options=(
"en-US,en;q=0.5",
"en-GB,en;q=0.5",
"en-CA,en;q=0.5",
),
extra_headers={"dnt": "1", "priority": "u=0, i"},
)
FIREFOX_LINUX = BrowserProfile(
name="firefox_linux",
browser="firefox",
platform="Linux",
user_agent=("Mozilla/5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"),
sec_ch_ua="",
sec_ch_ua_full_version_list="",
sec_ch_ua_mobile="",
sec_ch_ua_platform="",
sec_ch_ua_platform_version="",
sec_ch_ua_model="",
accept=(
"text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8"
),
accept_language_options=(
"en-US,en;q=0.5",
"en-GB,en;q=0.5",
),
extra_headers={"dnt": "1", "priority": "u=0, i"},
)
# ---------------------------------------------------------------------------
# Safari profiles
# ---------------------------------------------------------------------------
SAFARI_MAC = BrowserProfile(
name="safari_mac",
browser="safari",
platform="macOS",
user_agent=(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) "
"Version/17.6 Safari/605.1.15"
),
sec_ch_ua="", # Safari does not send Client Hints
sec_ch_ua_full_version_list="",
sec_ch_ua_mobile="",
sec_ch_ua_platform="",
sec_ch_ua_platform_version="",
sec_ch_ua_model="",
accept=("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
accept_language_options=(
"en-US,en;q=0.9",
"en-GB,en;q=0.9",
),
accept_encoding="gzip, deflate, br",
)
SAFARI_IOS = BrowserProfile(
name="safari_ios",
browser="safari",
platform="iOS",
mobile=True,
user_agent=(
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_6 like Mac OS X) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) "
"Version/17.6 Mobile/15E148 Safari/604.1"
),
sec_ch_ua="",
sec_ch_ua_full_version_list="",
sec_ch_ua_mobile="",
sec_ch_ua_platform="",
sec_ch_ua_platform_version="",
sec_ch_ua_model="",
accept=("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
accept_language_options=(
"en-US,en;q=0.9",
"en-GB,en;q=0.9",
),
accept_encoding="gzip, deflate, br",
)
# ---------------------------------------------------------------------------
# Edge profiles
# ---------------------------------------------------------------------------
EDGE_WIN = BrowserProfile(
name="edge_win",
browser="edge",
platform="Windows",
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"
),
sec_ch_ua=('"Microsoft Edge";v="133", "Chromium";v="133", "Not(A:Brand";v="99"'),
sec_ch_ua_full_version_list=(
'"Microsoft Edge";v="133.0.3065.69", '
'"Chromium";v="133.0.6943.98", '
'"Not(A:Brand";v="99.0.0.0"'
),
sec_ch_ua_mobile="?0",
sec_ch_ua_platform='"Windows"',
sec_ch_ua_platform_version='"10.0.0"',
sec_ch_ua_model='""',
)
EDGE_MAC = BrowserProfile(
name="edge_mac",
browser="edge",
platform="macOS",
user_agent=(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"
),
sec_ch_ua=('"Microsoft Edge";v="133", "Chromium";v="133", "Not(A:Brand";v="99"'),
sec_ch_ua_full_version_list=(
'"Microsoft Edge";v="133.0.3065.69", '
'"Chromium";v="133.0.6943.98", '
'"Not(A:Brand";v="99.0.0.0"'
),
sec_ch_ua_mobile="?0",
sec_ch_ua_platform='"macOS"',
sec_ch_ua_platform_version='"14.5.0"',
sec_ch_ua_model='""',
)
# ---------------------------------------------------------------------------
# Profile registry
# ---------------------------------------------------------------------------
_PROFILES: dict[str, BrowserProfile] = {
p.name: p
for p in [
CHROME_WIN,
CHROME_MAC,
CHROME_LINUX,
CHROME_ANDROID,
FIREFOX_WIN,
FIREFOX_MAC,
FIREFOX_LINUX,
SAFARI_MAC,
SAFARI_IOS,
EDGE_WIN,
EDGE_MAC,
]
}
# Weighted by approximate real-world browser market share.
# Chrome ~65%, Firefox ~18%, Safari ~12%, Edge ~5%.
DESKTOP_PROFILES: list[BrowserProfile] = [
CHROME_WIN,
CHROME_MAC,
CHROME_LINUX,
FIREFOX_WIN,
FIREFOX_MAC,
FIREFOX_LINUX,
SAFARI_MAC,
EDGE_WIN,
EDGE_MAC,
]
MOBILE_PROFILES: list[BrowserProfile] = [
CHROME_ANDROID,
SAFARI_IOS,
]
PROFILE_WEIGHTS: dict[str, float] = {
"chrome_win": 0.30,
"chrome_mac": 0.20,
"chrome_linux": 0.15,
"chrome_android": 0.10,
"firefox_win": 0.07,
"firefox_mac": 0.05,
"firefox_linux": 0.03,
"safari_mac": 0.05,
"safari_ios": 0.02,
"edge_win": 0.02,
"edge_mac": 0.01,
}
[docs]
def get_profile(name: str) -> BrowserProfile:
"""Get a browser profile by name.
Args:
name: Profile name (e.g. ``'chrome_win'``, ``'firefox_mac'``).
Returns:
The matching :class:`BrowserProfile` instance.
Raises:
KeyError: If no profile matches the given name.
Examples:
::
>>> get_profile("chrome_win").browser
'chrome'
"""
return _PROFILES[name]
[docs]
def list_profiles() -> list[str]:
"""List all available profile names.
Returns:
A sorted list of registered profile name strings.
Examples:
::
>>> "chrome_win" in list_profiles()
True
"""
return sorted(_PROFILES.keys())