55 lines
1.7 KiB
Python
55 lines
1.7 KiB
Python
"""Lightweight browser/OS classification for human traffic (Chapter 08).
|
|
|
|
Separate from Chapter 07's bot_identifier — that identifies automated
|
|
crawlers via UA substring + DNS verification. This classifies ordinary
|
|
browsers/OSes, same ordered-substring-match pattern, signatures as JSON
|
|
data (not hardcoded), no third-party UA-parsing dependency — consistent
|
|
with the low-footprint bias in Chapter 03.
|
|
|
|
Order matters within each signature list: e.g. Edge/Opera must be checked
|
|
before Chrome (their UAs also contain "Chrome/"); iOS before macOS (iPhone
|
|
UAs also contain "like Mac OS X"); Android before Linux (Android UAs also
|
|
contain "Linux").
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
_DATA_DIR = Path(__file__).parent / "data"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class UASignature:
|
|
name: str
|
|
ua_substrings: tuple[str, ...]
|
|
|
|
|
|
def _load(filename: str) -> list[UASignature]:
|
|
raw = json.loads((_DATA_DIR / filename).read_text())
|
|
return [UASignature(name=e["name"], ua_substrings=tuple(e["ua_substrings"])) for e in raw]
|
|
|
|
|
|
_BROWSER_SIGNATURES = _load("browser_signatures.json")
|
|
_OS_SIGNATURES = _load("os_signatures.json")
|
|
|
|
|
|
def _match(user_agent: str, signatures: list[UASignature], default: str) -> str:
|
|
for sig in signatures:
|
|
if any(sub in user_agent for sub in sig.ua_substrings):
|
|
return sig.name
|
|
return default
|
|
|
|
|
|
def classify_browser(user_agent: str | None) -> str:
|
|
if not user_agent:
|
|
return "Unknown"
|
|
return _match(user_agent, _BROWSER_SIGNATURES, "Other")
|
|
|
|
|
|
def classify_os(user_agent: str | None) -> str:
|
|
if not user_agent:
|
|
return "Unknown"
|
|
return _match(user_agent, _OS_SIGNATURES, "Other")
|