ua-parser
The official Python implementation of the ua-parser project, which matches user agent strings against the shared uap-core regex set to work out browser, operating system and device. The top level is four functions: parse for everything, and parse_user_agent, parse_os and parse_device when you only want one part. Underneath sits a resolver stack you can swap, with a pure Python matcher, a google-re2 matcher and a Rust matcher available through extras, plus a caching layer in front. The regexes themselves live in a separate ua-parser-builtins package, so data updates and library updates are independent.
The reference Python implementation of a cross-language dataset, and a sound choice for classifying server logs, provided you install the regex extra and use the 1.x API rather than the pre-1.0 examples that dominate search results. Just be clear that user agent parsing is a shrinking source of truth, and that anything load-bearing should move to Client Hints.
Use it if
- You need to classify server-side request logs by browser, OS or device and want the same answers as the JavaScript, Ruby and Go implementations of ua-parser
- You are parsing at volume, where the regex extra backed by Rust plus the built-in caching resolver make a large difference over the pure Python matcher
- You only need one field, since parse_user_agent, parse_os and parse_device skip the matching work for the domains you did not ask for
- You want a maintained implementation of a shared cross-language dataset rather than a hand-rolled regex table that nobody updates
- You are trying to detect capabilities. This tells you a name and a version number, not whether the client supports a feature, and building feature decisions on it ages badly
- You are reading modern browser user agents and expecting precision. Chrome and Edge freeze and reduce their strings deliberately, so minor versions, exact OS versions and device models are often unavailable at the source, and User Agent Client Hints are the intended replacement. No parser can recover information the browser no longer sends
- You followed a pre-1.0 tutorial. Version 1.0 replaced ua_parser.user_agent_parser.Parse and its dictionary results with a new top-level API returning dataclasses, so almost every example you find online is for the old shape
- You installed it plainly and expect speed. Without the regex or re2 extra you get the pure Python resolver, which the README calls significantly slower, and the extra is described as strongly recommended rather than optional
- You want rich device classification such as bot detection, mobile versus tablet flags or brand normalisation, which are not here; user-agents and device_detector exist for exactly that
- Your Python is older than 3.10, which is the floor for the 1.x line
Setup reality
Install ua-parser[regex] rather than plain ua-parser, because the documentation says the pure Python resolver is significantly slower and the plain install is the one you get by accident. The extras are regex for the Rust backed ua-parser-rs, re2 for google-re2, and yaml if you want to load your own regex file; the built-in regex data always arrives through the ua-parser-builtins dependency, which is what you bump to get newer device coverage without touching the library version. The first call to any top-level function lazily builds a global parser, which loads the matcher set and can take a noticeable moment; touching ua_parser.parser explicitly at startup moves that cost out of your first request. That global parser wraps a caching resolver holding 2000 entries, which is a good default for log processing where the same strings repeat, and the exact caching behaviour is documented as unspecified, so build your own Parser if you need it to be deterministic. Results are typed and every field is optional: parse('') returns a Result with user_agent, os and device all None, and parse_user_agent on an unmatched string returns None rather than an empty object, so None handling belongs in the call site rather than at the end. Version fields are strings, not integers, and can be None at any level, so comparing major versions numerically means guarding both. Expect a meaningful share of real traffic to come back unmatched or with generic values, because bots, embedded webviews and reduced user agents genuinely do not carry the information.
Patterns
Parse a full user agent stringparse-everything
from ua_parser import parse
result = parse(request.headers['User-Agent'])
print(result.user_agent) # UserAgent(family='Chrome', major='41', ...)
print(result.os)
print(result.device)Any domain that does not match comes back as None, including all three at once for an empty string. Check before attribute access or you get an AttributeError on real traffic.
Ask for only the part you needparse-single-domain
from ua_parser import parse_user_agent, parse_os, parse_device
browser = parse_user_agent(ua_string)
os_info = parse_os(ua_string)
device = parse_device(ua_string)These skip the matching work for the other domains, so they are faster than parse when you only want one. Each returns None rather than an empty object on no match.
Cope with fields that are not therehandle-missing-values
browser = parse_user_agent(ua_string)
family = browser.family if browser else 'Unknown'
major = int(browser.major) if browser and browser.major else NoneVersion parts are strings and any of them can be None, which is increasingly common as browsers reduce their user agents. Casting without both guards is the usual crash.
Get non-null fields for reportinguse-defaulted-result
from ua_parser import parse
filled = parse(ua_string).with_defaults()
print(filled.user_agent.family, filled.os.family, filled.device.family)with_defaults() returns a DefaultedResult where the three domains are always present, using 'Other' for unmatched families. Handy for grouping in a dashboard, misleading if you treat 'Other' as a real value.
Get the fast matcher instead of pure Pythoninstall-fast-resolver
pip install 'ua-parser[regex]'
# alternative backend
pip install 'ua-parser[re2]'Plain ua-parser installs the pure Python resolver, which the documentation calls significantly slower. The extra is picked up automatically at import; no code change is needed.
Move first-call cost to startupwarm-global-parser
import ua_parser
def on_startup():
ua_parser.parser # forces lazy initialisation now
The global parser is built on first access and loading the matcher set is not free. Touching it during startup keeps that cost out of the first user request.
Create your own parser instead of the global onebuild-custom-parser
from ua_parser import Parser, CachingResolver, Cache, BasicResolver
from ua_parser.loaders import load_builtins
parser = Parser(CachingResolver(BasicResolver(load_builtins()), Cache(10_000)))
result = parser.parse(ua_string)The global parser's caching behaviour is documented as unspecified. Building your own is the supported way to fix the cache size and the resolver for reproducible benchmarks.
Classify a log file efficientlybatch-process-logs
from collections import Counter
from ua_parser import parse_user_agent
counts = Counter()
with open('access.log') as fh:
for line in fh:
ua = extract_ua(line)
browser = parse_user_agent(ua)
counts[browser.family if browser else 'Unknown'] += 1Repeated strings hit the default cache, which is where most of the speed on real logs comes from. Deduplicating unique strings first is faster still when the file is large.
Resolve specific domains in one passparse-partial-domains
from ua_parser import Domain
import ua_parser
partial = ua_parser.parser(ua_string, Domain.USER_AGENT | Domain.OS)
print(partial.user_agent, partial.os)Calling the parser directly returns a PartialResult covering only the domains you asked for. Reading .device on it is not meaningful, since it was never resolved.
Notice when the string has no detail to givedetect-reduced-user-agent
browser = parse_user_agent(ua_string)
reduced = bool(browser) and browser.family in {'Chrome', 'Edge'} and browser.minor in (None, '0')
if reduced:
version = client_hints.get('Sec-CH-UA-Full-Version-List')Chrome and Edge freeze the minor and build parts, so a zeroed version is the string being reduced rather than a parse failure. The real value only arrives through Client Hints headers.
Refresh device coverage without upgrading the libraryupdate-regex-data
pip install --upgrade ua-parser-builtinsNew phones and browsers land in the shared uap-core dataset, which ships as this separate package. Pinning it means new devices classify as unknown until you bump it.
Use your own regex fileload-custom-regexes
from ua_parser import Parser
from ua_parser.loaders import load_yaml
parser = Parser.from_matchers(load_yaml('custom_regexes.yaml'))
result = parser.parse(ua_string)Needs the yaml extra for PyYaml. Useful for internal clients and crawlers that uap-core will never know about, but you now own keeping that file current.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| user-agents | PyPI | You want convenience properties such as is_mobile, is_tablet, is_bot and a readable summary string on top of this parser |
| device_detector | PyPI | You need richer classification including bots, brands and device types from the Matomo dataset rather than uap-core |
| ua-parser-rs | PyPI | You are already installing the regex extra and want to call the Rust matcher directly without the Python wrapper |