|
import random |
|
from typing import Optional, Literal, List, Dict, Tuple |
|
import re |
|
|
|
|
|
class UserAgentGenerator: |
|
""" |
|
Generate random user agents with specified constraints. |
|
|
|
Attributes: |
|
desktop_platforms (dict): A dictionary of possible desktop platforms and their corresponding user agent strings. |
|
mobile_platforms (dict): A dictionary of possible mobile platforms and their corresponding user agent strings. |
|
browser_combinations (dict): A dictionary of possible browser combinations and their corresponding user agent strings. |
|
rendering_engines (dict): A dictionary of possible rendering engines and their corresponding user agent strings. |
|
chrome_versions (list): A list of possible Chrome browser versions. |
|
firefox_versions (list): A list of possible Firefox browser versions. |
|
edge_versions (list): A list of possible Edge browser versions. |
|
safari_versions (list): A list of possible Safari browser versions. |
|
ios_versions (list): A list of possible iOS browser versions. |
|
android_versions (list): A list of possible Android browser versions. |
|
|
|
Methods: |
|
generate_user_agent( |
|
platform: Literal["desktop", "mobile"] = "desktop", |
|
browser: str = "chrome", |
|
rendering_engine: str = "chrome_webkit", |
|
chrome_version: Optional[str] = None, |
|
firefox_version: Optional[str] = None, |
|
edge_version: Optional[str] = None, |
|
safari_version: Optional[str] = None, |
|
ios_version: Optional[str] = None, |
|
android_version: Optional[str] = None |
|
): Generates a random user agent string based on the specified parameters. |
|
""" |
|
def __init__(self): |
|
|
|
self.desktop_platforms = { |
|
"windows": { |
|
"10_64": "(Windows NT 10.0; Win64; x64)", |
|
"10_32": "(Windows NT 10.0; WOW64)", |
|
}, |
|
"macos": { |
|
"intel": "(Macintosh; Intel Mac OS X 10_15_7)", |
|
"newer": "(Macintosh; Intel Mac OS X 10.15; rv:109.0)", |
|
}, |
|
"linux": { |
|
"generic": "(X11; Linux x86_64)", |
|
"ubuntu": "(X11; Ubuntu; Linux x86_64)", |
|
"chrome_os": "(X11; CrOS x86_64 14541.0.0)", |
|
} |
|
} |
|
|
|
self.mobile_platforms = { |
|
"android": { |
|
"samsung": "(Linux; Android 13; SM-S901B)", |
|
"pixel": "(Linux; Android 12; Pixel 6)", |
|
"oneplus": "(Linux; Android 13; OnePlus 9 Pro)", |
|
"xiaomi": "(Linux; Android 12; M2102J20SG)", |
|
}, |
|
"ios": { |
|
"iphone": "(iPhone; CPU iPhone OS 16_5 like Mac OS X)", |
|
"ipad": "(iPad; CPU OS 16_5 like Mac OS X)", |
|
} |
|
} |
|
|
|
|
|
self.browser_combinations = { |
|
1: [ |
|
["chrome"], |
|
["firefox"], |
|
["safari"], |
|
["edge"] |
|
], |
|
2: [ |
|
["gecko", "firefox"], |
|
["chrome", "safari"], |
|
["webkit", "safari"] |
|
], |
|
3: [ |
|
["chrome", "safari", "edge"], |
|
["webkit", "chrome", "safari"] |
|
] |
|
} |
|
|
|
|
|
self.rendering_engines = { |
|
"chrome_webkit": "AppleWebKit/537.36", |
|
"safari_webkit": "AppleWebKit/605.1.15", |
|
"gecko": [ |
|
"Gecko/20100101", |
|
"Gecko/20100101", |
|
"Gecko/2010010", |
|
] |
|
} |
|
|
|
|
|
self.chrome_versions = [ |
|
"Chrome/119.0.6045.199", |
|
"Chrome/118.0.5993.117", |
|
"Chrome/117.0.5938.149", |
|
"Chrome/116.0.5845.187", |
|
"Chrome/115.0.5790.171", |
|
] |
|
|
|
self.edge_versions = [ |
|
"Edg/119.0.2151.97", |
|
"Edg/118.0.2088.76", |
|
"Edg/117.0.2045.47", |
|
"Edg/116.0.1938.81", |
|
"Edg/115.0.1901.203", |
|
] |
|
|
|
self.safari_versions = [ |
|
"Safari/537.36", |
|
"Safari/605.1.15", |
|
"Safari/604.1", |
|
"Safari/602.1", |
|
"Safari/601.5.17", |
|
] |
|
|
|
|
|
self.firefox_versions = [ |
|
"Firefox/119.0", |
|
"Firefox/118.0.2", |
|
"Firefox/117.0.1", |
|
"Firefox/116.0", |
|
"Firefox/115.0.3", |
|
"Firefox/114.0.2", |
|
"Firefox/113.0.1", |
|
"Firefox/112.0", |
|
"Firefox/111.0.1", |
|
"Firefox/110.0", |
|
] |
|
|
|
def get_browser_stack(self, num_browsers: int = 1) -> List[str]: |
|
""" |
|
Get a valid combination of browser versions. |
|
|
|
How it works: |
|
1. Check if the number of browsers is supported. |
|
2. Randomly choose a combination of browsers. |
|
3. Iterate through the combination and add browser versions. |
|
4. Return the browser stack. |
|
|
|
Args: |
|
num_browsers: Number of browser specifications (1-3) |
|
|
|
Returns: |
|
List[str]: A list of browser versions. |
|
""" |
|
if num_browsers not in self.browser_combinations: |
|
raise ValueError(f"Unsupported number of browsers: {num_browsers}") |
|
|
|
combination = random.choice(self.browser_combinations[num_browsers]) |
|
browser_stack = [] |
|
|
|
for browser in combination: |
|
if browser == "chrome": |
|
browser_stack.append(random.choice(self.chrome_versions)) |
|
elif browser == "firefox": |
|
browser_stack.append(random.choice(self.firefox_versions)) |
|
elif browser == "safari": |
|
browser_stack.append(random.choice(self.safari_versions)) |
|
elif browser == "edge": |
|
browser_stack.append(random.choice(self.edge_versions)) |
|
elif browser == "gecko": |
|
browser_stack.append(random.choice(self.rendering_engines["gecko"])) |
|
elif browser == "webkit": |
|
browser_stack.append(self.rendering_engines["chrome_webkit"]) |
|
|
|
return browser_stack |
|
|
|
def generate(self, |
|
device_type: Optional[Literal['desktop', 'mobile']] = None, |
|
os_type: Optional[str] = None, |
|
device_brand: Optional[str] = None, |
|
browser_type: Optional[Literal['chrome', 'edge', 'safari', 'firefox']] = None, |
|
num_browsers: int = 3) -> str: |
|
""" |
|
Generate a random user agent with specified constraints. |
|
|
|
Args: |
|
device_type: 'desktop' or 'mobile' |
|
os_type: 'windows', 'macos', 'linux', 'android', 'ios' |
|
device_brand: Specific device brand |
|
browser_type: 'chrome', 'edge', 'safari', or 'firefox' |
|
num_browsers: Number of browser specifications (1-3) |
|
""" |
|
|
|
platform = self.get_random_platform(device_type, os_type, device_brand) |
|
|
|
|
|
components = ["Mozilla/5.0", platform] |
|
|
|
|
|
browser_stack = self.get_browser_stack(num_browsers) |
|
|
|
|
|
if "Firefox" in str(browser_stack): |
|
components.append(random.choice(self.rendering_engines["gecko"])) |
|
elif "Chrome" in str(browser_stack) or "Safari" in str(browser_stack): |
|
components.append(self.rendering_engines["chrome_webkit"]) |
|
components.append("(KHTML, like Gecko)") |
|
|
|
|
|
components.extend(browser_stack) |
|
|
|
return " ".join(components) |
|
|
|
def generate_with_client_hints(self, **kwargs) -> Tuple[str, str]: |
|
"""Generate both user agent and matching client hints""" |
|
user_agent = self.generate(**kwargs) |
|
client_hints = self.generate_client_hints(user_agent) |
|
return user_agent, client_hints |
|
|
|
def get_random_platform(self, device_type, os_type, device_brand): |
|
"""Helper method to get random platform based on constraints""" |
|
platforms = self.desktop_platforms if device_type == 'desktop' else \ |
|
self.mobile_platforms if device_type == 'mobile' else \ |
|
{**self.desktop_platforms, **self.mobile_platforms} |
|
|
|
if os_type: |
|
for platform_group in [self.desktop_platforms, self.mobile_platforms]: |
|
if os_type in platform_group: |
|
platforms = {os_type: platform_group[os_type]} |
|
break |
|
|
|
os_key = random.choice(list(platforms.keys())) |
|
if device_brand and device_brand in platforms[os_key]: |
|
return platforms[os_key][device_brand] |
|
return random.choice(list(platforms[os_key].values())) |
|
|
|
def parse_user_agent(self, user_agent: str) -> Dict[str, str]: |
|
"""Parse a user agent string to extract browser and version information""" |
|
browsers = { |
|
'chrome': r'Chrome/(\d+)', |
|
'edge': r'Edg/(\d+)', |
|
'safari': r'Version/(\d+)', |
|
'firefox': r'Firefox/(\d+)' |
|
} |
|
|
|
result = {} |
|
for browser, pattern in browsers.items(): |
|
match = re.search(pattern, user_agent) |
|
if match: |
|
result[browser] = match.group(1) |
|
|
|
return result |
|
|
|
def generate_client_hints(self, user_agent: str) -> str: |
|
"""Generate Sec-CH-UA header value based on user agent string""" |
|
browsers = self.parse_user_agent(user_agent) |
|
|
|
|
|
hints = [] |
|
|
|
|
|
if 'chrome' in browsers: |
|
hints.append(f'"Chromium";v="{browsers["chrome"]}"') |
|
hints.append('"Not_A Brand";v="8"') |
|
|
|
if 'edge' in browsers: |
|
hints.append(f'"Microsoft Edge";v="{browsers["edge"]}"') |
|
else: |
|
hints.append(f'"Google Chrome";v="{browsers["chrome"]}"') |
|
|
|
elif 'firefox' in browsers: |
|
|
|
return '""' |
|
|
|
elif 'safari' in browsers: |
|
|
|
hints.append(f'"Safari";v="{browsers["safari"]}"') |
|
hints.append('"Not_A Brand";v="8"') |
|
|
|
return ', '.join(hints) |
|
|
|
|
|
if __name__ == "__main__": |
|
generator = UserAgentGenerator() |
|
print(generator.generate()) |
|
|
|
print("\nSingle browser (Chrome):") |
|
print(generator.generate(num_browsers=1, browser_type='chrome')) |
|
|
|
print("\nTwo browsers (Gecko/Firefox):") |
|
print(generator.generate(num_browsers=2)) |
|
|
|
print("\nThree browsers (Chrome/Safari/Edge):") |
|
print(generator.generate(num_browsers=3)) |
|
|
|
print("\nFirefox on Linux:") |
|
print(generator.generate( |
|
device_type='desktop', |
|
os_type='linux', |
|
browser_type='firefox', |
|
num_browsers=2 |
|
)) |
|
|
|
print("\nChrome/Safari/Edge on Windows:") |
|
print(generator.generate( |
|
device_type='desktop', |
|
os_type='windows', |
|
num_browsers=3 |
|
)) |