mrkeyoor.com_
Sat 08 Aug 20:59 UTC
PyPIUtilsupdated 08 Aug 2026

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.

Verdict

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.

API stability3/5The 1.x surface is small, typed and settled, and the resolver protocol is documented for anyone implementing their own. The score reflects the 1.0 break: the pre-1.0 user_agent_parser module and its dictionary results were replaced outright, which invalidated years of published examples, and a migration guide exists because it was needed.
Docs4/5There is a real Read the Docs site with an API reference, a guides section explaining the trade-offs between the built-in resolvers, an upgrade guide from 0.x, and a README with runnable doctests for each function. What is thin is guidance on the operational realities, such as what proportion of live traffic goes unmatched or how to combine this with Client Hints.
Maintenance4/5Version 1.0.2 was released on 2026-04-05 and the repository was pushed on 2026-05-27, with only 12 open issues and pull requests. The project supports CPython 3.10 and newer, recent pypy and GraalPy 25, and sits under the shared ua-parser organisation, so the regex data is maintained by a wider group than the Python binding.
Ecosystem4/5Roughly 6,402,725 weekly downloads with 648 stars, so most installs arrive as a transitive dependency of analytics and logging tooling. Sharing uap-core with the JavaScript, Ruby, Java, Go and PHP implementations is the real strength: the same string classifies identically across a polyglot stack, and the user-agents package builds convenience helpers on top.

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
Skip it if

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 None

Version 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'] += 1

Repeated 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-builtins

New 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

PackageRegistryPick it when
user-agentsPyPIYou want convenience properties such as is_mobile, is_tablet, is_bot and a readable summary string on top of this parser
device_detectorPyPIYou need richer classification including bots, brands and device types from the Matomo dataset rather than uap-core
ua-parser-rsPyPIYou are already installing the regex extra and want to call the Rust matcher directly without the Python wrapper